feat(store): add hosts.always_on flag (default on)
This commit is contained in:
+22
-4
@@ -44,7 +44,7 @@ func (s *Store) LookupHostByAgentToken(ctx context.Context, tokenHash string) (*
|
||||
repo_size_bytes, snapshot_count, open_alert_count,
|
||||
applied_schedule_version, bandwidth_up_kbps, bandwidth_down_kbps,
|
||||
pre_hook_default, post_hook_default,
|
||||
repo_status, repo_status_error
|
||||
repo_status, repo_status_error, always_on
|
||||
FROM hosts WHERE agent_token_hash = ?`,
|
||||
tokenHash)
|
||||
return scanHost(row)
|
||||
@@ -59,7 +59,7 @@ func (s *Store) GetHost(ctx context.Context, id string) (*Host, error) {
|
||||
repo_size_bytes, snapshot_count, open_alert_count,
|
||||
applied_schedule_version, bandwidth_up_kbps, bandwidth_down_kbps,
|
||||
pre_hook_default, post_hook_default,
|
||||
repo_status, repo_status_error
|
||||
repo_status, repo_status_error, always_on
|
||||
FROM hosts WHERE id = ?`, id)
|
||||
return scanHost(row)
|
||||
}
|
||||
@@ -227,7 +227,7 @@ func (s *Store) ListHosts(ctx context.Context) ([]Host, error) {
|
||||
repo_size_bytes, snapshot_count, open_alert_count,
|
||||
applied_schedule_version, bandwidth_up_kbps, bandwidth_down_kbps,
|
||||
pre_hook_default, post_hook_default,
|
||||
repo_status, repo_status_error
|
||||
repo_status, repo_status_error, always_on
|
||||
FROM hosts ORDER BY name`)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("store: list hosts: %w", err)
|
||||
@@ -267,6 +267,7 @@ func scanHostRow(s hostScanner) (*Host, error) {
|
||||
tags string
|
||||
bwUp, bwDown sql.NullInt64
|
||||
preHook, postHook sql.NullString
|
||||
alwaysOn int
|
||||
)
|
||||
err := s.Scan(&h.ID, &h.Name, &h.OS, &h.Arch,
|
||||
&h.AgentVersion, &h.ResticVersion, &h.ProtocolVersion,
|
||||
@@ -275,7 +276,7 @@ func scanHostRow(s hostScanner) (*Host, error) {
|
||||
&h.RepoSizeBytes, &h.SnapshotCount, &h.OpenAlertCount,
|
||||
&h.AppliedScheduleVersion, &bwUp, &bwDown,
|
||||
&preHook, &postHook,
|
||||
&h.RepoStatus, &h.RepoStatusError)
|
||||
&h.RepoStatus, &h.RepoStatusError, &alwaysOn)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, ErrNotFound
|
||||
@@ -330,6 +331,7 @@ func scanHostRow(s hostScanner) (*Host, error) {
|
||||
if postHook.Valid {
|
||||
h.PostHookDefault = postHook.String
|
||||
}
|
||||
h.AlwaysOn = alwaysOn != 0
|
||||
return &h, nil
|
||||
}
|
||||
|
||||
@@ -378,6 +380,22 @@ func (s *Store) SetHostTags(ctx context.Context, hostID string, tags []string) e
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetHostAlwaysOn flips the host's always-on flag. true = 24x7 server
|
||||
// (default); false = intermittent host (laptop). See the
|
||||
// always-on-host-mode spec.
|
||||
func (s *Store) SetHostAlwaysOn(ctx context.Context, hostID string, alwaysOn bool) error {
|
||||
v := 0
|
||||
if alwaysOn {
|
||||
v = 1
|
||||
}
|
||||
_, err := s.db.ExecContext(ctx,
|
||||
`UPDATE hosts SET always_on = ? WHERE id = ?`, v, hostID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("store: set host always_on: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DistinctHostTags returns the union of every tag in use across the
|
||||
// fleet, sorted. Powers the autocomplete on the host-tags editor and
|
||||
// the chip-row filter on the dashboard. Cheap at fleet sizes this
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestHostAlwaysOnDefaultAndToggle(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
st := openTestStore(t)
|
||||
|
||||
h := Host{
|
||||
ID: "h-always-on", Name: "lap", OS: "linux", Arch: "amd64",
|
||||
ProtocolVersion: 1, EnrolledAt: time.Now().UTC(),
|
||||
}
|
||||
if err := st.CreateHost(ctx, h, "tok-hash", "pin"); err != nil {
|
||||
t.Fatalf("create host: %v", err)
|
||||
}
|
||||
got, err := st.GetHost(ctx, h.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("get host: %v", err)
|
||||
}
|
||||
if !got.AlwaysOn {
|
||||
t.Fatalf("new host should default to always_on=true, got false")
|
||||
}
|
||||
|
||||
if err := st.SetHostAlwaysOn(ctx, h.ID, false); err != nil {
|
||||
t.Fatalf("set always_on: %v", err)
|
||||
}
|
||||
got, err = st.GetHost(ctx, h.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("get host 2: %v", err)
|
||||
}
|
||||
if got.AlwaysOn {
|
||||
t.Fatalf("expected always_on=false after toggle, got true")
|
||||
}
|
||||
|
||||
hosts, err := st.ListHosts(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("list hosts: %v", err)
|
||||
}
|
||||
if len(hosts) != 1 || hosts[0].AlwaysOn {
|
||||
t.Fatalf("ListHosts should report always_on=false, got %+v", hosts)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
-- 0024: distinguish always-on (24x7 server) hosts from intermittent
|
||||
-- hosts (laptops/workstations that legitimately sleep). Default 1 so
|
||||
-- every existing and future host keeps today's offline/alert
|
||||
-- semantics unless explicitly opted out. Column-level ALTER per the
|
||||
-- repo's migration rules (no table rebuild — hosts has inbound FKs).
|
||||
ALTER TABLE hosts ADD COLUMN always_on INTEGER NOT NULL DEFAULT 1;
|
||||
@@ -99,6 +99,12 @@ type Host struct {
|
||||
// agent-side message when RepoStatus == "init_failed".
|
||||
RepoStatus string
|
||||
RepoStatusError string
|
||||
|
||||
// AlwaysOn is true for 24x7 server hosts (the default). When false
|
||||
// the host is intermittent (laptop/workstation): offline alerts are
|
||||
// suppressed, the UI shows an "asleep" state, and a missed backup is
|
||||
// caught up ~1 min after reconnect. See the always-on-host-mode spec.
|
||||
AlwaysOn bool
|
||||
}
|
||||
|
||||
// Schedule is now intentionally slim: cron + which groups + enabled.
|
||||
|
||||
Reference in New Issue
Block a user