zabbix_manager 5.1.6 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -1
  3. data/README.md +69 -46
  4. data/lib/zabbix_manager/classes/actions.rb +3 -11
  5. data/lib/zabbix_manager/classes/applications.rb +2 -8
  6. data/lib/zabbix_manager/classes/configurations.rb +4 -9
  7. data/lib/zabbix_manager/classes/{drules.rb → discovery_rules.rb} +2 -8
  8. data/lib/zabbix_manager/classes/errors.rb +3 -0
  9. data/lib/zabbix_manager/classes/events.rb +2 -1
  10. data/lib/zabbix_manager/classes/graphs.rb +19 -15
  11. data/lib/zabbix_manager/classes/{hostgroups.rb → host_groups.rb} +5 -11
  12. data/lib/zabbix_manager/classes/host_interfaces.rb +261 -0
  13. data/lib/zabbix_manager/classes/hosts.rb +135 -89
  14. data/lib/zabbix_manager/classes/{httptests.rb → http_tests.rb} +2 -8
  15. data/lib/zabbix_manager/classes/items.rb +91 -87
  16. data/lib/zabbix_manager/classes/maintenance.rb +2 -1
  17. data/lib/zabbix_manager/classes/{mediatypes.rb → media_types.rb} +2 -7
  18. data/lib/zabbix_manager/classes/problems.rb +11 -40
  19. data/lib/zabbix_manager/classes/proxies.rb +5 -15
  20. data/lib/zabbix_manager/classes/proxy_groups.rb +16 -0
  21. data/lib/zabbix_manager/classes/roles.rb +13 -19
  22. data/lib/zabbix_manager/classes/screens.rb +11 -30
  23. data/lib/zabbix_manager/classes/scripts.rb +2 -8
  24. data/lib/zabbix_manager/classes/server.rb +1 -0
  25. data/lib/zabbix_manager/classes/templates.rb +2 -65
  26. data/lib/zabbix_manager/classes/triggers.rb +42 -45
  27. data/lib/zabbix_manager/classes/user_groups.rb +63 -0
  28. data/lib/zabbix_manager/classes/user_macros.rb +110 -0
  29. data/lib/zabbix_manager/classes/users.rb +15 -54
  30. data/lib/zabbix_manager/classes/value_maps.rb +52 -0
  31. data/lib/zabbix_manager/client.rb +148 -277
  32. data/lib/zabbix_manager/configuration.rb +107 -0
  33. data/lib/zabbix_manager/http_transport.rb +145 -97
  34. data/lib/zabbix_manager/log_sanitizer.rb +9 -19
  35. data/lib/zabbix_manager/monitoring/device.rb +393 -0
  36. data/lib/zabbix_manager/monitoring/expressions.rb +36 -0
  37. data/lib/zabbix_manager/monitoring/line.rb +175 -0
  38. data/lib/zabbix_manager/monitoring/line_items.rb +110 -0
  39. data/lib/zabbix_manager/monitoring/line_lifecycle.rb +169 -0
  40. data/lib/zabbix_manager/monitoring/line_plan.rb +113 -0
  41. data/lib/zabbix_manager/monitoring/thresholds.rb +182 -0
  42. data/lib/zabbix_manager/monitoring/traffic_items.rb +132 -0
  43. data/lib/zabbix_manager/monitoring/validation.rb +92 -0
  44. data/lib/zabbix_manager/monitoring.rb +216 -642
  45. data/lib/zabbix_manager/resource.rb +235 -0
  46. data/lib/zabbix_manager/traffic.rb +238 -0
  47. data/lib/zabbix_manager/version.rb +1 -1
  48. data/lib/zabbix_manager.rb +66 -179
  49. data/zabbix_manager.gemspec +6 -22
  50. metadata +41 -190
  51. data/lib/zabbix_manager/basic/basic_alias.rb +0 -42
  52. data/lib/zabbix_manager/basic/basic_func.rb +0 -55
  53. data/lib/zabbix_manager/basic/basic_init.rb +0 -50
  54. data/lib/zabbix_manager/basic/basic_logic.rb +0 -239
  55. data/lib/zabbix_manager/classes/hostinterfaces.rb +0 -189
  56. data/lib/zabbix_manager/classes/usergroups.rb +0 -75
  57. data/lib/zabbix_manager/classes/usermacros.rb +0 -222
  58. data/lib/zabbix_manager/classes/valuemaps.rb +0 -26
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ZabbixManager
4
+ class Monitoring
5
+ # 精确匹配已发现接口,并证明流量项的方向与 bit/s 语义。
6
+ class TrafficItems
7
+ OCTET_OID = /\A\.?1\.3\.6\.1\.2\.1\.31\.1\.1\.1\.(6|10)(?:\.\d+)*\z/
8
+ LEGACY_OCTET_OID = /\A\.?1\.3\.6\.1\.2\.1\.2\.2\.1\.(10|16)(?:\.\d+)*\z/
9
+ SYMBOLIC_OCTET_OID = /\A(?:IF-MIB::)?if(?:HC)?(In|Out)Octets(?:\.\d+)*\z/
10
+ INTERFACE_TOKEN = /(?<![A-Za-z0-9\/._-])(?:[A-Za-z][A-Za-z_-]*)?\d+(?:[\/.:]\d+)*(?![A-Za-z0-9\/._-]|:\d)/
11
+
12
+ def self.interface_identity(value)
13
+ # 只归一已知厂商前缀;未知接口名的大小写和分隔符属于身份。
14
+ aliases = { "te" => /\A(?:ten[-_ ]?gigabit[-_ ]?ethernet|te)(\d[\d\/.:]*)\z/i,
15
+ "gi" => /\A(?:gigabit[-_ ]?ethernet|gi)(\d[\d\/.:]*)\z/i,
16
+ "eth" => /\A(?:ethernet|eth)(\d[\d\/.:]*)\z/i }
17
+ aliases.each do |prefix, pattern|
18
+ match = pattern.match(value)
19
+ return "#{prefix}#{match[1]}" if match
20
+ end
21
+ value
22
+ end
23
+
24
+ def self.counter_direction(value)
25
+ oid = value.to_s
26
+ oid = oid.delete_prefix("get[").delete_suffix("]") if oid.start_with?("get[") && oid.end_with?("]")
27
+ match = OCTET_OID.match(oid)
28
+ return match[1] == "6" ? :inbound : :outbound if match
29
+
30
+ symbolic = SYMBOLIC_OCTET_OID.match(oid)
31
+ return symbolic[1] == "In" ? :inbound : :outbound if symbolic
32
+
33
+ legacy = LEGACY_OCTET_OID.match(oid)
34
+ return unless legacy
35
+
36
+ legacy[1] == "10" ? :inbound : :outbound
37
+ end
38
+
39
+ # 匹配完整接口名称标记,包括子接口及常见厂商缩写。
40
+ # @api private
41
+ def self.interface_matches?(item, interface_name)
42
+ identity = interface_identity(interface_name)
43
+ [item["name"], item["key_"], item["snmp_oid"]].compact.any? do |field|
44
+ field.to_s.scan(INTERFACE_TOKEN).any? { |token| interface_identity(token) == identity }
45
+ end
46
+ end
47
+
48
+ def initialize(items)
49
+ @items = items
50
+ end
51
+
52
+ # 拒绝零匹配、多匹配和同一 itemid 同时充当两个方向;返回项仍须由调用方核对主机。
53
+ # @return [Hash] inbound、outbound 对应的符号键监控项属性
54
+ def for_interface(interface_name)
55
+ identity = self.class.interface_identity(interface_name)
56
+ candidates = @items.select do |item|
57
+ traffic_item?(item) && interface_item?(item, identity)
58
+ end
59
+ selected = %i[inbound outbound].to_h do |direction|
60
+ matches = candidates.select { |item| direction_item?(item, direction) }
61
+ unless matches.one?
62
+ raise Conflict, "expected one #{direction} traffic item for #{interface_name}, found #{matches.length}"
63
+ end
64
+
65
+ item = matches.first.deep_symbolize_keys
66
+ Validation.positive_id!(item[:itemid], "traffic itemid")
67
+ Validation.item_key!(item[:key_])
68
+ self.class.validate_bps!(item)
69
+ [direction, item]
70
+ end
71
+ if selected[:inbound][:itemid].to_s == selected[:outbound][:itemid].to_s
72
+ raise Conflict, "inbound and outbound traffic items must have distinct itemids"
73
+ end
74
+
75
+ selected
76
+ end
77
+
78
+ # bps 标签不足以证明原始 octet 计数的量纲;同时检查速率转换和乘数 8。
79
+ # @raise [Invalid] 单位或预处理不足以证明 bit/s 语义
80
+ def self.validate_bps!(item)
81
+ unless item[:units] == "bps"
82
+ raise Invalid, "traffic item #{item[:itemid] || item[:key_]} must use bps units"
83
+ end
84
+ return unless raw_counter?(item)
85
+
86
+ steps = Validation.array!(item.fetch(:preprocessing, []), "traffic preprocessing")
87
+ .map { |step| Validation.hash!(step, "traffic preprocessing step") }
88
+ if steps.first&.dig(:type).to_s == "28"
89
+ oid, format = steps.first[:params].to_s.split("\n")
90
+ steps = steps.drop(1) if counter_direction(oid) && format == "0"
91
+ end
92
+ change_per_second = steps.count { |step| step[:type].to_s == "10" } == 1
93
+ multipliers = steps.select { |step| step[:type].to_s == "1" }
94
+ bits_multiplier = multipliers.one? && Validation.number!(multipliers.first[:params], "multiplier") == 8
95
+ return if change_per_second && bits_multiplier && steps.length == 2
96
+
97
+ raise Invalid,
98
+ "raw octet item #{item[:itemid] || item[:key_]} requires change-per-second and multiplier 8 preprocessing"
99
+ end
100
+
101
+ def self.raw_counter?(item)
102
+ counter_direction(item[:snmp_oid]) || item[:key_].to_s.match?(/if(?:hc)?(in|out)octets/i) ||
103
+ (%w[0 7].include?(item[:type].to_s) && item[:key_].match?(/\Anet\.if\.(in|out)\[/))
104
+ end
105
+ private_class_method :raw_counter?
106
+
107
+ private
108
+
109
+ def interface_item?(item, identity)
110
+ self.class.interface_matches?(item, identity)
111
+ end
112
+
113
+ def traffic_item?(item)
114
+ blob = [item["key_"], item["name"]].join(" ").downcase
115
+ return false if blob.match?(/error|discard|drop|packet|status|utilization|percentage|percent/)
116
+
117
+ self.class.counter_direction(item["snmp_oid"]) ||
118
+ blob.match?(/net\.if\.(in|out)(?:\[|\b)|ifhc(in|out)octets|inbound|outbound/)
119
+ end
120
+
121
+ def direction_item?(item, direction)
122
+ counter = self.class.counter_direction(item["snmp_oid"])
123
+ return counter == direction if counter
124
+
125
+ blob = [item["key_"], item["name"]].join(" ").downcase
126
+ inbound = blob.match?(/net\.if\.in\b|ifhcin|inbound/)
127
+ outbound = blob.match?(/net\.if\.out\b|ifhcout|outbound/)
128
+ direction == :inbound ? inbound && !outbound : outbound && !inbound
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bigdecimal"
4
+
5
+ class ZabbixManager
6
+ class Monitoring
7
+ # 监控输入的形状、数值和表达式边界;不做静默类型强制转换。
8
+ # 校验容器不代表授权;数值允许可解析的十进制字符串,ID 不接受浮点或布尔值。
9
+ # @api private
10
+ module Validation
11
+ ITEM_KEY = /\A[A-Za-z0-9_.-]+(?:\[(?:[A-Za-z0-9_.:,\/\- {}\#$]|"(?:[^"\\\r\n]|\\.)*")*\])?\z/
12
+ HOST_NAME = /\A[A-Za-z0-9 ._-]+\z/
13
+
14
+ module_function
15
+
16
+ def hash!(value, name)
17
+ raise Invalid, "#{name} must be a hash" unless value.is_a?(Hash)
18
+
19
+ value.deep_symbolize_keys
20
+ end
21
+
22
+ def array!(value, name)
23
+ raise Invalid, "#{name} must be an array" unless value.is_a?(Array)
24
+
25
+ value
26
+ end
27
+
28
+ # 字符串 "false" 不能作为真值启用提前终止等控制分支。
29
+ def boolean!(value, name)
30
+ raise Invalid, "#{name} must be true or false" unless value == true || value == false
31
+
32
+ value
33
+ end
34
+
35
+ def text!(value, name)
36
+ raise Invalid, "#{name} must be a non-empty string" unless value.is_a?(String) && value.present?
37
+
38
+ value
39
+ end
40
+
41
+ def positive_id!(value, name)
42
+ unless value.to_s.match?(/\A[1-9]\d*\z/) && (value.is_a?(Integer) || value.is_a?(String))
43
+ raise Invalid, "#{name} must be a positive integer"
44
+ end
45
+
46
+ value
47
+ end
48
+
49
+ def host!(host)
50
+ positive_id!(host[:hostid], "host.hostid")
51
+ text!(host[:host], "host.host")
52
+ raise Invalid, "host.host contains unsupported characters" unless HOST_NAME.match?(host[:host])
53
+ end
54
+
55
+ def item_key!(key)
56
+ text!(key, "item key_")
57
+ raise Invalid, "item key_ contains unsupported expression characters" unless ITEM_KEY.match?(key)
58
+
59
+ key
60
+ end
61
+
62
+ def number!(value, name)
63
+ number = BigDecimal(value.to_s)
64
+ raise Invalid, "#{name} must be finite" unless number.finite?
65
+
66
+ number
67
+ rescue ArgumentError, TypeError
68
+ raise Invalid, "#{name} must be a finite number"
69
+ end
70
+
71
+ def window!(window)
72
+ unless window.is_a?(String) && window.match?(/\A[1-9]\d*[smhdw]\z/)
73
+ raise Invalid, "window must use a positive Zabbix duration such as 5m or 1h"
74
+ end
75
+
76
+ window
77
+ end
78
+
79
+ def priority!(priority)
80
+ unless (priority.is_a?(Integer) || priority.is_a?(String)) && priority.to_s.match?(/\A[0-5]\z/)
81
+ raise Invalid, "priority must be an integer between 0 and 5"
82
+ end
83
+
84
+ priority.to_i
85
+ end
86
+
87
+ def format_number(number)
88
+ number.to_i == number ? number.to_i.to_s : number.to_s("F")
89
+ end
90
+ end
91
+ end
92
+ end