feat: big ahh commit
- agent+proto+backend: transfer service status - agent: fix returning empty message on nonzero exit status - backend: refactor collector+commander and handlers dependent on them: implement agent accounting via grpc stats handler
This commit is contained in:
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/repository"
|
||||
@@ -13,26 +12,19 @@ import (
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
// Collector handles log streaming from connected agents.
|
||||
type Collector struct {
|
||||
proto.UnimplementedCollectorServer
|
||||
logRepo *repository.LogRepository
|
||||
agents map[string]*Agent
|
||||
mu sync.RWMutex
|
||||
tracker *ConnTracker
|
||||
batchSize int
|
||||
flushInterval time.Duration
|
||||
}
|
||||
|
||||
type Agent struct {
|
||||
ID string
|
||||
Label string
|
||||
Services []string
|
||||
ConnectedAt time.Time
|
||||
}
|
||||
|
||||
func New(logRepo *repository.LogRepository) *Collector {
|
||||
func New(logRepo *repository.LogRepository, tracker *ConnTracker) *Collector {
|
||||
return &Collector{
|
||||
logRepo: logRepo,
|
||||
agents: make(map[string]*Agent),
|
||||
tracker: tracker,
|
||||
batchSize: 100,
|
||||
flushInterval: 2 * time.Second,
|
||||
}
|
||||
@@ -56,27 +48,15 @@ func (c *Collector) Stream(stream proto.Collector_StreamServer) error {
|
||||
}
|
||||
service := serviceVals[0]
|
||||
|
||||
servicesVals := md["services"]
|
||||
var services []string
|
||||
if len(servicesVals) > 0 {
|
||||
services = servicesVals
|
||||
}
|
||||
|
||||
// Register agent
|
||||
c.mu.Lock()
|
||||
c.agents[agentName] = &Agent{
|
||||
agent := &Agent{
|
||||
ID: agentName,
|
||||
Label: agentName,
|
||||
Services: services,
|
||||
Services: make([]Service, 0),
|
||||
ConnectedAt: time.Now(),
|
||||
}
|
||||
c.mu.Unlock()
|
||||
|
||||
defer func() {
|
||||
c.mu.Lock()
|
||||
delete(c.agents, agentName)
|
||||
c.mu.Unlock()
|
||||
}()
|
||||
c.tracker.Register(agent)
|
||||
defer c.tracker.Unregister(agent.ID)
|
||||
|
||||
log.Printf("Agent %s connected, streaming logs for service: %s", agentName, service)
|
||||
|
||||
@@ -139,7 +119,6 @@ func (c *Collector) Stream(stream proto.Collector_StreamServer) error {
|
||||
for {
|
||||
select {
|
||||
case <-stream.Context().Done():
|
||||
// Context cancelled, flush remaining
|
||||
_ = flush()
|
||||
return stream.Context().Err()
|
||||
case <-ticker.C:
|
||||
@@ -162,7 +141,6 @@ func (c *Collector) Stream(stream proto.Collector_StreamServer) error {
|
||||
}
|
||||
case err := <-errCh:
|
||||
if err == io.EOF {
|
||||
// Client closed stream
|
||||
return flush()
|
||||
}
|
||||
return fmt.Errorf("failed to receive: %w", err)
|
||||
@@ -170,19 +148,12 @@ func (c *Collector) Stream(stream proto.Collector_StreamServer) error {
|
||||
}
|
||||
}
|
||||
|
||||
// GetAgent delegates to the tracker.
|
||||
func (c *Collector) GetAgent(name string) (*Agent, bool) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
a, ok := c.agents[name]
|
||||
return a, ok
|
||||
return c.tracker.GetAgent(name)
|
||||
}
|
||||
|
||||
// Agents delegates to the tracker.
|
||||
func (c *Collector) Agents() []*Agent {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
result := make([]*Agent, 0, len(c.agents))
|
||||
for _, a := range c.agents {
|
||||
result = append(result, a)
|
||||
}
|
||||
return result
|
||||
return c.tracker.Agents()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package collector
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/proto/proto"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
// ReportServices handles a unary service status update from an agent.
|
||||
// Agents send their current services list, which is stored in the collector.
|
||||
func (c *Collector) ReportServices(ctx context.Context, req *proto.ServicesUpdate) (*proto.ServicesUpdateResp, error) {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no metadata in context")
|
||||
}
|
||||
|
||||
whoamiVals := md["whoami"]
|
||||
if len(whoamiVals) == 0 {
|
||||
return nil, fmt.Errorf("whoami metadata missing")
|
||||
}
|
||||
agentName := whoamiVals[0]
|
||||
|
||||
services := make([]Service, 0, len(req.Services))
|
||||
for _, s := range req.Services {
|
||||
services = append(services, Service{s.Name, s.Status})
|
||||
}
|
||||
|
||||
if ok := c.tracker.UpdateServices(agentName, services); ok {
|
||||
log.Printf("Updated services for agent %s: %v", agentName, services)
|
||||
} else {
|
||||
log.Printf("Warning: received services update for unknown agent %s", agentName)
|
||||
}
|
||||
|
||||
return &proto.ServicesUpdateResp{}, nil
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package collector
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/stats"
|
||||
)
|
||||
|
||||
// ConnTracker tracks connected Collector agents and handles cleanup on disconnect.
|
||||
// It implements grpc.StatsHandler for disconnect detection.
|
||||
type ConnTracker struct {
|
||||
mu sync.RWMutex
|
||||
agents map[string]*Agent
|
||||
}
|
||||
|
||||
func NewConnTracker() *ConnTracker {
|
||||
return &ConnTracker{
|
||||
agents: make(map[string]*Agent),
|
||||
}
|
||||
}
|
||||
|
||||
// Register adds an agent to the tracker. Called by Collector.Stream().
|
||||
func (t *ConnTracker) Register(agent *Agent) {
|
||||
t.mu.Lock()
|
||||
t.agents[agent.ID] = agent
|
||||
t.mu.Unlock()
|
||||
log.Printf("[collector] agent registered: %s", agent.ID)
|
||||
}
|
||||
|
||||
// Unregister removes an agent from the tracker.
|
||||
func (t *ConnTracker) Unregister(id string) {
|
||||
t.mu.Lock()
|
||||
delete(t.agents, id)
|
||||
t.mu.Unlock()
|
||||
log.Printf("[collector] agent unregistered: %s", id)
|
||||
}
|
||||
|
||||
// GetAgent returns the agent for the given ID.
|
||||
func (t *ConnTracker) GetAgent(id string) (*Agent, bool) {
|
||||
t.mu.RLock()
|
||||
defer t.mu.RUnlock()
|
||||
a, ok := t.agents[id]
|
||||
return a, ok
|
||||
}
|
||||
|
||||
// Agents returns all connected agents.
|
||||
func (t *ConnTracker) Agents() []*Agent {
|
||||
t.mu.RLock()
|
||||
defer t.mu.RUnlock()
|
||||
result := make([]*Agent, 0, len(t.agents))
|
||||
for _, a := range t.agents {
|
||||
result = append(result, a)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// grpc.StatsHandler implementation.
|
||||
|
||||
func (t *ConnTracker) TagRPC(ctx context.Context, _ *stats.RPCTagInfo) context.Context {
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (t *ConnTracker) HandleRPC(ctx context.Context, _ stats.RPCStats) {}
|
||||
|
||||
func (t *ConnTracker) TagConn(ctx context.Context, _ *stats.ConnTagInfo) context.Context {
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (t *ConnTracker) HandleConn(ctx context.Context, s stats.ConnStats) {
|
||||
switch s.(type) {
|
||||
case *stats.ConnEnd:
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
whoamiVals := md["whoami"]
|
||||
if len(whoamiVals) == 0 {
|
||||
return
|
||||
}
|
||||
t.Unregister(whoamiVals[0])
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateServices updates the services list for the given agent.
|
||||
func (t *ConnTracker) UpdateServices(id string, services []Service) bool {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
agent, ok := t.agents[id]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
agent.Services = services
|
||||
return true
|
||||
}
|
||||
|
||||
// Service represents a named service with its current status.
|
||||
type Service struct {
|
||||
Name, Status string
|
||||
}
|
||||
|
||||
// Agent represents a connected agent streaming logs to the collector.
|
||||
type Agent struct {
|
||||
ID string
|
||||
Label string
|
||||
Services []Service
|
||||
ConnectedAt time.Time
|
||||
}
|
||||
Reference in New Issue
Block a user