@@ -14,6 +14,13 @@ import (
1414 "github.com/gosnmp/gosnmp"
1515)
1616
17+ const (
18+ // DurationNever 代表永不执行 (-1s)
19+ DurationNever = - 1 * time .Second
20+ // DurationImmediately 代表立即执行 (0s)
21+ DurationImmediately = 0
22+ )
23+
1724type DiscoveryEngine struct {
1825 client * SNMPClientManager
1926 template * ZabbixTemplate
@@ -55,6 +62,9 @@ type MonitorItem struct {
5562 DiscoveryIndex string // 从 {#SNMPINDEX} 等宏解析出的唯一索引
5663
5764 Preprocessing []PreprocessStep `json:"preprocessing,omitempty"`
65+
66+ // DependentItems 存储依赖于该 Item 的子 Item
67+ DependentItems []MonitorItem
5868}
5969
6070func extractLabelKey (itemKey string ) string {
@@ -404,6 +414,10 @@ func (d *DiscoveryEngine) extractIndexFromOID(fullOID, baseOID string) string {
404414 if ! strings .HasPrefix (fullOID , baseOID ) {
405415 return ""
406416 }
417+ // 如果比 baseOID 长,必须紧跟切分符(.)
418+ if len (fullOID ) > len (baseOID ) && fullOID [len (baseOID )] != '.' {
419+ return ""
420+ }
407421
408422 // 提取索引部分
409423 if len (fullOID ) <= len (baseOID ) {
@@ -529,6 +543,12 @@ func (d *DiscoveryEngine) ApplyItemPrototypes(discoveries []DiscoveryItem, rule
529543 var items []MonitorItem
530544 prototypes := rule .ItemPrototypes
531545
546+ // Flat list to scope item creation per discovery iteration
547+ type GeneratedItem struct {
548+ MonitorItem
549+ MasterKey string
550+ }
551+
532552 for _ , discovery := range discoveries {
533553 discoveryIndex := ""
534554 if idx , ok := discovery .Macros ["{#SNMPINDEX}" ]; ok {
@@ -538,9 +558,10 @@ func (d *DiscoveryEngine) ApplyItemPrototypes(discoveries []DiscoveryItem, rule
538558 discoveryIndex = idx
539559 }
540560
561+ var currentScopeItems []GeneratedItem
562+
541563 for _ , prototype := range prototypes {
542- if prototype .Status == "DISABLED" ||
543- prototype .Status == "UNSUPPORTED" {
564+ if prototype .Status == "DISABLED" || prototype .Status == "UNSUPPORTED" {
544565 continue
545566 }
546567 delay := parseZabbixDelay (prototype .Delay )
@@ -549,8 +570,15 @@ func (d *DiscoveryEngine) ApplyItemPrototypes(discoveries []DiscoveryItem, rule
549570 tags [tag .Tag ] = tag .Value
550571 }
551572
573+ // Expand macros in keys to properly link masters and dependents
574+ expandedKey := d .expandMacros (prototype .Key , discovery .Macros )
575+ expandedMasterKey := ""
576+ if prototype .MasterItem .Key != "" {
577+ expandedMasterKey = d .expandMacros (prototype .MasterItem .Key , discovery .Macros )
578+ }
579+
552580 item := MonitorItem {
553- Key : d . expandMacros ( prototype . Key , discovery . Macros ) ,
581+ Key : expandedKey ,
554582 OID : d .expandMacros (prototype .SNMPOID , discovery .Macros ),
555583 Type : ConvertZabbixItemType (prototype .Type ),
556584 Name : d .expandMacros (prototype .Name , discovery .Macros ),
@@ -571,10 +599,55 @@ func (d *DiscoveryEngine) ApplyItemPrototypes(discoveries []DiscoveryItem, rule
571599 default :
572600 item .IsLabelProvider = false
573601 }
574- items = append (items , item )
602+
603+ currentScopeItems = append (currentScopeItems , GeneratedItem {
604+ MonitorItem : item ,
605+ MasterKey : expandedMasterKey ,
606+ })
607+ }
608+
609+ // Map for O(1) lookups during dependency linkage
610+ localMap := make (map [string ]* MonitorItem )
611+ var localList []* MonitorItem
612+ var dependentList []* GeneratedItem
613+
614+ for i := range currentScopeItems {
615+ ptr := & currentScopeItems [i ].MonitorItem
616+ localMap [ptr .Key ] = ptr
617+
618+ if currentScopeItems [i ].MasterKey != "" {
619+ dependentList = append (dependentList , & currentScopeItems [i ])
620+ } else {
621+ localList = append (localList , ptr )
622+ }
575623 }
576- }
577624
625+ depMap := make (map [string ][]string )
626+ for _ , dep := range dependentList {
627+ depMap [dep .MasterKey ] = append (depMap [dep .MasterKey ], dep .Key )
628+ }
629+
630+ var buildTree func (key string ) MonitorItem
631+ buildTree = func (key string ) MonitorItem {
632+ item := * localMap [key ]
633+ children := depMap [key ]
634+ for _ , childKey := range children {
635+ if _ , exists := localMap [childKey ]; exists {
636+ childItem := buildTree (childKey )
637+ item .DependentItems = append (item .DependentItems , childItem )
638+ }
639+ }
640+ return item
641+ }
642+
643+ // Only return root items; dependents are nested within them
644+ for i := range currentScopeItems {
645+ if currentScopeItems [i ].MasterKey == "" {
646+ root := buildTree (currentScopeItems [i ].Key )
647+ items = append (items , root )
648+ }
649+ }
650+ }
578651 return items
579652}
580653
@@ -608,6 +681,61 @@ func parseZabbixDelay(delayStr string) time.Duration {
608681 return 60 * time .Second
609682}
610683
684+ // ParseLLDLifetimes 解析 Zabbix 7.0+ 的生命周期配置
685+ // 返回值: (deleteTTL, disableTTL)
686+ // -1 (DurationNever): 永不
687+ // 0 (DurationImmediately): 立即
688+ // >0: 延迟执行的时长
689+ func ParseLLDLifetimes (rule DiscoveryRule ) (time.Duration , time.Duration ) {
690+ // 1. 解析 Delete 策略 (彻底删除)
691+ deleteTTL := parseStrategy (rule .LifetimeType , rule .Lifetime , 7 * 24 * time .Hour ) // 默认删除时间 7d
692+
693+ // 2. 解析 Disable 策略 (停止采集)
694+ // 默认禁用策略:如果未配置,通常 Zabbix 行为是立即禁用(0)或者永不禁用。
695+ // 这里我们设定:如果未显式配置,且旧版 logic (lifetime) 存在,则 Disable 默认为 0 (立即禁用,保持旧版行为一致性)
696+ disableTTL := parseStrategy (rule .EnabledLifetimeType , rule .EnabledLifetime , DurationImmediately )
697+
698+ return deleteTTL , disableTTL
699+ }
700+
701+ // 辅助函数:通用策略解析
702+ // typeStr:
703+ // - "DELETE_NEVER", "DISABLE_NEVER" -> 返回 DurationNever (-1s),表示“永不执行”
704+ // - "DELETE_IMMEDIATELY", "DISABLE_IMMEDIATELY" -> 返回 DurationImmediately (0s),表示“立即执行”
705+ // - "DELETE_AFTER", "DISABLE_AFTER" -> 解析 durationStr,得到一个 >0 的延迟时长
706+ // - "" (空字符串) -> 若 durationStr 非空,则按 *AFTER* 处理;否则返回 defaultValue
707+ // - 其他未知值 -> 返回 defaultValue
708+ // durationStr: 期望为 Zabbix 风格的延迟字符串,例如 "7d", "1h", "30m" 等;当 typeStr 为 *_AFTER 或为空且 durationStr 非空时生效
709+ // defaultValue: 当无法从 typeStr 和 durationStr 推导策略时使用的默认时长。
710+ // 返回值含义:
711+ // - DurationNever (-1s): 永不执行
712+ // - DurationImmediately (0s): 立即执行
713+ // - >0: 表示延迟执行的时长
714+ func parseStrategy (typeStr , durationStr string , defaultValue time.Duration ) time.Duration {
715+ // 归一化
716+ typeStr = strings .ToUpper (strings .TrimSpace (typeStr ))
717+
718+ switch typeStr {
719+ case "DELETE_NEVER" , "DISABLE_NEVER" :
720+ return DurationNever
721+ case "DELETE_IMMEDIATELY" , "DISABLE_IMMEDIATELY" :
722+ return DurationImmediately
723+ case "DELETE_AFTER" , "DISABLE_AFTER" :
724+ if durationStr == "" {
725+ return defaultValue
726+ }
727+ return parseZabbixDelay (durationStr )
728+ default :
729+ // 兼容旧版配置或空配置
730+ // 如果 explicit type 为空,但 durationStr 有值,视为 AFTER
731+ if typeStr == "" && durationStr != "" {
732+ return parseZabbixDelay (durationStr )
733+ }
734+ // 否则返回默认值
735+ return defaultValue
736+ }
737+ }
738+
611739func (d * DiscoveryEngine ) expandMacros (text string , macros map [string ]string ) string {
612740 result := text
613741 for macro , value := range macros {
0 commit comments