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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +31 -1
- data/README.md +69 -46
- data/lib/zabbix_manager/classes/actions.rb +3 -11
- data/lib/zabbix_manager/classes/applications.rb +2 -8
- data/lib/zabbix_manager/classes/configurations.rb +4 -9
- data/lib/zabbix_manager/classes/{drules.rb → discovery_rules.rb} +2 -8
- data/lib/zabbix_manager/classes/errors.rb +3 -0
- data/lib/zabbix_manager/classes/events.rb +2 -1
- data/lib/zabbix_manager/classes/graphs.rb +19 -15
- data/lib/zabbix_manager/classes/{hostgroups.rb → host_groups.rb} +5 -11
- data/lib/zabbix_manager/classes/host_interfaces.rb +261 -0
- data/lib/zabbix_manager/classes/hosts.rb +135 -89
- data/lib/zabbix_manager/classes/{httptests.rb → http_tests.rb} +2 -8
- data/lib/zabbix_manager/classes/items.rb +91 -87
- data/lib/zabbix_manager/classes/maintenance.rb +2 -1
- data/lib/zabbix_manager/classes/{mediatypes.rb → media_types.rb} +2 -7
- data/lib/zabbix_manager/classes/problems.rb +11 -40
- data/lib/zabbix_manager/classes/proxies.rb +5 -15
- data/lib/zabbix_manager/classes/proxy_groups.rb +16 -0
- data/lib/zabbix_manager/classes/roles.rb +13 -19
- data/lib/zabbix_manager/classes/screens.rb +11 -30
- data/lib/zabbix_manager/classes/scripts.rb +2 -8
- data/lib/zabbix_manager/classes/server.rb +1 -0
- data/lib/zabbix_manager/classes/templates.rb +2 -65
- data/lib/zabbix_manager/classes/triggers.rb +42 -45
- data/lib/zabbix_manager/classes/user_groups.rb +63 -0
- data/lib/zabbix_manager/classes/user_macros.rb +110 -0
- data/lib/zabbix_manager/classes/users.rb +15 -54
- data/lib/zabbix_manager/classes/value_maps.rb +52 -0
- data/lib/zabbix_manager/client.rb +148 -277
- data/lib/zabbix_manager/configuration.rb +107 -0
- data/lib/zabbix_manager/http_transport.rb +145 -97
- data/lib/zabbix_manager/log_sanitizer.rb +9 -19
- data/lib/zabbix_manager/monitoring/device.rb +393 -0
- data/lib/zabbix_manager/monitoring/expressions.rb +36 -0
- data/lib/zabbix_manager/monitoring/line.rb +175 -0
- data/lib/zabbix_manager/monitoring/line_items.rb +110 -0
- data/lib/zabbix_manager/monitoring/line_lifecycle.rb +169 -0
- data/lib/zabbix_manager/monitoring/line_plan.rb +113 -0
- data/lib/zabbix_manager/monitoring/thresholds.rb +182 -0
- data/lib/zabbix_manager/monitoring/traffic_items.rb +132 -0
- data/lib/zabbix_manager/monitoring/validation.rb +92 -0
- data/lib/zabbix_manager/monitoring.rb +216 -642
- data/lib/zabbix_manager/resource.rb +235 -0
- data/lib/zabbix_manager/traffic.rb +238 -0
- data/lib/zabbix_manager/version.rb +1 -1
- data/lib/zabbix_manager.rb +66 -179
- data/zabbix_manager.gemspec +6 -22
- metadata +41 -190
- data/lib/zabbix_manager/basic/basic_alias.rb +0 -42
- data/lib/zabbix_manager/basic/basic_func.rb +0 -55
- data/lib/zabbix_manager/basic/basic_init.rb +0 -50
- data/lib/zabbix_manager/basic/basic_logic.rb +0 -239
- data/lib/zabbix_manager/classes/hostinterfaces.rb +0 -189
- data/lib/zabbix_manager/classes/usergroups.rb +0 -75
- data/lib/zabbix_manager/classes/usermacros.rb +0 -222
- 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
|