-
Notifications
You must be signed in to change notification settings - Fork 103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
windows restart service #1681
Merged
Merged
windows restart service #1681
Changes from all commits
Commits
Show all changes
36 commits
Select commit
Hold shift + click to select a range
ea333c1
add basic working restart service installation and subcommand
zackattack01 59c87cf
wire in opts from launcher proper and localLogger, more clean up
zackattack01 0bc3cc9
gofmt and remove unused account const
zackattack01 69d049e
stash changes for rebase with exit wrapper updates
zackattack01 90be693
fix up restart service to use new os.Exit wrapper
zackattack01 7997c98
add sqlite log publication
zackattack01 1648fae
update restart service to use new gowrapper
zackattack01 1b52d9e
clean up and close log publisher on interrupt
zackattack01 e0cdfd2
fix DeleteRows when called without rowids
zackattack01 c700844
add in enable_launcher_watchdog flag and update types+knapsack
zackattack01 b0e1262
remove watchdog when disabled, add launcher watchdog to options
zackattack01 eca53df
unstage tests, stash working version post refactor
zackattack01 0de997e
rework logstore interfaces, break off from kevalue, bugfixes
zackattack01 7d405e7
re-add staged fix for non-windows compilation
zackattack01 ac9a49b
test updates
zackattack01 04c0288
bring back test updates
zackattack01 2a54920
gofmt
zackattack01 3cd80b4
remove temporary option manipulation from local testing
zackattack01 c891283
remove flag from startup settings, reset tests
zackattack01 cf7144c
unstage options for PR breakout
zackattack01 0333b2f
add some documentation, small optimizations
zackattack01 88896fa
test updates
zackattack01 fbfeed8
add explicit install and removal logs, update docs
zackattack01 6bb1380
remove duplicate install log
zackattack01 fc198db
PR feedback: early returns for failed installation paths, better logging
zackattack01 c66661e
PR feedback: use backoff.Waitfor for service restart logic
zackattack01 d359bab
Apply suggestions from code review
zackattack01 ac8ff41
PR feedback: move watchdog package to ee, rename docs to README.md
zackattack01 d0aec27
PR feedback: const for serviceResetPeriod, logging and code flow impr…
zackattack01 733c591
Update ee/agent/storage/sqlite/logstore_sqlite.go
zackattack01 fc5b0f0
PR review: move docs to docs/architecture, add clarifying comments, m…
zackattack01 1be4237
PR feedback and refactor: bring logwriter logic directly into sqliteS…
zackattack01 2d68f1e
add logic to integrate power_event_watcher, don't restart when in mod…
zackattack01 7f4df2b
PR review: update rungroup name to be camelCase
zackattack01 ea0fbad
PR Review: add backoffs to service manager connection and service del…
zackattack01 093c89e
Update ee/watchdog/watchdog_service_windows.go
zackattack01 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
### Watchdog Service | ||
|
||
Note that for the initial implementation, this service is windows only. It is intentionally designed to give room for alternate OS implementations if needed in the future. | ||
Most of the relevant code can be found in [ee/watchdog](../../ee/watchdog/) | ||
|
||
Here is a basic sequence diagram displaying the enable path for the windows watchdog service. The `launcher_watchdog_enabled` control flag will trigger the initial configuration and installation, and removal of the flag will trigger removal of the service. | ||
|
||
```mermaid | ||
sequenceDiagram | ||
participant LauncherKolideK2Svc | ||
Note right of LauncherKolideK2Svc: ./launcher.exe svc ... | ||
create participant WindowsServiceManager | ||
LauncherKolideK2Svc->>WindowsServiceManager: if launcher_watchdog_enabled | ||
create participant LauncherKolideWatchdogSvc | ||
WindowsServiceManager->>LauncherKolideWatchdogSvc: have we installed the watchdog? | ||
Note left of LauncherKolideWatchdogSvc: ./launcher.exe watchdog | ||
|
||
alt yes the service already exists | ||
LauncherKolideK2Svc->>LauncherKolideWatchdogSvc: Restart to ensure latest | ||
else no the service does not exist | ||
LauncherKolideK2Svc->>WindowsServiceManager: 1 - create, configure, etc | ||
LauncherKolideK2Svc->>LauncherKolideWatchdogSvc: 2 - Start | ||
activate LauncherKolideWatchdogSvc | ||
end | ||
|
||
loop every n minutes | ||
LauncherKolideWatchdogSvc->>WindowsServiceManager: Query LauncherKolideK2Svc status | ||
LauncherKolideWatchdogSvc->>LauncherKolideK2Svc: Start if Stopped | ||
end | ||
``` | ||
|
||
The restart functionality is currently limited to detecting a stopped state, but the idea here is to lay out the foundation for more advanced healthchecking. | ||
The watchdog service itself runs as a separate invocation of launcher, writing all logs to sqlite. The main invocation of launcher runs a watchdog controller, which responds to the `launcher_watchdog_enabled` flag, and publishes all sqlite logs to debug.json. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
package agentsqlite | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"strings" | ||
"time" | ||
) | ||
|
||
func (s *sqliteStore) AppendValue(timestamp int64, value []byte) error { | ||
colInfo := s.getColumns() | ||
if s == nil || s.conn == nil || colInfo == nil { | ||
return errors.New("store is nil") | ||
} | ||
|
||
if s.readOnly { | ||
return errors.New("cannot perform update with RO connection") | ||
} | ||
|
||
if !colInfo.isLogstore { | ||
return errors.New("this table type does not support adding values by timestamp") | ||
} | ||
|
||
insertSql := fmt.Sprintf( | ||
`INSERT INTO %s (%s, %s) VALUES (?, ?)`, | ||
directionless marked this conversation as resolved.
Show resolved
Hide resolved
|
||
s.tableName, | ||
colInfo.pk, | ||
colInfo.valueColumn, | ||
) | ||
|
||
if _, err := s.conn.Exec(insertSql, timestamp, value); err != nil { | ||
return fmt.Errorf("appending row into %s: %w", s.tableName, err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (s *sqliteStore) DeleteRows(rowids ...any) error { | ||
if s == nil || s.conn == nil { | ||
return errors.New("store is nil") | ||
} | ||
|
||
if s.readOnly { | ||
return errors.New("cannot perform deletes with RO connection") | ||
} | ||
|
||
if len(rowids) == 0 { | ||
return nil | ||
} | ||
|
||
// interpolate the proper number of question marks | ||
paramQs := strings.Repeat("?,", len(rowids)) | ||
paramQs = paramQs[:len(paramQs)-1] | ||
deleteSql := fmt.Sprintf(`DELETE FROM %s WHERE rowid IN (%s)`, s.tableName, paramQs) | ||
|
||
if _, err := s.conn.Exec(deleteSql, rowids...); err != nil { | ||
return fmt.Errorf("deleting row from %s: %w", s.tableName, err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (s *sqliteStore) ForEach(fn func(rowid, timestamp int64, v []byte) error) error { | ||
colInfo := s.getColumns() | ||
if s == nil || s.conn == nil || colInfo == nil { | ||
return errors.New("store is nil") | ||
} | ||
RebeccaMahany marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if !colInfo.isLogstore { | ||
return errors.New("this table type is not supported for timestamped iteration") | ||
} | ||
|
||
query := fmt.Sprintf( | ||
`SELECT rowid, %s, %s FROM %s;`, | ||
colInfo.pk, | ||
colInfo.valueColumn, | ||
s.tableName, | ||
) | ||
|
||
rows, err := s.conn.Query(query) | ||
if err != nil { | ||
return fmt.Errorf("issuing foreach query: %w", err) | ||
} | ||
|
||
defer rows.Close() | ||
|
||
for rows.Next() { | ||
var rowid int64 | ||
var timestamp int64 | ||
var result string | ||
if err := rows.Scan(&rowid, ×tamp, &result); err != nil { | ||
return fmt.Errorf("scanning foreach query: %w", err) | ||
} | ||
|
||
if err := fn(rowid, timestamp, []byte(result)); err != nil { | ||
return fmt.Errorf("caller error during foreach iteration: %w", err) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Write implements the io.Writer interface, allowing sqliteStore to be used as | ||
// a logging backend via multislogger handler | ||
func (s *sqliteStore) Write(p []byte) (n int, err error) { | ||
if s.readOnly { | ||
return 0, errors.New("cannot perform write with RO connection") | ||
} | ||
|
||
colInfo := s.getColumns() | ||
if s == nil || s.conn == nil || colInfo == nil { | ||
return 0, errors.New("store is nil") | ||
} | ||
|
||
if !colInfo.isLogstore { | ||
return 0, errors.New("this table type is not supported for timestamped logging") | ||
} | ||
|
||
timestamp := time.Now().Unix() | ||
if err := s.AppendValue(timestamp, p); err != nil { | ||
return 0, err | ||
} | ||
|
||
return len(p), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package agentsqlite | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestAppendAndIterateValues(t *testing.T) { | ||
t.Parallel() | ||
|
||
testRootDir := t.TempDir() | ||
|
||
s, err := OpenRW(context.TODO(), testRootDir, WatchdogLogStore) | ||
require.NoError(t, err, "creating test store") | ||
|
||
startTime := time.Now() | ||
expectedLogCount := 5 | ||
for i := 0; i < expectedLogCount; i++ { | ||
currTime := startTime.Add(time.Duration(i) * time.Minute) | ||
logEntry := fmt.Sprintf(`{"time":"%s", "msg":"testMessage%d"}`, currTime.Format(time.RFC3339), i) | ||
require.NoError(t, s.AppendValue(currTime.Unix(), []byte(logEntry)), "expected no error appending value row") | ||
} | ||
|
||
logsSeen := 0 | ||
err = s.ForEach(func(rowid, timestamp int64, v []byte) error { | ||
logRecord := make(map[string]any) | ||
|
||
require.NoError(t, json.Unmarshal(v, &logRecord), "expected to be able to unmarshal row value") | ||
expectedTime := startTime.Add(time.Duration(logsSeen) * time.Minute) | ||
require.Equal(t, expectedTime.Unix(), timestamp, "expected log timestamp to match") | ||
|
||
logsSeen++ | ||
return nil | ||
}) | ||
|
||
require.NoError(t, err, "expected no error iterating over new logs") | ||
require.Equal(t, expectedLogCount, logsSeen, "did not see expected count of logs during iteration") | ||
|
||
require.NoError(t, s.Close()) | ||
} |
1 change: 1 addition & 0 deletions
1
ee/agent/storage/sqlite/migrations/000002_add_watchdog_logs.down.sqlite
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
DROP TABLE IF EXISTS watchdog_logs; |
4 changes: 4 additions & 0 deletions
4
ee/agent/storage/sqlite/migrations/000002_add_watchdog_logs.up.sqlite
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
CREATE TABLE IF NOT EXISTS watchdog_logs ( | ||
timestamp INT NOT NULL, | ||
log TEXT | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want to push other logs here (say early startup logs) how would we do it? Would we make a new store? Accept it as slightly misnamed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think new store will give us more implementation flexibility and we can still re-use all of the new interfaces and wire that in pretty easily