diff --git a/agent.go b/agent.go index 6058a0f..251c7ab 100644 --- a/agent.go +++ b/agent.go @@ -95,6 +95,7 @@ func runSession(ctx context.Context, baseURL, token string) error { snmp: newWorkerPool(100), mikrotik: newWorkerPool(20), ping: newWorkerPool(50), + checks: newWorkerPool(50), } // Result channels @@ -102,6 +103,7 @@ func runSession(ctx context.Context, baseURL, token string) error { mikrotikResultCh := make(chan *pb.MikrotikResult, 5000) credTestResultCh := make(chan *pb.CredentialTestResult, 5000) monitoringCheckCh := make(chan *pb.MonitoringCheck, 10000) + checkResultCh := make(chan *pb.CheckResult, 10000) // Ref counter for outbound messages var refCounter atomic.Uint64 @@ -234,6 +236,7 @@ func runSession(ctx context.Context, baseURL, token string) error { pools.snmp.stop() pools.mikrotik.stop() pools.ping.stop() + pools.checks.stop() close(writeCh) writerWg.Wait() }() @@ -272,7 +275,7 @@ func runSession(ctx context.Context, baseURL, token string) error { slog.Warn("invalid message", "error", err) continue } - if handleMessage(ctx, msg, pools, snmpResultCh, mikrotikResultCh, credTestResultCh, monitoringCheckCh) { + if handleMessage(ctx, msg, pools, snmpResultCh, mikrotikResultCh, credTestResultCh, monitoringCheckCh, checkResultCh) { flushSnmpBatch() return errRestartRequested } @@ -295,6 +298,10 @@ func runSession(ctx context.Context, baseURL, token string) error { sendBinaryResult("monitoring_check", result) slog.Info("sent monitoring check", "device", result.DeviceId, "status", result.Status) + case result := <-checkResultCh: + sendBinaryResult("check_result", result) + slog.Info("sent check result", "check", result.CheckId, "status", result.Status) + case <-flushTicker.C: flushSnmpBatch() @@ -335,6 +342,7 @@ func handleMessage( mikrotikResultCh chan<- *pb.MikrotikResult, credTestResultCh chan<- *pb.CredentialTestResult, monitoringCheckCh chan<- *pb.MonitoringCheck, + checkResultCh chan<- *pb.CheckResult, ) bool { switch msg.Event { case "phx_reply": @@ -364,7 +372,30 @@ func handleMessage( } slog.Info("received jobs", "count", len(jobList.Jobs)) for _, job := range jobList.Jobs { - dispatchJob(ctx, job, pools, snmpResultCh, mikrotikResultCh, credTestResultCh, monitoringCheckCh) + dispatchJob(ctx, job, pools, snmpResultCh, mikrotikResultCh, credTestResultCh, monitoringCheckCh, checkResultCh) + } + + case "check_jobs": + var payload struct { + Binary string `json:"binary"` + } + if err := json.Unmarshal(msg.Payload, &payload); err != nil { + slog.Error("decode check_jobs payload", "error", err) + return false + } + bin, err := base64.StdEncoding.DecodeString(payload.Binary) + if err != nil { + slog.Error("decode check_jobs base64", "error", err) + return false + } + var checkList pb.CheckList + if err := proto.Unmarshal(bin, &checkList); err != nil { + slog.Error("unmarshal check list", "error", err) + return false + } + slog.Info("received checks", "count", len(checkList.Checks)) + for _, check := range checkList.Checks { + executeCheck(ctx, check, pools, checkResultCh) } case "restart": @@ -396,6 +427,7 @@ type jobPools struct { snmp *workerPool mikrotik *workerPool ping *workerPool + checks *workerPool } // dispatchJob routes a job to the appropriate worker pool. @@ -407,6 +439,7 @@ func dispatchJob( mikrotikResultCh chan<- *pb.MikrotikResult, credTestResultCh chan<- *pb.CredentialTestResult, monitoringCheckCh chan<- *pb.MonitoringCheck, + checkResultCh chan<- *pb.CheckResult, ) { slog.Info("starting job", "job_id", job.JobId, "type", job.JobType) @@ -448,3 +481,17 @@ func zeroBytes(b []byte) { } func strPtr(s string) *string { return &s } + +// executeCheck dispatches a check to the worker pool. +func executeCheck(ctx context.Context, check *pb.Check, pools *jobPools, checkResultCh chan<- *pb.CheckResult) { + ok := pools.checks.submit(ctx, func() { + result := ExecuteCheck(ctx, check) + select { + case checkResultCh <- result: + case <-ctx.Done(): + } + }) + if !ok { + slog.Warn("check rejected (pool full)", "check_id", check.Id, "type", check.CheckType) + } +} diff --git a/checks.go b/checks.go new file mode 100644 index 0000000..2f7bd98 --- /dev/null +++ b/checks.go @@ -0,0 +1,269 @@ +package main + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "net/http" + "regexp" + "strings" + "time" + + "github.com/towerops-app/towerops-agent/pb" +) + +// ExecuteCheck runs a service check and returns the result. +// Agent is stateless - just executes what it's told and reports back. +func ExecuteCheck(ctx context.Context, check *pb.Check) *pb.CheckResult { + startTime := time.Now() + + var status uint32 + var output string + var responseTimeMs float64 + + switch check.CheckType { + case "http": + if httpConfig := check.GetHttp(); httpConfig != nil { + status, output, responseTimeMs = executeHTTPCheck(ctx, httpConfig, check.TimeoutMs) + } else { + status, output = 3, "Missing HTTP config" + } + + case "tcp": + if tcpConfig := check.GetTcp(); tcpConfig != nil { + status, output, responseTimeMs = executeTCPCheck(ctx, tcpConfig, check.TimeoutMs) + } else { + status, output = 3, "Missing TCP config" + } + + case "dns": + if dnsConfig := check.GetDns(); dnsConfig != nil { + status, output, responseTimeMs = executeDNSCheck(ctx, dnsConfig, check.TimeoutMs) + } else { + status, output = 3, "Missing DNS config" + } + + default: + status, output = 3, fmt.Sprintf("Unknown check type: %s", check.CheckType) + } + + // If responseTimeMs wasn't set by executor, calculate from start time + if responseTimeMs == 0 { + responseTimeMs = float64(time.Since(startTime).Milliseconds()) + } + + return &pb.CheckResult{ + CheckId: check.Id, + Status: status, + Output: output, + ResponseTimeMs: responseTimeMs, + Timestamp: time.Now().Unix(), + } +} + +// executeHTTPCheck performs an HTTP/HTTPS check +func executeHTTPCheck(ctx context.Context, config *pb.HttpCheckConfig, timeoutMs uint32) (uint32, string, float64) { + timeout := time.Duration(timeoutMs) * time.Millisecond + + // Create HTTP client with timeout and TLS config + client := &http.Client{ + Timeout: timeout, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: !config.VerifySsl, + }, + }, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + if !config.FollowRedirects { + return http.ErrUseLastResponse + } + return nil + }, + } + + method := strings.ToUpper(config.Method) + if method == "" { + method = "GET" + } + + req, err := http.NewRequestWithContext(ctx, method, config.Url, strings.NewReader(config.Body)) + if err != nil { + return 2, fmt.Sprintf("Failed to create request: %v", err), 0 + } + + // Add headers + for key, value := range config.Headers { + req.Header.Set(key, value) + } + + startTime := time.Now() + resp, err := client.Do(req) + responseTime := float64(time.Since(startTime).Milliseconds()) + + if err != nil { + return 2, fmt.Sprintf("Request failed: %v", err), responseTime + } + defer resp.Body.Close() + + // Check status code + expectedStatus := int(config.ExpectedStatus) + if expectedStatus == 0 { + expectedStatus = 200 + } + + if resp.StatusCode != expectedStatus { + return 2, fmt.Sprintf("HTTP %d, expected %d", resp.StatusCode, expectedStatus), responseTime + } + + // Check content regex if provided + if config.Regex != "" { + body := make([]byte, 1024*1024) // Read up to 1MB + n, _ := resp.Body.Read(body) + + matched, err := regexp.MatchString(config.Regex, string(body[:n])) + if err != nil { + return 2, fmt.Sprintf("Invalid regex: %v", err), responseTime + } + + if !matched { + return 2, fmt.Sprintf("Content does not match pattern: %s", config.Regex), responseTime + } + } + + return 0, fmt.Sprintf("HTTP %d OK", resp.StatusCode), responseTime +} + +// executeTCPCheck performs a TCP port connectivity check +func executeTCPCheck(ctx context.Context, config *pb.TcpCheckConfig, timeoutMs uint32) (uint32, string, float64) { + timeout := time.Duration(timeoutMs) * time.Millisecond + address := fmt.Sprintf("%s:%d", config.Host, config.Port) + + startTime := time.Now() + conn, err := net.DialTimeout("tcp", address, timeout) + responseTime := float64(time.Since(startTime).Milliseconds()) + + if err != nil { + return 2, fmt.Sprintf("Connection failed: %v", err), responseTime + } + defer conn.Close() + + // If send/expect strings provided, test them + if config.Send != "" { + conn.SetDeadline(time.Now().Add(timeout)) + + _, err = conn.Write([]byte(config.Send)) + if err != nil { + return 2, fmt.Sprintf("Send failed: %v", err), responseTime + } + + if config.Expect != "" { + buffer := make([]byte, 4096) + n, err := conn.Read(buffer) + if err != nil { + return 2, fmt.Sprintf("Receive failed: %v", err), responseTime + } + + received := string(buffer[:n]) + if !strings.Contains(received, config.Expect) { + return 2, fmt.Sprintf("Unexpected response: %s", received), responseTime + } + } + } + + return 0, fmt.Sprintf("TCP port %d open", config.Port), responseTime +} + +// executeDNSCheck performs a DNS resolution check +func executeDNSCheck(ctx context.Context, config *pb.DnsCheckConfig, timeoutMs uint32) (uint32, string, float64) { + timeout := time.Duration(timeoutMs) * time.Millisecond + + resolver := &net.Resolver{} + if config.Server != "" { + resolver = &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + d := net.Dialer{Timeout: timeout} + return d.DialContext(ctx, "udp", config.Server+":53") + }, + } + } + + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + recordType := strings.ToUpper(config.RecordType) + if recordType == "" { + recordType = "A" + } + + startTime := time.Now() + + var results []string + var err error + + switch recordType { + case "A": + ips, lookupErr := resolver.LookupIP(ctx, "ip4", config.Hostname) + err = lookupErr + for _, ip := range ips { + results = append(results, ip.String()) + } + + case "AAAA": + ips, lookupErr := resolver.LookupIP(ctx, "ip6", config.Hostname) + err = lookupErr + for _, ip := range ips { + results = append(results, ip.String()) + } + + case "CNAME": + cname, lookupErr := resolver.LookupCNAME(ctx, config.Hostname) + err = lookupErr + if cname != "" { + results = append(results, cname) + } + + case "MX": + mxs, lookupErr := resolver.LookupMX(ctx, config.Hostname) + err = lookupErr + for _, mx := range mxs { + results = append(results, fmt.Sprintf("%d %s", mx.Pref, mx.Host)) + } + + case "TXT": + txts, lookupErr := resolver.LookupTXT(ctx, config.Hostname) + err = lookupErr + results = txts + + default: + return 3, fmt.Sprintf("Unsupported record type: %s", recordType), 0 + } + + responseTime := float64(time.Since(startTime).Milliseconds()) + + if err != nil { + return 2, fmt.Sprintf("DNS query failed: %v", err), responseTime + } + + if len(results) == 0 { + return 2, fmt.Sprintf("No %s records found", recordType), responseTime + } + + // Check expected result if provided + if config.Expected != "" { + found := false + for _, result := range results { + if result == config.Expected { + found = true + break + } + } + + if !found { + return 2, fmt.Sprintf("Expected '%s', got: %s", config.Expected, strings.Join(results, ", ")), responseTime + } + } + + return 0, fmt.Sprintf("Resolved to: %s", strings.Join(results, ", ")), responseTime +} diff --git a/pb/agent.pb.go b/pb/agent.pb.go index 21fd5b7..267269d 100644 --- a/pb/agent.pb.go +++ b/pb/agent.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v6.32.1 +// protoc v6.33.4 // source: proto/agent.proto package pb @@ -128,6 +128,7 @@ type AgentConfig struct { Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` PollIntervalSeconds uint32 `protobuf:"varint,2,opt,name=poll_interval_seconds,json=pollIntervalSeconds,proto3" json:"poll_interval_seconds,omitempty"` Devices []*Device `protobuf:"bytes,3,rep,name=devices,proto3" json:"devices,omitempty"` + Checks []*Check `protobuf:"bytes,4,rep,name=checks,proto3" json:"checks,omitempty"` // Service checks (HTTP/TCP/DNS) unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -183,6 +184,13 @@ func (x *AgentConfig) GetDevices() []*Device { return nil } +func (x *AgentConfig) GetChecks() []*Check { + if x != nil { + return x.Checks + } + return nil +} + type Device struct { state protoimpl.MessageState `protogen:"open.v1"` Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` @@ -564,6 +572,7 @@ type Metric struct { // *Metric_InterfaceStat // *Metric_NeighborDiscovery // *Metric_MonitoringCheck + // *Metric_CheckResult MetricType isMetric_MetricType `protobuf_oneof:"metric_type"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -642,6 +651,15 @@ func (x *Metric) GetMonitoringCheck() *MonitoringCheck { return nil } +func (x *Metric) GetCheckResult() *CheckResult { + if x != nil { + if x, ok := x.MetricType.(*Metric_CheckResult); ok { + return x.CheckResult + } + } + return nil +} + type isMetric_MetricType interface { isMetric_MetricType() } @@ -662,6 +680,10 @@ type Metric_MonitoringCheck struct { MonitoringCheck *MonitoringCheck `protobuf:"bytes,4,opt,name=monitoring_check,json=monitoringCheck,proto3,oneof"` } +type Metric_CheckResult struct { + CheckResult *CheckResult `protobuf:"bytes,5,opt,name=check_result,json=checkResult,proto3,oneof"` // Service check results (HTTP/TCP/DNS) +} + func (*Metric_SensorReading) isMetric_MetricType() {} func (*Metric_InterfaceStat) isMetric_MetricType() {} @@ -670,6 +692,8 @@ func (*Metric_NeighborDiscovery) isMetric_MetricType() {} func (*Metric_MonitoringCheck) isMetric_MetricType() {} +func (*Metric_CheckResult) isMetric_MetricType() {} + type SensorReading struct { state protoimpl.MessageState `protogen:"open.v1"` SensorId string `protobuf:"bytes,1,opt,name=sensor_id,json=sensorId,proto3" json:"sensor_id,omitempty"` @@ -1030,6 +1054,494 @@ func (x *MonitoringCheck) GetTimestamp() int64 { return 0 } +type Check struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + CheckType string `protobuf:"bytes,2,opt,name=check_type,json=checkType,proto3" json:"check_type,omitempty"` // "http", "tcp", "dns" + IntervalSeconds uint32 `protobuf:"varint,3,opt,name=interval_seconds,json=intervalSeconds,proto3" json:"interval_seconds,omitempty"` + TimeoutMs uint32 `protobuf:"varint,4,opt,name=timeout_ms,json=timeoutMs,proto3" json:"timeout_ms,omitempty"` + // Type-specific config (JSON from Phoenix config field) + // + // Types that are valid to be assigned to Config: + // + // *Check_Http + // *Check_Tcp + // *Check_Dns + Config isCheck_Config `protobuf_oneof:"config"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Check) Reset() { + *x = Check{} + mi := &file_proto_agent_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Check) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Check) ProtoMessage() {} + +func (x *Check) ProtoReflect() protoreflect.Message { + mi := &file_proto_agent_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Check.ProtoReflect.Descriptor instead. +func (*Check) Descriptor() ([]byte, []int) { + return file_proto_agent_proto_rawDescGZIP(), []int{11} +} + +func (x *Check) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Check) GetCheckType() string { + if x != nil { + return x.CheckType + } + return "" +} + +func (x *Check) GetIntervalSeconds() uint32 { + if x != nil { + return x.IntervalSeconds + } + return 0 +} + +func (x *Check) GetTimeoutMs() uint32 { + if x != nil { + return x.TimeoutMs + } + return 0 +} + +func (x *Check) GetConfig() isCheck_Config { + if x != nil { + return x.Config + } + return nil +} + +func (x *Check) GetHttp() *HttpCheckConfig { + if x != nil { + if x, ok := x.Config.(*Check_Http); ok { + return x.Http + } + } + return nil +} + +func (x *Check) GetTcp() *TcpCheckConfig { + if x != nil { + if x, ok := x.Config.(*Check_Tcp); ok { + return x.Tcp + } + } + return nil +} + +func (x *Check) GetDns() *DnsCheckConfig { + if x != nil { + if x, ok := x.Config.(*Check_Dns); ok { + return x.Dns + } + } + return nil +} + +type isCheck_Config interface { + isCheck_Config() +} + +type Check_Http struct { + Http *HttpCheckConfig `protobuf:"bytes,5,opt,name=http,proto3,oneof"` +} + +type Check_Tcp struct { + Tcp *TcpCheckConfig `protobuf:"bytes,6,opt,name=tcp,proto3,oneof"` +} + +type Check_Dns struct { + Dns *DnsCheckConfig `protobuf:"bytes,7,opt,name=dns,proto3,oneof"` +} + +func (*Check_Http) isCheck_Config() {} + +func (*Check_Tcp) isCheck_Config() {} + +func (*Check_Dns) isCheck_Config() {} + +type HttpCheckConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` + Method string `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"` // GET, POST, etc. + ExpectedStatus uint32 `protobuf:"varint,3,opt,name=expected_status,json=expectedStatus,proto3" json:"expected_status,omitempty"` + VerifySsl bool `protobuf:"varint,4,opt,name=verify_ssl,json=verifySsl,proto3" json:"verify_ssl,omitempty"` + Headers map[string]string `protobuf:"bytes,5,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + Body string `protobuf:"bytes,6,opt,name=body,proto3" json:"body,omitempty"` + Regex string `protobuf:"bytes,7,opt,name=regex,proto3" json:"regex,omitempty"` // Content match regex + FollowRedirects bool `protobuf:"varint,8,opt,name=follow_redirects,json=followRedirects,proto3" json:"follow_redirects,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HttpCheckConfig) Reset() { + *x = HttpCheckConfig{} + mi := &file_proto_agent_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HttpCheckConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HttpCheckConfig) ProtoMessage() {} + +func (x *HttpCheckConfig) ProtoReflect() protoreflect.Message { + mi := &file_proto_agent_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HttpCheckConfig.ProtoReflect.Descriptor instead. +func (*HttpCheckConfig) Descriptor() ([]byte, []int) { + return file_proto_agent_proto_rawDescGZIP(), []int{12} +} + +func (x *HttpCheckConfig) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *HttpCheckConfig) GetMethod() string { + if x != nil { + return x.Method + } + return "" +} + +func (x *HttpCheckConfig) GetExpectedStatus() uint32 { + if x != nil { + return x.ExpectedStatus + } + return 0 +} + +func (x *HttpCheckConfig) GetVerifySsl() bool { + if x != nil { + return x.VerifySsl + } + return false +} + +func (x *HttpCheckConfig) GetHeaders() map[string]string { + if x != nil { + return x.Headers + } + return nil +} + +func (x *HttpCheckConfig) GetBody() string { + if x != nil { + return x.Body + } + return "" +} + +func (x *HttpCheckConfig) GetRegex() string { + if x != nil { + return x.Regex + } + return "" +} + +func (x *HttpCheckConfig) GetFollowRedirects() bool { + if x != nil { + return x.FollowRedirects + } + return false +} + +type TcpCheckConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` + Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` + Send string `protobuf:"bytes,3,opt,name=send,proto3" json:"send,omitempty"` // Data to send after connection + Expect string `protobuf:"bytes,4,opt,name=expect,proto3" json:"expect,omitempty"` // Expected response + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TcpCheckConfig) Reset() { + *x = TcpCheckConfig{} + mi := &file_proto_agent_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TcpCheckConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TcpCheckConfig) ProtoMessage() {} + +func (x *TcpCheckConfig) ProtoReflect() protoreflect.Message { + mi := &file_proto_agent_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TcpCheckConfig.ProtoReflect.Descriptor instead. +func (*TcpCheckConfig) Descriptor() ([]byte, []int) { + return file_proto_agent_proto_rawDescGZIP(), []int{13} +} + +func (x *TcpCheckConfig) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *TcpCheckConfig) GetPort() uint32 { + if x != nil { + return x.Port + } + return 0 +} + +func (x *TcpCheckConfig) GetSend() string { + if x != nil { + return x.Send + } + return "" +} + +func (x *TcpCheckConfig) GetExpect() string { + if x != nil { + return x.Expect + } + return "" +} + +type DnsCheckConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + Hostname string `protobuf:"bytes,1,opt,name=hostname,proto3" json:"hostname,omitempty"` + Server string `protobuf:"bytes,2,opt,name=server,proto3" json:"server,omitempty"` // DNS server (optional) + RecordType string `protobuf:"bytes,3,opt,name=record_type,json=recordType,proto3" json:"record_type,omitempty"` // A, AAAA, CNAME, MX, TXT + Expected string `protobuf:"bytes,4,opt,name=expected,proto3" json:"expected,omitempty"` // Expected result (optional) + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DnsCheckConfig) Reset() { + *x = DnsCheckConfig{} + mi := &file_proto_agent_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DnsCheckConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DnsCheckConfig) ProtoMessage() {} + +func (x *DnsCheckConfig) ProtoReflect() protoreflect.Message { + mi := &file_proto_agent_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DnsCheckConfig.ProtoReflect.Descriptor instead. +func (*DnsCheckConfig) Descriptor() ([]byte, []int) { + return file_proto_agent_proto_rawDescGZIP(), []int{14} +} + +func (x *DnsCheckConfig) GetHostname() string { + if x != nil { + return x.Hostname + } + return "" +} + +func (x *DnsCheckConfig) GetServer() string { + if x != nil { + return x.Server + } + return "" +} + +func (x *DnsCheckConfig) GetRecordType() string { + if x != nil { + return x.RecordType + } + return "" +} + +func (x *DnsCheckConfig) GetExpected() string { + if x != nil { + return x.Expected + } + return "" +} + +type CheckResult struct { + state protoimpl.MessageState `protogen:"open.v1"` + CheckId string `protobuf:"bytes,1,opt,name=check_id,json=checkId,proto3" json:"check_id,omitempty"` + Status uint32 `protobuf:"varint,2,opt,name=status,proto3" json:"status,omitempty"` // 0=OK, 1=WARNING, 2=CRITICAL, 3=UNKNOWN + Output string `protobuf:"bytes,3,opt,name=output,proto3" json:"output,omitempty"` + ResponseTimeMs float64 `protobuf:"fixed64,4,opt,name=response_time_ms,json=responseTimeMs,proto3" json:"response_time_ms,omitempty"` + Timestamp int64 `protobuf:"varint,5,opt,name=timestamp,proto3" json:"timestamp,omitempty"` // Unix timestamp in seconds + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CheckResult) Reset() { + *x = CheckResult{} + mi := &file_proto_agent_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CheckResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CheckResult) ProtoMessage() {} + +func (x *CheckResult) ProtoReflect() protoreflect.Message { + mi := &file_proto_agent_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CheckResult.ProtoReflect.Descriptor instead. +func (*CheckResult) Descriptor() ([]byte, []int) { + return file_proto_agent_proto_rawDescGZIP(), []int{15} +} + +func (x *CheckResult) GetCheckId() string { + if x != nil { + return x.CheckId + } + return "" +} + +func (x *CheckResult) GetStatus() uint32 { + if x != nil { + return x.Status + } + return 0 +} + +func (x *CheckResult) GetOutput() string { + if x != nil { + return x.Output + } + return "" +} + +func (x *CheckResult) GetResponseTimeMs() float64 { + if x != nil { + return x.ResponseTimeMs + } + return 0 +} + +func (x *CheckResult) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +type CheckList struct { + state protoimpl.MessageState `protogen:"open.v1"` + Checks []*Check `protobuf:"bytes,1,rep,name=checks,proto3" json:"checks,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CheckList) Reset() { + *x = CheckList{} + mi := &file_proto_agent_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CheckList) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CheckList) ProtoMessage() {} + +func (x *CheckList) ProtoReflect() protoreflect.Message { + mi := &file_proto_agent_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CheckList.ProtoReflect.Descriptor instead. +func (*CheckList) Descriptor() ([]byte, []int) { + return file_proto_agent_proto_rawDescGZIP(), []int{16} +} + +func (x *CheckList) GetChecks() []*Check { + if x != nil { + return x.Checks + } + return nil +} + // Heartbeat metadata type HeartbeatMetadata struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -1042,7 +1554,7 @@ type HeartbeatMetadata struct { func (x *HeartbeatMetadata) Reset() { *x = HeartbeatMetadata{} - mi := &file_proto_agent_proto_msgTypes[11] + mi := &file_proto_agent_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1054,7 +1566,7 @@ func (x *HeartbeatMetadata) String() string { func (*HeartbeatMetadata) ProtoMessage() {} func (x *HeartbeatMetadata) ProtoReflect() protoreflect.Message { - mi := &file_proto_agent_proto_msgTypes[11] + mi := &file_proto_agent_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1067,7 +1579,7 @@ func (x *HeartbeatMetadata) ProtoReflect() protoreflect.Message { // Deprecated: Use HeartbeatMetadata.ProtoReflect.Descriptor instead. func (*HeartbeatMetadata) Descriptor() ([]byte, []int) { - return file_proto_agent_proto_rawDescGZIP(), []int{11} + return file_proto_agent_proto_rawDescGZIP(), []int{17} } func (x *HeartbeatMetadata) GetVersion() string { @@ -1100,7 +1612,7 @@ type HeartbeatResponse struct { func (x *HeartbeatResponse) Reset() { *x = HeartbeatResponse{} - mi := &file_proto_agent_proto_msgTypes[12] + mi := &file_proto_agent_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1112,7 +1624,7 @@ func (x *HeartbeatResponse) String() string { func (*HeartbeatResponse) ProtoMessage() {} func (x *HeartbeatResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_agent_proto_msgTypes[12] + mi := &file_proto_agent_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1125,7 +1637,7 @@ func (x *HeartbeatResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HeartbeatResponse.ProtoReflect.Descriptor instead. func (*HeartbeatResponse) Descriptor() ([]byte, []int) { - return file_proto_agent_proto_rawDescGZIP(), []int{12} + return file_proto_agent_proto_rawDescGZIP(), []int{18} } func (x *HeartbeatResponse) GetStatus() string { @@ -1144,7 +1656,7 @@ type AgentJobList struct { func (x *AgentJobList) Reset() { *x = AgentJobList{} - mi := &file_proto_agent_proto_msgTypes[13] + mi := &file_proto_agent_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1156,7 +1668,7 @@ func (x *AgentJobList) String() string { func (*AgentJobList) ProtoMessage() {} func (x *AgentJobList) ProtoReflect() protoreflect.Message { - mi := &file_proto_agent_proto_msgTypes[13] + mi := &file_proto_agent_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1169,7 +1681,7 @@ func (x *AgentJobList) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentJobList.ProtoReflect.Descriptor instead. func (*AgentJobList) Descriptor() ([]byte, []int) { - return file_proto_agent_proto_rawDescGZIP(), []int{13} + return file_proto_agent_proto_rawDescGZIP(), []int{19} } func (x *AgentJobList) GetJobs() []*AgentJob { @@ -1194,7 +1706,7 @@ type AgentJob struct { func (x *AgentJob) Reset() { *x = AgentJob{} - mi := &file_proto_agent_proto_msgTypes[14] + mi := &file_proto_agent_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1206,7 +1718,7 @@ func (x *AgentJob) String() string { func (*AgentJob) ProtoMessage() {} func (x *AgentJob) ProtoReflect() protoreflect.Message { - mi := &file_proto_agent_proto_msgTypes[14] + mi := &file_proto_agent_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1219,7 +1731,7 @@ func (x *AgentJob) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentJob.ProtoReflect.Descriptor instead. func (*AgentJob) Descriptor() ([]byte, []int) { - return file_proto_agent_proto_rawDescGZIP(), []int{14} + return file_proto_agent_proto_rawDescGZIP(), []int{20} } func (x *AgentJob) GetJobId() string { @@ -1291,7 +1803,7 @@ type SnmpDevice struct { func (x *SnmpDevice) Reset() { *x = SnmpDevice{} - mi := &file_proto_agent_proto_msgTypes[15] + mi := &file_proto_agent_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1303,7 +1815,7 @@ func (x *SnmpDevice) String() string { func (*SnmpDevice) ProtoMessage() {} func (x *SnmpDevice) ProtoReflect() protoreflect.Message { - mi := &file_proto_agent_proto_msgTypes[15] + mi := &file_proto_agent_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1316,7 +1828,7 @@ func (x *SnmpDevice) ProtoReflect() protoreflect.Message { // Deprecated: Use SnmpDevice.ProtoReflect.Descriptor instead. func (*SnmpDevice) Descriptor() ([]byte, []int) { - return file_proto_agent_proto_rawDescGZIP(), []int{15} + return file_proto_agent_proto_rawDescGZIP(), []int{21} } func (x *SnmpDevice) GetIp() string { @@ -1406,7 +1918,7 @@ type SnmpQuery struct { func (x *SnmpQuery) Reset() { *x = SnmpQuery{} - mi := &file_proto_agent_proto_msgTypes[16] + mi := &file_proto_agent_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1418,7 +1930,7 @@ func (x *SnmpQuery) String() string { func (*SnmpQuery) ProtoMessage() {} func (x *SnmpQuery) ProtoReflect() protoreflect.Message { - mi := &file_proto_agent_proto_msgTypes[16] + mi := &file_proto_agent_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1431,7 +1943,7 @@ func (x *SnmpQuery) ProtoReflect() protoreflect.Message { // Deprecated: Use SnmpQuery.ProtoReflect.Descriptor instead. func (*SnmpQuery) Descriptor() ([]byte, []int) { - return file_proto_agent_proto_rawDescGZIP(), []int{16} + return file_proto_agent_proto_rawDescGZIP(), []int{22} } func (x *SnmpQuery) GetQueryType() QueryType { @@ -1461,7 +1973,7 @@ type SnmpResult struct { func (x *SnmpResult) Reset() { *x = SnmpResult{} - mi := &file_proto_agent_proto_msgTypes[17] + mi := &file_proto_agent_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1473,7 +1985,7 @@ func (x *SnmpResult) String() string { func (*SnmpResult) ProtoMessage() {} func (x *SnmpResult) ProtoReflect() protoreflect.Message { - mi := &file_proto_agent_proto_msgTypes[17] + mi := &file_proto_agent_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1486,7 +1998,7 @@ func (x *SnmpResult) ProtoReflect() protoreflect.Message { // Deprecated: Use SnmpResult.ProtoReflect.Descriptor instead. func (*SnmpResult) Descriptor() ([]byte, []int) { - return file_proto_agent_proto_rawDescGZIP(), []int{17} + return file_proto_agent_proto_rawDescGZIP(), []int{23} } func (x *SnmpResult) GetDeviceId() string { @@ -1537,7 +2049,7 @@ type AgentHeartbeat struct { func (x *AgentHeartbeat) Reset() { *x = AgentHeartbeat{} - mi := &file_proto_agent_proto_msgTypes[18] + mi := &file_proto_agent_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1549,7 +2061,7 @@ func (x *AgentHeartbeat) String() string { func (*AgentHeartbeat) ProtoMessage() {} func (x *AgentHeartbeat) ProtoReflect() protoreflect.Message { - mi := &file_proto_agent_proto_msgTypes[18] + mi := &file_proto_agent_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1562,7 +2074,7 @@ func (x *AgentHeartbeat) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentHeartbeat.ProtoReflect.Descriptor instead. func (*AgentHeartbeat) Descriptor() ([]byte, []int) { - return file_proto_agent_proto_rawDescGZIP(), []int{18} + return file_proto_agent_proto_rawDescGZIP(), []int{24} } func (x *AgentHeartbeat) GetVersion() string { @@ -1611,7 +2123,7 @@ type AgentError struct { func (x *AgentError) Reset() { *x = AgentError{} - mi := &file_proto_agent_proto_msgTypes[19] + mi := &file_proto_agent_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1623,7 +2135,7 @@ func (x *AgentError) String() string { func (*AgentError) ProtoMessage() {} func (x *AgentError) ProtoReflect() protoreflect.Message { - mi := &file_proto_agent_proto_msgTypes[19] + mi := &file_proto_agent_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1636,7 +2148,7 @@ func (x *AgentError) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentError.ProtoReflect.Descriptor instead. func (*AgentError) Descriptor() ([]byte, []int) { - return file_proto_agent_proto_rawDescGZIP(), []int{19} + return file_proto_agent_proto_rawDescGZIP(), []int{25} } func (x *AgentError) GetDeviceId() string { @@ -1673,7 +2185,7 @@ type CredentialTestResult struct { func (x *CredentialTestResult) Reset() { *x = CredentialTestResult{} - mi := &file_proto_agent_proto_msgTypes[20] + mi := &file_proto_agent_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1685,7 +2197,7 @@ func (x *CredentialTestResult) String() string { func (*CredentialTestResult) ProtoMessage() {} func (x *CredentialTestResult) ProtoReflect() protoreflect.Message { - mi := &file_proto_agent_proto_msgTypes[20] + mi := &file_proto_agent_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1698,7 +2210,7 @@ func (x *CredentialTestResult) ProtoReflect() protoreflect.Message { // Deprecated: Use CredentialTestResult.ProtoReflect.Descriptor instead. func (*CredentialTestResult) Descriptor() ([]byte, []int) { - return file_proto_agent_proto_rawDescGZIP(), []int{20} + return file_proto_agent_proto_rawDescGZIP(), []int{26} } func (x *CredentialTestResult) GetTestId() string { @@ -1750,7 +2262,7 @@ type MikrotikDevice struct { func (x *MikrotikDevice) Reset() { *x = MikrotikDevice{} - mi := &file_proto_agent_proto_msgTypes[21] + mi := &file_proto_agent_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1762,7 +2274,7 @@ func (x *MikrotikDevice) String() string { func (*MikrotikDevice) ProtoMessage() {} func (x *MikrotikDevice) ProtoReflect() protoreflect.Message { - mi := &file_proto_agent_proto_msgTypes[21] + mi := &file_proto_agent_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1775,7 +2287,7 @@ func (x *MikrotikDevice) ProtoReflect() protoreflect.Message { // Deprecated: Use MikrotikDevice.ProtoReflect.Descriptor instead. func (*MikrotikDevice) Descriptor() ([]byte, []int) { - return file_proto_agent_proto_rawDescGZIP(), []int{21} + return file_proto_agent_proto_rawDescGZIP(), []int{27} } func (x *MikrotikDevice) GetIp() string { @@ -1830,7 +2342,7 @@ type MikrotikCommand struct { func (x *MikrotikCommand) Reset() { *x = MikrotikCommand{} - mi := &file_proto_agent_proto_msgTypes[22] + mi := &file_proto_agent_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1842,7 +2354,7 @@ func (x *MikrotikCommand) String() string { func (*MikrotikCommand) ProtoMessage() {} func (x *MikrotikCommand) ProtoReflect() protoreflect.Message { - mi := &file_proto_agent_proto_msgTypes[22] + mi := &file_proto_agent_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1855,7 +2367,7 @@ func (x *MikrotikCommand) ProtoReflect() protoreflect.Message { // Deprecated: Use MikrotikCommand.ProtoReflect.Descriptor instead. func (*MikrotikCommand) Descriptor() ([]byte, []int) { - return file_proto_agent_proto_rawDescGZIP(), []int{22} + return file_proto_agent_proto_rawDescGZIP(), []int{28} } func (x *MikrotikCommand) GetCommand() string { @@ -1885,7 +2397,7 @@ type MikrotikResult struct { func (x *MikrotikResult) Reset() { *x = MikrotikResult{} - mi := &file_proto_agent_proto_msgTypes[23] + mi := &file_proto_agent_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1897,7 +2409,7 @@ func (x *MikrotikResult) String() string { func (*MikrotikResult) ProtoMessage() {} func (x *MikrotikResult) ProtoReflect() protoreflect.Message { - mi := &file_proto_agent_proto_msgTypes[23] + mi := &file_proto_agent_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1910,7 +2422,7 @@ func (x *MikrotikResult) ProtoReflect() protoreflect.Message { // Deprecated: Use MikrotikResult.ProtoReflect.Descriptor instead. func (*MikrotikResult) Descriptor() ([]byte, []int) { - return file_proto_agent_proto_rawDescGZIP(), []int{23} + return file_proto_agent_proto_rawDescGZIP(), []int{29} } func (x *MikrotikResult) GetDeviceId() string { @@ -1957,7 +2469,7 @@ type MikrotikSentence struct { func (x *MikrotikSentence) Reset() { *x = MikrotikSentence{} - mi := &file_proto_agent_proto_msgTypes[24] + mi := &file_proto_agent_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1969,7 +2481,7 @@ func (x *MikrotikSentence) String() string { func (*MikrotikSentence) ProtoMessage() {} func (x *MikrotikSentence) ProtoReflect() protoreflect.Message { - mi := &file_proto_agent_proto_msgTypes[24] + mi := &file_proto_agent_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1982,7 +2494,7 @@ func (x *MikrotikSentence) ProtoReflect() protoreflect.Message { // Deprecated: Use MikrotikSentence.ProtoReflect.Descriptor instead. func (*MikrotikSentence) Descriptor() ([]byte, []int) { - return file_proto_agent_proto_rawDescGZIP(), []int{24} + return file_proto_agent_proto_rawDescGZIP(), []int{30} } func (x *MikrotikSentence) GetAttributes() map[string]string { @@ -1996,11 +2508,12 @@ var File_proto_agent_proto protoreflect.FileDescriptor const file_proto_agent_proto_rawDesc = "" + "\n" + - "\x11proto/agent.proto\x12\x0etowerops.agent\"\x8d\x01\n" + + "\x11proto/agent.proto\x12\x0etowerops.agent\"\xbc\x01\n" + "\vAgentConfig\x12\x18\n" + "\aversion\x18\x01 \x01(\tR\aversion\x122\n" + "\x15poll_interval_seconds\x18\x02 \x01(\rR\x13pollIntervalSeconds\x120\n" + - "\adevices\x18\x03 \x03(\v2\x16.towerops.agent.DeviceR\adevices\"\x81\x03\n" + + "\adevices\x18\x03 \x03(\v2\x16.towerops.agent.DeviceR\adevices\x12-\n" + + "\x06checks\x18\x04 \x03(\v2\x15.towerops.agent.CheckR\x06checks\"\x81\x03\n" + "\x06Device\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12\x1d\n" + @@ -2036,12 +2549,13 @@ const file_proto_agent_proto_rawDesc = "" + "\bif_index\x18\x02 \x01(\rR\aifIndex\x12\x17\n" + "\aif_name\x18\x03 \x01(\tR\x06ifName\"?\n" + "\vMetricBatch\x120\n" + - "\ametrics\x18\x01 \x03(\v2\x16.towerops.agent.MetricR\ametrics\"\xc9\x02\n" + + "\ametrics\x18\x01 \x03(\v2\x16.towerops.agent.MetricR\ametrics\"\x8b\x03\n" + "\x06Metric\x12F\n" + "\x0esensor_reading\x18\x01 \x01(\v2\x1d.towerops.agent.SensorReadingH\x00R\rsensorReading\x12F\n" + "\x0einterface_stat\x18\x02 \x01(\v2\x1d.towerops.agent.InterfaceStatH\x00R\rinterfaceStat\x12R\n" + "\x12neighbor_discovery\x18\x03 \x01(\v2!.towerops.agent.NeighborDiscoveryH\x00R\x11neighborDiscovery\x12L\n" + - "\x10monitoring_check\x18\x04 \x01(\v2\x1f.towerops.agent.MonitoringCheckH\x00R\x0fmonitoringCheckB\r\n" + + "\x10monitoring_check\x18\x04 \x01(\v2\x1f.towerops.agent.MonitoringCheckH\x00R\x0fmonitoringCheck\x12@\n" + + "\fcheck_result\x18\x05 \x01(\v2\x1b.towerops.agent.CheckResultH\x00R\vcheckResultB\r\n" + "\vmetric_type\"x\n" + "\rSensorReading\x12\x1b\n" + "\tsensor_id\x18\x01 \x01(\tR\bsensorId\x12\x14\n" + @@ -2076,7 +2590,50 @@ const file_proto_agent_proto_rawDesc = "" + "\tdevice_id\x18\x01 \x01(\tR\bdeviceId\x12\x16\n" + "\x06status\x18\x02 \x01(\tR\x06status\x12(\n" + "\x10response_time_ms\x18\x03 \x01(\x01R\x0eresponseTimeMs\x12\x1c\n" + - "\ttimestamp\x18\x04 \x01(\x03R\ttimestamp\"p\n" + + "\ttimestamp\x18\x04 \x01(\x03R\ttimestamp\"\xa9\x02\n" + + "\x05Check\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1d\n" + + "\n" + + "check_type\x18\x02 \x01(\tR\tcheckType\x12)\n" + + "\x10interval_seconds\x18\x03 \x01(\rR\x0fintervalSeconds\x12\x1d\n" + + "\n" + + "timeout_ms\x18\x04 \x01(\rR\ttimeoutMs\x125\n" + + "\x04http\x18\x05 \x01(\v2\x1f.towerops.agent.HttpCheckConfigH\x00R\x04http\x122\n" + + "\x03tcp\x18\x06 \x01(\v2\x1e.towerops.agent.TcpCheckConfigH\x00R\x03tcp\x122\n" + + "\x03dns\x18\a \x01(\v2\x1e.towerops.agent.DnsCheckConfigH\x00R\x03dnsB\b\n" + + "\x06config\"\xdc\x02\n" + + "\x0fHttpCheckConfig\x12\x10\n" + + "\x03url\x18\x01 \x01(\tR\x03url\x12\x16\n" + + "\x06method\x18\x02 \x01(\tR\x06method\x12'\n" + + "\x0fexpected_status\x18\x03 \x01(\rR\x0eexpectedStatus\x12\x1d\n" + + "\n" + + "verify_ssl\x18\x04 \x01(\bR\tverifySsl\x12F\n" + + "\aheaders\x18\x05 \x03(\v2,.towerops.agent.HttpCheckConfig.HeadersEntryR\aheaders\x12\x12\n" + + "\x04body\x18\x06 \x01(\tR\x04body\x12\x14\n" + + "\x05regex\x18\a \x01(\tR\x05regex\x12)\n" + + "\x10follow_redirects\x18\b \x01(\bR\x0ffollowRedirects\x1a:\n" + + "\fHeadersEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"d\n" + + "\x0eTcpCheckConfig\x12\x12\n" + + "\x04host\x18\x01 \x01(\tR\x04host\x12\x12\n" + + "\x04port\x18\x02 \x01(\rR\x04port\x12\x12\n" + + "\x04send\x18\x03 \x01(\tR\x04send\x12\x16\n" + + "\x06expect\x18\x04 \x01(\tR\x06expect\"\x81\x01\n" + + "\x0eDnsCheckConfig\x12\x1a\n" + + "\bhostname\x18\x01 \x01(\tR\bhostname\x12\x16\n" + + "\x06server\x18\x02 \x01(\tR\x06server\x12\x1f\n" + + "\vrecord_type\x18\x03 \x01(\tR\n" + + "recordType\x12\x1a\n" + + "\bexpected\x18\x04 \x01(\tR\bexpected\"\xa0\x01\n" + + "\vCheckResult\x12\x19\n" + + "\bcheck_id\x18\x01 \x01(\tR\acheckId\x12\x16\n" + + "\x06status\x18\x02 \x01(\rR\x06status\x12\x16\n" + + "\x06output\x18\x03 \x01(\tR\x06output\x12(\n" + + "\x10response_time_ms\x18\x04 \x01(\x01R\x0eresponseTimeMs\x12\x1c\n" + + "\ttimestamp\x18\x05 \x01(\x03R\ttimestamp\":\n" + + "\tCheckList\x12-\n" + + "\x06checks\x18\x01 \x03(\v2\x15.towerops.agent.CheckR\x06checks\"p\n" + "\x11HeartbeatMetadata\x12\x18\n" + "\aversion\x18\x01 \x01(\tR\aversion\x12\x1a\n" + "\bhostname\x18\x02 \x01(\tR\bhostname\x12%\n" + @@ -2191,7 +2748,7 @@ func file_proto_agent_proto_rawDescGZIP() []byte { } var file_proto_agent_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_proto_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 29) +var file_proto_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 36) var file_proto_agent_proto_goTypes = []any{ (JobType)(0), // 0: towerops.agent.JobType (QueryType)(0), // 1: towerops.agent.QueryType @@ -2206,53 +2763,67 @@ var file_proto_agent_proto_goTypes = []any{ (*InterfaceStat)(nil), // 10: towerops.agent.InterfaceStat (*NeighborDiscovery)(nil), // 11: towerops.agent.NeighborDiscovery (*MonitoringCheck)(nil), // 12: towerops.agent.MonitoringCheck - (*HeartbeatMetadata)(nil), // 13: towerops.agent.HeartbeatMetadata - (*HeartbeatResponse)(nil), // 14: towerops.agent.HeartbeatResponse - (*AgentJobList)(nil), // 15: towerops.agent.AgentJobList - (*AgentJob)(nil), // 16: towerops.agent.AgentJob - (*SnmpDevice)(nil), // 17: towerops.agent.SnmpDevice - (*SnmpQuery)(nil), // 18: towerops.agent.SnmpQuery - (*SnmpResult)(nil), // 19: towerops.agent.SnmpResult - (*AgentHeartbeat)(nil), // 20: towerops.agent.AgentHeartbeat - (*AgentError)(nil), // 21: towerops.agent.AgentError - (*CredentialTestResult)(nil), // 22: towerops.agent.CredentialTestResult - (*MikrotikDevice)(nil), // 23: towerops.agent.MikrotikDevice - (*MikrotikCommand)(nil), // 24: towerops.agent.MikrotikCommand - (*MikrotikResult)(nil), // 25: towerops.agent.MikrotikResult - (*MikrotikSentence)(nil), // 26: towerops.agent.MikrotikSentence - nil, // 27: towerops.agent.Sensor.MetadataEntry - nil, // 28: towerops.agent.SnmpResult.OidValuesEntry - nil, // 29: towerops.agent.MikrotikCommand.ArgsEntry - nil, // 30: towerops.agent.MikrotikSentence.AttributesEntry + (*Check)(nil), // 13: towerops.agent.Check + (*HttpCheckConfig)(nil), // 14: towerops.agent.HttpCheckConfig + (*TcpCheckConfig)(nil), // 15: towerops.agent.TcpCheckConfig + (*DnsCheckConfig)(nil), // 16: towerops.agent.DnsCheckConfig + (*CheckResult)(nil), // 17: towerops.agent.CheckResult + (*CheckList)(nil), // 18: towerops.agent.CheckList + (*HeartbeatMetadata)(nil), // 19: towerops.agent.HeartbeatMetadata + (*HeartbeatResponse)(nil), // 20: towerops.agent.HeartbeatResponse + (*AgentJobList)(nil), // 21: towerops.agent.AgentJobList + (*AgentJob)(nil), // 22: towerops.agent.AgentJob + (*SnmpDevice)(nil), // 23: towerops.agent.SnmpDevice + (*SnmpQuery)(nil), // 24: towerops.agent.SnmpQuery + (*SnmpResult)(nil), // 25: towerops.agent.SnmpResult + (*AgentHeartbeat)(nil), // 26: towerops.agent.AgentHeartbeat + (*AgentError)(nil), // 27: towerops.agent.AgentError + (*CredentialTestResult)(nil), // 28: towerops.agent.CredentialTestResult + (*MikrotikDevice)(nil), // 29: towerops.agent.MikrotikDevice + (*MikrotikCommand)(nil), // 30: towerops.agent.MikrotikCommand + (*MikrotikResult)(nil), // 31: towerops.agent.MikrotikResult + (*MikrotikSentence)(nil), // 32: towerops.agent.MikrotikSentence + nil, // 33: towerops.agent.Sensor.MetadataEntry + nil, // 34: towerops.agent.HttpCheckConfig.HeadersEntry + nil, // 35: towerops.agent.SnmpResult.OidValuesEntry + nil, // 36: towerops.agent.MikrotikCommand.ArgsEntry + nil, // 37: towerops.agent.MikrotikSentence.AttributesEntry } var file_proto_agent_proto_depIdxs = []int32{ 3, // 0: towerops.agent.AgentConfig.devices:type_name -> towerops.agent.Device - 4, // 1: towerops.agent.Device.snmp:type_name -> towerops.agent.SnmpConfig - 5, // 2: towerops.agent.Device.sensors:type_name -> towerops.agent.Sensor - 6, // 3: towerops.agent.Device.interfaces:type_name -> towerops.agent.Interface - 27, // 4: towerops.agent.Sensor.metadata:type_name -> towerops.agent.Sensor.MetadataEntry - 8, // 5: towerops.agent.MetricBatch.metrics:type_name -> towerops.agent.Metric - 9, // 6: towerops.agent.Metric.sensor_reading:type_name -> towerops.agent.SensorReading - 10, // 7: towerops.agent.Metric.interface_stat:type_name -> towerops.agent.InterfaceStat - 11, // 8: towerops.agent.Metric.neighbor_discovery:type_name -> towerops.agent.NeighborDiscovery - 12, // 9: towerops.agent.Metric.monitoring_check:type_name -> towerops.agent.MonitoringCheck - 16, // 10: towerops.agent.AgentJobList.jobs:type_name -> towerops.agent.AgentJob - 0, // 11: towerops.agent.AgentJob.job_type:type_name -> towerops.agent.JobType - 17, // 12: towerops.agent.AgentJob.snmp_device:type_name -> towerops.agent.SnmpDevice - 18, // 13: towerops.agent.AgentJob.queries:type_name -> towerops.agent.SnmpQuery - 23, // 14: towerops.agent.AgentJob.mikrotik_device:type_name -> towerops.agent.MikrotikDevice - 24, // 15: towerops.agent.AgentJob.mikrotik_commands:type_name -> towerops.agent.MikrotikCommand - 1, // 16: towerops.agent.SnmpQuery.query_type:type_name -> towerops.agent.QueryType - 0, // 17: towerops.agent.SnmpResult.job_type:type_name -> towerops.agent.JobType - 28, // 18: towerops.agent.SnmpResult.oid_values:type_name -> towerops.agent.SnmpResult.OidValuesEntry - 29, // 19: towerops.agent.MikrotikCommand.args:type_name -> towerops.agent.MikrotikCommand.ArgsEntry - 26, // 20: towerops.agent.MikrotikResult.sentences:type_name -> towerops.agent.MikrotikSentence - 30, // 21: towerops.agent.MikrotikSentence.attributes:type_name -> towerops.agent.MikrotikSentence.AttributesEntry - 22, // [22:22] is the sub-list for method output_type - 22, // [22:22] is the sub-list for method input_type - 22, // [22:22] is the sub-list for extension type_name - 22, // [22:22] is the sub-list for extension extendee - 0, // [0:22] is the sub-list for field type_name + 13, // 1: towerops.agent.AgentConfig.checks:type_name -> towerops.agent.Check + 4, // 2: towerops.agent.Device.snmp:type_name -> towerops.agent.SnmpConfig + 5, // 3: towerops.agent.Device.sensors:type_name -> towerops.agent.Sensor + 6, // 4: towerops.agent.Device.interfaces:type_name -> towerops.agent.Interface + 33, // 5: towerops.agent.Sensor.metadata:type_name -> towerops.agent.Sensor.MetadataEntry + 8, // 6: towerops.agent.MetricBatch.metrics:type_name -> towerops.agent.Metric + 9, // 7: towerops.agent.Metric.sensor_reading:type_name -> towerops.agent.SensorReading + 10, // 8: towerops.agent.Metric.interface_stat:type_name -> towerops.agent.InterfaceStat + 11, // 9: towerops.agent.Metric.neighbor_discovery:type_name -> towerops.agent.NeighborDiscovery + 12, // 10: towerops.agent.Metric.monitoring_check:type_name -> towerops.agent.MonitoringCheck + 17, // 11: towerops.agent.Metric.check_result:type_name -> towerops.agent.CheckResult + 14, // 12: towerops.agent.Check.http:type_name -> towerops.agent.HttpCheckConfig + 15, // 13: towerops.agent.Check.tcp:type_name -> towerops.agent.TcpCheckConfig + 16, // 14: towerops.agent.Check.dns:type_name -> towerops.agent.DnsCheckConfig + 34, // 15: towerops.agent.HttpCheckConfig.headers:type_name -> towerops.agent.HttpCheckConfig.HeadersEntry + 13, // 16: towerops.agent.CheckList.checks:type_name -> towerops.agent.Check + 22, // 17: towerops.agent.AgentJobList.jobs:type_name -> towerops.agent.AgentJob + 0, // 18: towerops.agent.AgentJob.job_type:type_name -> towerops.agent.JobType + 23, // 19: towerops.agent.AgentJob.snmp_device:type_name -> towerops.agent.SnmpDevice + 24, // 20: towerops.agent.AgentJob.queries:type_name -> towerops.agent.SnmpQuery + 29, // 21: towerops.agent.AgentJob.mikrotik_device:type_name -> towerops.agent.MikrotikDevice + 30, // 22: towerops.agent.AgentJob.mikrotik_commands:type_name -> towerops.agent.MikrotikCommand + 1, // 23: towerops.agent.SnmpQuery.query_type:type_name -> towerops.agent.QueryType + 0, // 24: towerops.agent.SnmpResult.job_type:type_name -> towerops.agent.JobType + 35, // 25: towerops.agent.SnmpResult.oid_values:type_name -> towerops.agent.SnmpResult.OidValuesEntry + 36, // 26: towerops.agent.MikrotikCommand.args:type_name -> towerops.agent.MikrotikCommand.ArgsEntry + 32, // 27: towerops.agent.MikrotikResult.sentences:type_name -> towerops.agent.MikrotikSentence + 37, // 28: towerops.agent.MikrotikSentence.attributes:type_name -> towerops.agent.MikrotikSentence.AttributesEntry + 29, // [29:29] is the sub-list for method output_type + 29, // [29:29] is the sub-list for method input_type + 29, // [29:29] is the sub-list for extension type_name + 29, // [29:29] is the sub-list for extension extendee + 0, // [0:29] is the sub-list for field type_name } func init() { file_proto_agent_proto_init() } @@ -2265,6 +2836,12 @@ func file_proto_agent_proto_init() { (*Metric_InterfaceStat)(nil), (*Metric_NeighborDiscovery)(nil), (*Metric_MonitoringCheck)(nil), + (*Metric_CheckResult)(nil), + } + file_proto_agent_proto_msgTypes[11].OneofWrappers = []any{ + (*Check_Http)(nil), + (*Check_Tcp)(nil), + (*Check_Dns)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -2272,7 +2849,7 @@ func file_proto_agent_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_agent_proto_rawDesc), len(file_proto_agent_proto_rawDesc)), NumEnums: 2, - NumMessages: 29, + NumMessages: 36, NumExtensions: 0, NumServices: 0, }, diff --git a/proto/agent.proto b/proto/agent.proto index 348ac3a..44099aa 100644 --- a/proto/agent.proto +++ b/proto/agent.proto @@ -9,6 +9,7 @@ message AgentConfig { string version = 1; uint32 poll_interval_seconds = 2; repeated Device devices = 3; + repeated Check checks = 4; // Service checks (HTTP/TCP/DNS) } message Device { @@ -57,6 +58,7 @@ message Metric { InterfaceStat interface_stat = 2; NeighborDiscovery neighbor_discovery = 3; MonitoringCheck monitoring_check = 4; + CheckResult check_result = 5; // Service check results (HTTP/TCP/DNS) } } @@ -99,6 +101,59 @@ message MonitoringCheck { int64 timestamp = 4; // Unix timestamp in seconds } +// Service Checks (HTTP/TCP/DNS) + +message Check { + string id = 1; + string check_type = 2; // "http", "tcp", "dns" + uint32 interval_seconds = 3; + uint32 timeout_ms = 4; + + // Type-specific config (JSON from Phoenix config field) + oneof config { + HttpCheckConfig http = 5; + TcpCheckConfig tcp = 6; + DnsCheckConfig dns = 7; + } +} + +message HttpCheckConfig { + string url = 1; + string method = 2; // GET, POST, etc. + uint32 expected_status = 3; + bool verify_ssl = 4; + map headers = 5; + string body = 6; + string regex = 7; // Content match regex + bool follow_redirects = 8; +} + +message TcpCheckConfig { + string host = 1; + uint32 port = 2; + string send = 3; // Data to send after connection + string expect = 4; // Expected response +} + +message DnsCheckConfig { + string hostname = 1; + string server = 2; // DNS server (optional) + string record_type = 3; // A, AAAA, CNAME, MX, TXT + string expected = 4; // Expected result (optional) +} + +message CheckResult { + string check_id = 1; + uint32 status = 2; // 0=OK, 1=WARNING, 2=CRITICAL, 3=UNKNOWN + string output = 3; + double response_time_ms = 4; + int64 timestamp = 5; // Unix timestamp in seconds +} + +message CheckList { + repeated Check checks = 1; +} + // Heartbeat metadata message HeartbeatMetadata { string version = 1;