server-client "show" reimplemented

Bug → expired matches are still present
This commit is contained in:
ppom 2023-09-24 16:01:56 +02:00
parent a30a6644dc
commit b6d7e5a946
5 changed files with 125 additions and 121 deletions

View File

@ -8,10 +8,12 @@ import (
"net" "net"
"os" "os"
"regexp" "regexp"
"gopkg.in/yaml.v3"
) )
const ( const (
Query = 0 Show = 0
Flush = 1 Flush = 1
) )
@ -22,7 +24,7 @@ type Request struct {
type Response struct { type Response struct {
Err error Err error
Actions ReadableMap ClientStatus ClientStatus
Number int Number int
} }
@ -31,6 +33,7 @@ func SendAndRetrieve(data Request) Response {
if err != nil { if err != nil {
log.Fatalln("Error opening connection top daemon:", err) log.Fatalln("Error opening connection top daemon:", err)
} }
defer conn.Close()
err = gob.NewEncoder(conn).Encode(data) err = gob.NewEncoder(conn).Encode(data)
if err != nil { if err != nil {
@ -45,19 +48,60 @@ func SendAndRetrieve(data Request) Response {
return response return response
} }
type PatternStatus struct {
Matches int `yaml:"matches_since_last_trigger"`
Actions map[string][]string `yaml:"pending_actions"`
}
type MapPatternStatus map[string]*PatternStatus
type ClientStatus map[string]map[string]MapPatternStatus
// This block is made to hide pending_actions when empty
// and matches_since_last_trigger when zero
type FullPatternStatus PatternStatus
type MatchesStatus struct {
Matches int `yaml:"matches_since_last_trigger"`
}
type ActionsStatus struct {
Actions map[string][]string `yaml:"pending_actions"`
}
func (mps MapPatternStatus) MarshalYAML() (interface{}, error) {
ret := make(map[string]interface{})
for k, v := range mps {
if v.Matches == 0 {
if len(v.Actions) != 0 {
ret[k] = ActionsStatus{v.Actions}
}
} else {
if len(v.Actions) != 0 {
ret[k] = v
} else {
ret[k] = MatchesStatus{v.Matches}
}
}
}
return ret, nil
}
// end block
func usage(err string) { func usage(err string) {
fmt.Println("Usage: reactionc") fmt.Println("Usage: reactionc")
fmt.Println("Usage: reactionc flush <PATTERN>") fmt.Println("Usage: reactionc flush <PATTERN>")
log.Fatalln(err) log.Fatalln(err)
} }
func ClientQuery(streamfilter string) { func ClientShow(streamfilter string) {
response := SendAndRetrieve(Request{Query, streamfilter}) response := SendAndRetrieve(Request{Show, streamfilter})
if response.Err != nil { if response.Err != nil {
log.Fatalln("Received error from daemon:", response.Err) log.Fatalln("Received error from daemon:", response.Err)
os.Exit(1) os.Exit(1)
} }
fmt.Println(response.Actions.ToString()) text, err := yaml.Marshal(response.ClientStatus)
if err != nil {
log.Fatalln("Failed to convert daemon binary response to text format:", err)
}
fmt.Println(string(text))
os.Exit(0) os.Exit(0)
} }

View File

@ -90,7 +90,6 @@ func (a *Action) exec(match string) {
} }
func ActionsManager() { func ActionsManager() {
actions := make(ActionsMap)
pendingActionsC := make(chan PAT) pendingActionsC := make(chan PAT)
var ( var (
pat PAT pat PAT
@ -111,12 +110,14 @@ func ActionsManager() {
wgActions.Add(1) wgActions.Add(1)
go action.exec(match) go action.exec(match)
} else { } else {
actionsLock.Lock()
// make sure map exists // make sure map exists
if actions[action] == nil { if actions[action] == nil {
actions[action] = make(PatternTimes) actions[action] = make(PatternTimes)
} }
// append() to nil is valid go // append() to nil is valid go
actions[action][match] = append(actions[action][match], then) actions[action][match] = append(actions[action][match], then)
actionsLock.Unlock()
go func(pat PAT, now time.Time) { go func(pat PAT, now time.Time) {
time.Sleep(pat.t.Sub(now)) time.Sleep(pat.t.Sub(now))
pendingActionsC <- pat pendingActionsC <- pat
@ -125,10 +126,13 @@ func ActionsManager() {
case pat = <-pendingActionsC: case pat = <-pendingActionsC:
match = pat.p match = pat.p
action = pat.a action = pat.a
actionsLock.Lock()
actions[action][match] = actions[action][match][1:] actions[action][match] = actions[action][match][1:]
actionsLock.Unlock()
wgActions.Add(1) wgActions.Add(1)
go action.exec(match) go action.exec(match)
case _, _ = <-stopActions: case _, _ = <-stopActions:
actionsLock.Lock()
for action := range actions { for action := range actions {
if action.OnExit { if action.OnExit {
for match := range actions[action] { for match := range actions[action] {
@ -137,6 +141,7 @@ func ActionsManager() {
} }
} }
} }
actionsLock.Unlock()
wgActions.Done() wgActions.Done()
return return
} }
@ -144,7 +149,6 @@ func ActionsManager() {
} }
func MatchesManager() { func MatchesManager() {
matches := make(MatchesMap)
var pf PF var pf PF
var pft PFT var pft PFT
end := false end := false
@ -157,7 +161,7 @@ func MatchesManager() {
if !ok { if !ok {
end = true end = true
} else { } else {
_ = matchesManagerHandleMatch(matches, pft) _ = matchesManagerHandleMatch(pft)
} }
} }
} }
@ -165,19 +169,23 @@ func MatchesManager() {
for { for {
select { select {
case pf = <-cleanMatchesC: case pf = <-cleanMatchesC:
matchesLock.Lock()
delete(matches[pf.f], pf.p) delete(matches[pf.f], pf.p)
matchesLock.Unlock()
case pft = <-matchesC: case pft = <-matchesC:
entry := LogEntry{pft.t, pft.p, pft.f.stream.name, pft.f.name, false} entry := LogEntry{pft.t, pft.p, pft.f.stream.name, pft.f.name, false}
entry.Exec = matchesManagerHandleMatch(matches, pft) matchesLock.Lock()
entry.Exec = matchesManagerHandleMatch(pft)
matchesLock.Unlock()
logsC <- entry logsC <- entry
} }
} }
} }
func matchesManagerHandleMatch(matches MatchesMap, pft PFT) bool { func matchesManagerHandleMatch(pft PFT) bool {
filter, match, then := pft.f, pft.p, pft.t filter, match, then := pft.f, pft.p, pft.t
if filter.Retry > 1 { if filter.Retry > 1 {
@ -229,9 +237,13 @@ func StreamManager(s *Stream, endedSignal chan *Stream) {
} }
var actions ActionsMap
var matches MatchesMap
var actionsLock sync.Mutex
var matchesLock sync.Mutex
var stopStreams chan bool var stopStreams chan bool
var stopActions chan bool var stopActions chan bool
var actionStore ActionStore
var wgActions sync.WaitGroup var wgActions sync.WaitGroup
var wgStreams sync.WaitGroup var wgStreams sync.WaitGroup
@ -254,8 +266,6 @@ var cleanMatchesC chan PF
var actionsC chan PAT var actionsC chan PAT
func Daemon(confFilename string) { func Daemon(confFilename string) {
actionStore.store = make(ActionMap)
conf := parseConf(confFilename) conf := parseConf(confFilename)
logsC = make(chan LogEntry) logsC = make(chan LogEntry)
@ -266,6 +276,8 @@ func Daemon(confFilename string) {
actionsC = make(chan PAT) actionsC = make(chan PAT)
stopActions = make(chan bool) stopActions = make(chan bool)
stopStreams = make(chan bool) stopStreams = make(chan bool)
actions = make(ActionsMap)
matches = make(MatchesMap)
go DatabaseManager(conf) go DatabaseManager(conf)
go MatchesManager() go MatchesManager()
@ -311,7 +323,6 @@ func quit() {
// send stop to ActionsManager // send stop to ActionsManager
close(stopActions) close(stopActions)
// stop all actions // stop all actions
actionStore.Quit()
log.Println("INFO Waiting for Actions to finish...") log.Println("INFO Waiting for Actions to finish...")
wgActions.Wait() wgActions.Wait()
// delete pipe // delete pipe

View File

@ -146,7 +146,7 @@ func Main() {
os.Exit(1) os.Exit(1)
} }
// f.Arg(0) is "" if there is no remaining argument // f.Arg(0) is "" if there is no remaining argument
ClientQuery(*limit) ClientShow(*limit)
case "flush": case "flush":
SocketPath = addSocketFlag(f) SocketPath = addSocketFlag(f)

View File

@ -6,114 +6,57 @@ import (
"net" "net"
"os" "os"
"path" "path"
"sync"
"time" "time"
"gopkg.in/yaml.v3"
) )
type ActionMap map[string]map[*Action]map[chan bool]bool func genClientStatus() ClientStatus {
type ReadableMap map[string]map[string]map[string]int cs := make(ClientStatus)
matchesLock.Lock()
type ActionStore struct { // Painful data manipulation
store ActionMap for filter, filterMatches := range matches {
mutex sync.Mutex if cs[filter.stream.name] == nil {
cs[filter.stream.name] = make(map[string]MapPatternStatus)
} }
if cs[filter.stream.name][filter.name] == nil {
cs[filter.stream.name][filter.name] = make(MapPatternStatus)
}
for pattern, patternMatches := range filterMatches {
var ps PatternStatus
cs[filter.stream.name][filter.name][pattern] = &ps
// Called by an Action before entering sleep ps.Matches = len(patternMatches)
func (a *ActionStore) Register(action *Action, pattern string) chan bool {
a.mutex.Lock()
defer a.mutex.Unlock()
if a.store[pattern] == nil {
a.store[pattern] = make(map[*Action]map[chan bool]bool)
}
if a.store[pattern][action] == nil {
a.store[pattern][action] = make(map[chan bool]bool)
}
sig := make(chan bool)
a.store[pattern][action][sig] = true
return sig
}
// Called by an Action after sleep
func (a *ActionStore) Unregister(action *Action, pattern string, sig chan bool) {
a.mutex.Lock()
defer a.mutex.Unlock()
if a.store[pattern] == nil || a.store[pattern][action] == nil || len(a.store[pattern][action]) == 0 {
return
}
close(sig)
delete(a.store[pattern][action], sig)
}
// Called by Main
func (a *ActionStore) Quit() {
a.mutex.Lock()
defer a.mutex.Unlock()
for _, actions := range a.store {
for action, sigs := range actions {
for sig := range sigs {
sig <- action.OnExit
}
}
}
a.store = make(ActionMap)
}
// Called by a CLI
func (a *ActionStore) Flush(pattern string) int {
var cpt int
a.mutex.Lock()
defer a.mutex.Unlock()
if a.store[pattern] != nil {
for _, action := range a.store[pattern] {
for sig := range action {
sig <- true
}
cpt++
}
}
delete(a.store, pattern)
flushesC <- LogEntry{time.Now(), pattern, "", "", false}
return cpt
}
// Called by a CLI
func (a *ActionStore) pendingActions() ReadableMap {
a.mutex.Lock()
defer a.mutex.Unlock()
return a.store.ToReadable()
}
func (a ActionMap) ToReadable() ReadableMap {
res := make(ReadableMap)
for pattern, actions := range a {
for action := range actions {
filter := action.filter.name
stream := action.filter.stream.name
if res[stream] == nil {
res[stream] = make(map[string]map[string]int)
}
if res[stream][filter] == nil {
res[stream][filter] = make(map[string]int)
}
res[stream][filter][pattern] = res[stream][filter][pattern] + 1
} }
} }
return res matchesLock.Unlock()
} actionsLock.Lock()
func (r ReadableMap) ToString() string { // Painful data manipulation
text, err := yaml.Marshal(r) for action, pendingActions := range actions {
if err != nil { if cs[action.filter.stream.name] == nil {
log.Fatalln(err) cs[action.filter.stream.name] = make(map[string]MapPatternStatus)
} }
return string(text) if cs[action.filter.stream.name][action.filter.name] == nil {
cs[action.filter.stream.name][action.filter.name] = make(MapPatternStatus)
} }
for pattern, patternPendingActions := range pendingActions {
if cs[action.filter.stream.name][action.filter.name][pattern] == nil {
var ps PatternStatus
cs[action.filter.stream.name][action.filter.name][pattern] = &ps
}
var ps *PatternStatus
ps = cs[action.filter.stream.name][action.filter.name][pattern]
ps.Actions = make(map[string][]string)
// Socket-related, server-related functions for _, t := range patternPendingActions {
ps.Actions[action.name] = append(ps.Actions[action.name], t.Format(time.DateTime))
}
}
}
actionsLock.Unlock()
return cs
}
func createOpenSocket() net.Listener { func createOpenSocket() net.Listener {
err := os.MkdirAll(path.Dir(*SocketPath), 0755) err := os.MkdirAll(path.Dir(*SocketPath), 0755)
@ -146,6 +89,7 @@ func SocketManager() {
continue continue
} }
go func(conn net.Conn) { go func(conn net.Conn) {
defer conn.Close()
var request Request var request Request
var response Response var response Response
@ -156,16 +100,17 @@ func SocketManager() {
} }
switch request.Request { switch request.Request {
case Query: case Show:
response.Actions = actionStore.store.ToReadable() response.ClientStatus = genClientStatus()
case Flush: case Flush:
response.Number = actionStore.Flush(request.Pattern) // FIXME reimplement flush
response.Number = 0
default: default:
log.Println("ERROR Invalid Message from cli: unrecognised Request type") log.Println("ERROR Invalid Message from cli: unrecognised Request type")
return return
} }
gob.NewEncoder(conn).Encode(response) err = gob.NewEncoder(conn).Encode(response)
if err != nil { if err != nil {
log.Println("ERROR Can't respond to cli:", err) log.Println("ERROR Can't respond to cli:", err)
return return

View File

@ -2,20 +2,24 @@
patterns: patterns:
num: num:
regex: '[0-9]+' regex: '[0-9]+'
ip:
regex: '(?:(?:[0-9]{1,3}\.){3}[0-9]{1,3})|(?:[0-9a-fA-F:]{2,90})'
ignore:
- 1.0.0.1
streams: streams:
tailDown: tailDown1:
cmd: [ "sh", "-c", "sleep 0.5; echo found 1; sleep 1; echo found 1; sleep 10" ] cmd: [ "sh", "-c", "echo 1 2 3 4 5 1 2 3 4 5 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 | tr ' ' '\n' | while read i; do sleep 2; echo found $(($i % 10)); done" ]
filters: filters:
findIP: findIP:
regex: regex:
- '^found <num>$' - '^found <num>$'
retry: 2 retry: 3
retry-period: 1m retry-period: 30s
actions: actions:
damn: damn:
cmd: [ "echo", "<num>" ] cmd: [ "echo", "<num>" ]
undamn: undamn:
cmd: [ "echo", "undamn", "<num>" ] cmd: [ "echo", "undamn", "<num>" ]
after: 5s after: 30s
onexit: true onexit: true