zabbix_manager 5.1.4 → 5.1.6
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 +25 -9
- data/LICENSE +1 -1
- data/README.md +202 -8
- data/lib/zabbix_manager/basic/basic_alias.rb +25 -3
- data/lib/zabbix_manager/basic/basic_func.rb +32 -61
- data/lib/zabbix_manager/basic/basic_init.rb +28 -13
- data/lib/zabbix_manager/basic/basic_logic.rb +172 -128
- data/lib/zabbix_manager/classes/actions.rb +21 -11
- data/lib/zabbix_manager/classes/applications.rb +12 -17
- data/lib/zabbix_manager/classes/configurations.rb +20 -5
- data/lib/zabbix_manager/classes/drules.rb +11 -23
- data/lib/zabbix_manager/classes/errors.rb +16 -28
- data/lib/zabbix_manager/classes/events.rb +6 -2
- data/lib/zabbix_manager/classes/graphs.rb +41 -34
- data/lib/zabbix_manager/classes/hostgroups.rb +53 -3
- data/lib/zabbix_manager/classes/hostinterfaces.rb +172 -12
- data/lib/zabbix_manager/classes/hosts.rb +197 -68
- data/lib/zabbix_manager/classes/httptests.rb +18 -20
- data/lib/zabbix_manager/classes/items.rb +199 -72
- data/lib/zabbix_manager/classes/maintenance.rb +6 -2
- data/lib/zabbix_manager/classes/mediatypes.rb +7 -63
- data/lib/zabbix_manager/classes/problems.rb +75 -57
- data/lib/zabbix_manager/classes/proxies.rb +48 -7
- data/lib/zabbix_manager/classes/roles.rb +47 -36
- data/lib/zabbix_manager/classes/screens.rb +45 -14
- data/lib/zabbix_manager/classes/scripts.rb +17 -7
- data/lib/zabbix_manager/classes/server.rb +7 -1
- data/lib/zabbix_manager/classes/templates.rb +59 -21
- data/lib/zabbix_manager/classes/triggers.rb +253 -66
- data/lib/zabbix_manager/classes/usergroups.rb +28 -6
- data/lib/zabbix_manager/classes/usermacros.rb +119 -33
- data/lib/zabbix_manager/classes/users.rb +39 -7
- data/lib/zabbix_manager/classes/valuemaps.rb +13 -19
- data/lib/zabbix_manager/client.rb +311 -135
- data/lib/zabbix_manager/http_transport.rb +192 -0
- data/lib/zabbix_manager/log_sanitizer.rb +68 -0
- data/lib/zabbix_manager/monitoring.rb +691 -0
- data/lib/zabbix_manager/version.rb +1 -1
- data/lib/zabbix_manager.rb +118 -40
- data/zabbix_manager.gemspec +21 -8
- metadata +208 -22
- data/lib/zabbix_manager/classes/unusable.rb +0 -11
|
@@ -2,103 +2,232 @@
|
|
|
2
2
|
|
|
3
3
|
class ZabbixManager
|
|
4
4
|
class Hosts < Basic
|
|
5
|
-
#
|
|
5
|
+
# 返回主机对应的 Zabbix API 模块名。
|
|
6
6
|
def method_name
|
|
7
7
|
"host"
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
#
|
|
10
|
+
# 使用技术主机名作为通用 CRUD 标识。
|
|
11
11
|
def identify
|
|
12
12
|
"host"
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
#
|
|
15
|
+
# 返回创建主机时的克制默认值。
|
|
16
16
|
def default_options
|
|
17
17
|
{
|
|
18
|
-
|
|
19
|
-
interfaces: {
|
|
20
|
-
main: 1,
|
|
21
|
-
useip: 1,
|
|
22
|
-
type: 2,
|
|
23
|
-
ip: nil,
|
|
24
|
-
dns: "",
|
|
25
|
-
port: 161,
|
|
26
|
-
details: {
|
|
27
|
-
version: 2,
|
|
28
|
-
community: "public"
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
status: 0,
|
|
32
|
-
available: 1,
|
|
33
|
-
groups: [],
|
|
34
|
-
proxy_hostid: nil,
|
|
18
|
+
status: 0,
|
|
35
19
|
inventory_mode: 1
|
|
36
20
|
}
|
|
37
21
|
end
|
|
38
22
|
|
|
39
|
-
#
|
|
23
|
+
# 按主机 ID 查询完整主机及接口、群组和模板。
|
|
40
24
|
def dump_by_id(data)
|
|
41
|
-
log "[DEBUG]
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
25
|
+
log "[DEBUG] Call dump_by_id with parameters: #{data.inspect}"
|
|
26
|
+
@client.api_request(
|
|
27
|
+
method: "host.get",
|
|
28
|
+
params: {
|
|
29
|
+
filter: { key.to_sym => data[key.to_sym] },
|
|
30
|
+
output: "extend",
|
|
31
|
+
selectGroups: "extend",
|
|
32
|
+
selectInterfaces: "extend",
|
|
33
|
+
selectParentTemplates: ["templateid", "host", "name"]
|
|
34
|
+
}
|
|
50
35
|
)
|
|
51
36
|
end
|
|
52
37
|
|
|
53
|
-
#
|
|
54
|
-
def
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
38
|
+
# 从指定主机批量解除模板关联。
|
|
39
|
+
def unlink_templates(data)
|
|
40
|
+
result = @client.api_request(
|
|
41
|
+
method: "host.massRemove",
|
|
42
|
+
params: {
|
|
43
|
+
hostids: data[:hosts_id],
|
|
44
|
+
templateids: data[:templates_id]
|
|
45
|
+
}
|
|
61
46
|
)
|
|
62
|
-
result.empty?
|
|
47
|
+
!result.empty?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# 使用显式群组和接口创建主机,不注入隐藏凭据。
|
|
51
|
+
def create(data)
|
|
52
|
+
attributes = default_options.merge(data.deep_symbolize_keys)
|
|
53
|
+
validate_create_attributes!(attributes)
|
|
54
|
+
result = @client.api_request(method: "host.create", params: attributes)
|
|
55
|
+
first_id(result, "hostids")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# 规范化并校验设备定义,不执行远端查询或写入。
|
|
59
|
+
# 已存在设备允许只更新部分字段,新设备的群组和接口由 create 再次严格校验。
|
|
60
|
+
# @return [Hash]
|
|
61
|
+
def validate(data)
|
|
62
|
+
attributes = data.deep_symbolize_keys
|
|
63
|
+
raise Invalid, "host is required" if attributes[:host].blank?
|
|
64
|
+
|
|
65
|
+
hostinterfaces.validate(attributes[:interfaces]) if attributes.key?(:interfaces)
|
|
66
|
+
attributes
|
|
63
67
|
end
|
|
64
68
|
|
|
65
|
-
#
|
|
66
|
-
def
|
|
67
|
-
|
|
69
|
+
# 幂等同步主机元数据和显式接口,调用方保持业务字段权威。
|
|
70
|
+
def reconcile(data)
|
|
71
|
+
attributes = validate(data)
|
|
72
|
+
host = attributes.fetch(:host)
|
|
73
|
+
|
|
74
|
+
hostid = get_id(host: host)
|
|
75
|
+
unless hostid
|
|
76
|
+
outcome, hostid = @client.with_upsert_lock("host-create:#{host}") do
|
|
77
|
+
current_id = get_id(host: host)
|
|
78
|
+
current_id ? [:existing, current_id] : [:created, create(attributes)]
|
|
79
|
+
end
|
|
80
|
+
return hostid.to_i if outcome == :created
|
|
81
|
+
end
|
|
68
82
|
|
|
69
|
-
hostid
|
|
70
|
-
|
|
83
|
+
update_existing(hostid, attributes)
|
|
84
|
+
end
|
|
71
85
|
|
|
72
|
-
|
|
73
|
-
|
|
86
|
+
# 更新主机元数据,并把接口同步委托给带主机锁的接口模块。
|
|
87
|
+
# @return [Integer]
|
|
88
|
+
# @api private
|
|
89
|
+
private def update_existing(hostid, attributes)
|
|
90
|
+
attributes = attributes.dup
|
|
74
91
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
update
|
|
92
|
+
interfaces = Array.wrap(attributes.delete(:interfaces))
|
|
93
|
+
hostinterfaces.validate(interfaces)
|
|
94
|
+
update_attributes = attributes.merge(hostid: hostid)
|
|
95
|
+
@client.api_request(method: "host.update", params: update_attributes)
|
|
96
|
+
hostinterfaces.reconcile_for_host(hostid: hostid, interfaces: interfaces) if interfaces.any?
|
|
97
|
+
hostid.to_i
|
|
79
98
|
end
|
|
80
99
|
|
|
81
|
-
#
|
|
82
|
-
def
|
|
83
|
-
|
|
100
|
+
# 查询指定主机的全部接口。
|
|
101
|
+
def interfaces_for(hostid)
|
|
102
|
+
hostinterfaces.for_host(hostid)
|
|
103
|
+
end
|
|
84
104
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
)
|
|
89
|
-
result&.[](0)&.[]("interfaceid")&.to_i
|
|
105
|
+
# 返回主机的第一个接口 ID;高频业务应优先使用 hostinterfaces。
|
|
106
|
+
def get_interface_id(hostid)
|
|
107
|
+
interfaces_for(hostid).first&.fetch("interfaceid", nil)
|
|
90
108
|
end
|
|
91
109
|
|
|
92
|
-
#
|
|
93
|
-
def
|
|
94
|
-
|
|
95
|
-
method: "host.massRemove",
|
|
96
|
-
params: {
|
|
97
|
-
hostids: data[:hosts_id],
|
|
98
|
-
templates: data[:templates_id]
|
|
99
|
-
}
|
|
100
|
-
)
|
|
101
|
-
result.empty? ? false : true
|
|
110
|
+
# 按技术主机名查询主机 ID。
|
|
111
|
+
def get_host_id(name)
|
|
112
|
+
find_host_id(host: name)
|
|
102
113
|
end
|
|
114
|
+
|
|
115
|
+
# 按可见名称查询主机 ID。
|
|
116
|
+
def get_hostid_by_name(name)
|
|
117
|
+
find_host_id(name: name)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# 用技术名、可见名或接口 IP/DNS 解析唯一主机。
|
|
121
|
+
def find_by_candidates(candidates)
|
|
122
|
+
matches = Array(candidates).filter_map { |candidate| candidate.to_s.strip.presence }.uniq.flat_map do |candidate|
|
|
123
|
+
hosts = [find_host_by_filter(host: candidate), find_host_by_filter(name: candidate)].compact
|
|
124
|
+
interface = find_interface_by_endpoint(candidate)
|
|
125
|
+
hosts << find_host_by_filter(hostid: interface["hostid"]) if interface
|
|
126
|
+
hosts
|
|
127
|
+
end
|
|
128
|
+
matches.uniq! { |host| host.fetch("hostid") }
|
|
129
|
+
if matches.length > 1
|
|
130
|
+
raise Conflict, "host candidates resolve to multiple hosts; provide an explicit host object"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
matches.first
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# 按主机 ID 查询唯一主机,并返回表达式需要的技术名称。
|
|
137
|
+
# @return [Hash, nil]
|
|
138
|
+
def find_by_id(hostid)
|
|
139
|
+
find_host_by_filter(hostid: hostid)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# 批量启用或停用主机。
|
|
143
|
+
# @return [Array<Integer>]
|
|
144
|
+
def set_status(hostids, enabled:)
|
|
145
|
+
ids = Array(hostids).filter_map { |value| value.to_s.strip.presence }.uniq
|
|
146
|
+
raise Invalid, "hostids are required" if ids.empty?
|
|
147
|
+
|
|
148
|
+
params = ids.map { |hostid| { hostid: hostid, status: enabled ? 0 : 1 } }
|
|
149
|
+
result = @client.api_request(method: "host.update", params: params)
|
|
150
|
+
Array(result.fetch("hostids")).map(&:to_i)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# 把可见名称对应的主机更新为稳定技术标识。
|
|
154
|
+
def update_host_to_serial(data)
|
|
155
|
+
attributes = data.deep_symbolize_keys
|
|
156
|
+
hostid = get_hostid_by_name(attributes[:name])
|
|
157
|
+
return nil unless hostid
|
|
158
|
+
|
|
159
|
+
attributes.delete(:templates)
|
|
160
|
+
@client.api_request(method: "host.update", params: attributes.merge(hostid: hostid))
|
|
161
|
+
hostid.to_i
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
private
|
|
165
|
+
|
|
166
|
+
# 校验创建主机所需的技术名、群组和接口。
|
|
167
|
+
def validate_create_attributes!(attributes)
|
|
168
|
+
raise Invalid, "host is required" if attributes[:host].blank?
|
|
169
|
+
raise Invalid, "groups are required when creating a host" if Array.wrap(attributes[:groups]).empty?
|
|
170
|
+
raise Invalid, "interfaces are required when creating a host" if Array.wrap(attributes[:interfaces]).empty?
|
|
171
|
+
|
|
172
|
+
attributes[:interfaces] = hostinterfaces.validate_for_create(attributes[:interfaces])
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# 使用精确过滤查询唯一主机。
|
|
176
|
+
def find_host_by_filter(filter)
|
|
177
|
+
result = @client.api_request(
|
|
178
|
+
method: "host.get",
|
|
179
|
+
params: {
|
|
180
|
+
output: %w[hostid host name status],
|
|
181
|
+
filter: filter,
|
|
182
|
+
selectInterfaces: %w[interfaceid ip dns]
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
raise Conflict, "host lookup is ambiguous for #{filter.inspect}" if result.length > 1
|
|
186
|
+
|
|
187
|
+
result.first
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# 按 IP 和 DNS 端点查询唯一主机接口。
|
|
191
|
+
def find_interface_by_endpoint(candidate)
|
|
192
|
+
matches = []
|
|
193
|
+
%i[ip dns].each do |field|
|
|
194
|
+
result = @client.api_request(
|
|
195
|
+
method: "hostinterface.get",
|
|
196
|
+
params: { filter: { field => candidate }, output: ["hostid"] }
|
|
197
|
+
)
|
|
198
|
+
matches.concat(result)
|
|
199
|
+
end
|
|
200
|
+
matches.uniq! { |interface| interface.fetch("hostid") }
|
|
201
|
+
raise Conflict, "interface endpoint #{candidate} belongs to multiple hosts" if matches.length > 1
|
|
202
|
+
|
|
203
|
+
matches.first
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# 只查询唯一主机 ID。
|
|
207
|
+
def find_host_id(filter)
|
|
208
|
+
result = @client.api_request(
|
|
209
|
+
method: "host.get",
|
|
210
|
+
params: {
|
|
211
|
+
output: ["hostid"],
|
|
212
|
+
filter: filter
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
raise Conflict, "host lookup is ambiguous for #{filter.inspect}" if result.length > 1
|
|
216
|
+
|
|
217
|
+
result.first&.fetch("hostid", nil)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# 从 Zabbix 创建响应中提取首个整数 ID。
|
|
221
|
+
def first_id(result, key_name)
|
|
222
|
+
value = result.fetch(key_name).first
|
|
223
|
+
value && value.to_i
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# 延迟构建接口模块并复用当前客户端。
|
|
227
|
+
# @return [HostInterfaces]
|
|
228
|
+
# @api private
|
|
229
|
+
def hostinterfaces
|
|
230
|
+
@hostinterfaces ||= HostInterfaces.new(@client)
|
|
231
|
+
end
|
|
103
232
|
end
|
|
104
233
|
end
|
|
@@ -2,39 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
class ZabbixManager
|
|
4
4
|
class HttpTests < Basic
|
|
5
|
-
#
|
|
5
|
+
# 返回 Web 场景对象对应的 Zabbix API 方法前缀。
|
|
6
|
+
#
|
|
7
|
+
# @return [String]
|
|
6
8
|
def method_name
|
|
7
9
|
"httptest"
|
|
8
10
|
end
|
|
9
11
|
|
|
10
|
-
#
|
|
12
|
+
# 返回 Web 场景用于业务识别的字段名。
|
|
13
|
+
#
|
|
14
|
+
# @return [String]
|
|
11
15
|
def identify
|
|
12
16
|
"name"
|
|
13
17
|
end
|
|
14
18
|
|
|
15
|
-
#
|
|
19
|
+
# 返回创建 Web 场景时使用的默认步骤列表。
|
|
20
|
+
#
|
|
21
|
+
# @return [Hash] Web 场景默认属性
|
|
16
22
|
def default_options
|
|
17
23
|
{
|
|
18
|
-
|
|
19
|
-
name: nil,
|
|
20
|
-
steps: []
|
|
24
|
+
steps: []
|
|
21
25
|
}
|
|
22
26
|
end
|
|
23
27
|
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# 通过 Zabbix API 创建或更新 HttpTest 对象
|
|
35
|
-
def create_or_update(data)
|
|
36
|
-
httptestid = get_id(name: data[:name], hostid: data[:hostid])
|
|
37
|
-
httptestid ? update(data.merge(httptestid: httptestid)) : create(data)
|
|
28
|
+
# 生成由 Web 场景名称和所属主机构成的稳定查询条件。
|
|
29
|
+
#
|
|
30
|
+
# @param data [Hash] 包含 name 和 hostid 的 Web 场景属性
|
|
31
|
+
# @raise [KeyError] 缺少 name 或 hostid 时抛出
|
|
32
|
+
# @return [Hash] Web 场景唯一查询条件
|
|
33
|
+
def identity_filter(data)
|
|
34
|
+
attributes = data.deep_symbolize_keys
|
|
35
|
+
{ name: attributes.fetch(:name), hostid: attributes.fetch(:hostid) }
|
|
38
36
|
end
|
|
39
37
|
end
|
|
40
38
|
end
|
|
@@ -2,97 +2,224 @@
|
|
|
2
2
|
|
|
3
3
|
class ZabbixManager
|
|
4
4
|
class Items < Basic
|
|
5
|
-
|
|
5
|
+
DEFAULT_OPTIONS = {
|
|
6
|
+
delay: "1m",
|
|
7
|
+
history: "1h",
|
|
8
|
+
status: 0,
|
|
9
|
+
value_type: 3
|
|
10
|
+
}.freeze
|
|
11
|
+
ITEM_TYPES = [0, 2, 3, 5, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22].freeze
|
|
12
|
+
REQUIRED_INTERFACE_TYPES = [0, 12, 16, 17, 20].freeze
|
|
13
|
+
REQUIRED_PARAMS_TYPES = [11, 13, 14, 15, 21, 22].freeze
|
|
14
|
+
|
|
15
|
+
# 返回监控项对应的 Zabbix API 模块名。
|
|
16
|
+
#
|
|
17
|
+
# @return [String]
|
|
6
18
|
def method_name
|
|
7
19
|
"item"
|
|
8
20
|
end
|
|
9
21
|
|
|
10
|
-
#
|
|
22
|
+
# 使用名称作为通用 CRUD 的业务标识。
|
|
23
|
+
#
|
|
24
|
+
# @return [String]
|
|
11
25
|
def identify
|
|
12
26
|
"name"
|
|
13
27
|
end
|
|
14
28
|
|
|
15
|
-
#
|
|
29
|
+
# 返回创建监控项时的克制默认值。
|
|
30
|
+
#
|
|
31
|
+
# @return [Hash]
|
|
16
32
|
def default_options
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
33
|
+
DEFAULT_OPTIONS.dup
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# 使用主机和名称组成通用 CRUD 的查询边界。
|
|
37
|
+
#
|
|
38
|
+
# @param data [Hash] Needs to include name and hostid to properly identify Items via Zabbix API
|
|
39
|
+
# @raise [ApiError] Error returned when there is a problem with the Zabbix API call.
|
|
40
|
+
# @raise [TransportError] Error raised when HTTP status from Zabbix Server response is not a 200 OK.
|
|
41
|
+
# @return [Integer] Zabbix object id
|
|
42
|
+
def identity_filter(data)
|
|
43
|
+
attributes = data.deep_symbolize_keys
|
|
44
|
+
{ name: attributes.fetch(:name), hostid: attributes.fetch(:hostid) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# 查询主机监控项,可按稳定 key 集合过滤并选择关联对象。
|
|
48
|
+
# @return [Array<Hash>]
|
|
49
|
+
def for_host(hostid, keys: nil, output: "extend", select_preprocessing: nil, select_tags: nil)
|
|
50
|
+
raise Invalid, "hostid is required" if hostid.blank?
|
|
51
|
+
|
|
52
|
+
params = { hostids: hostid, output: output }
|
|
53
|
+
params[:filter] = { key_: keys } if keys.present?
|
|
54
|
+
params[:selectPreprocessing] = select_preprocessing if select_preprocessing
|
|
55
|
+
params[:selectTags] = select_tags if select_tags
|
|
56
|
+
@client.api_request(method: "item.get", params: params)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# 按主机和稳定 key 查询唯一监控项。
|
|
60
|
+
# @return [Hash, nil]
|
|
61
|
+
def find_by_key(hostid:, key:)
|
|
62
|
+
result = for_host(hostid, keys: key, output: ["itemid", "key_", "name"])
|
|
63
|
+
raise Conflict, "multiple items use key #{key} on host #{hostid}" if result.length > 1
|
|
64
|
+
|
|
65
|
+
result.first
|
|
51
66
|
end
|
|
52
67
|
|
|
53
|
-
#
|
|
54
|
-
|
|
55
|
-
|
|
68
|
+
# 按 hostid + key_ 幂等创建或更新监控项。
|
|
69
|
+
# @return [Integer]
|
|
70
|
+
def upsert_by_key(data)
|
|
71
|
+
attributes = validate_item!(data)
|
|
56
72
|
|
|
57
|
-
|
|
58
|
-
|
|
73
|
+
current = find_by_key(hostid: attributes[:hostid], key: attributes[:key_])
|
|
74
|
+
if current
|
|
75
|
+
itemid = current.fetch("itemid")
|
|
76
|
+
@client.api_request(
|
|
77
|
+
method: "item.update",
|
|
78
|
+
params: attributes.except(:hostid).merge(itemid: itemid)
|
|
79
|
+
)
|
|
80
|
+
itemid.to_i
|
|
59
81
|
else
|
|
60
|
-
|
|
82
|
+
create_attributes = default_options.merge(attributes)
|
|
83
|
+
validate_create_item!(create_attributes)
|
|
84
|
+
result = @client.api_request(method: "item.create", params: create_attributes)
|
|
85
|
+
result.fetch("itemids").first.to_i
|
|
61
86
|
end
|
|
62
87
|
end
|
|
63
88
|
|
|
64
|
-
#
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
89
|
+
# 先校验整批数据,再按稳定 key 逐项幂等写入。
|
|
90
|
+
# @return [Array<Integer>]
|
|
91
|
+
def upsert_many(collection)
|
|
92
|
+
items = Array(collection).map { |data| validate_item!(data) }
|
|
93
|
+
identities = items.map { |item| [item[:hostid].to_s, item[:key_].to_s] }
|
|
94
|
+
duplicate = identities.tally.find { |_identity, count| count > 1 }&.first
|
|
95
|
+
raise Invalid, "duplicate hostid + key_ identity #{duplicate.join(":")}" if duplicate
|
|
96
|
+
|
|
97
|
+
items.map { |item| upsert_by_key(item) }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# 批量启用或停用监控项。
|
|
101
|
+
# @return [Array<Integer>]
|
|
102
|
+
def set_status(hostid:, itemids:, enabled:)
|
|
103
|
+
ids = normalized_ids(itemids, "itemids")
|
|
104
|
+
verify_host_ownership!(hostid, ids)
|
|
105
|
+
result = @client.api_request(
|
|
106
|
+
method: "item.update",
|
|
107
|
+
params: ids.map { |itemid| { itemid: itemid, status: enabled ? 0 : 1 } }
|
|
108
|
+
)
|
|
109
|
+
Array(result.fetch("itemids")).map(&:to_i)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# 批量删除明确指定的监控项。
|
|
113
|
+
# @return [Array<Integer>]
|
|
114
|
+
def delete_many(hostid:, itemids:)
|
|
115
|
+
ids = normalized_ids(itemids, "itemids")
|
|
116
|
+
verify_host_ownership!(hostid, ids)
|
|
117
|
+
result = @client.api_request(method: "item.delete", params: ids)
|
|
118
|
+
Array(result.fetch("itemids")).map(&:to_i)
|
|
68
119
|
end
|
|
69
120
|
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
hostids: hostid,
|
|
84
|
-
search: {
|
|
85
|
-
name: iface
|
|
86
|
-
}
|
|
121
|
+
# 返回主机中可能表示接口流量的已启用监控项。
|
|
122
|
+
# 方向和接口精确匹配由 Monitoring 业务层负责。
|
|
123
|
+
# @return [Array<Hash>]
|
|
124
|
+
def monitored_traffic_candidates(hostid)
|
|
125
|
+
@client.api_request(
|
|
126
|
+
method: "item.get",
|
|
127
|
+
params: {
|
|
128
|
+
hostids: [hostid],
|
|
129
|
+
monitored: true,
|
|
130
|
+
output: %w[itemid hostid name key_ snmp_oid value_type status units],
|
|
131
|
+
selectPreprocessing: "extend",
|
|
132
|
+
sortfield: "name",
|
|
133
|
+
sortorder: "ASC"
|
|
87
134
|
}
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
135
|
+
)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# 按主机接口幂等创建或更新 DNS 解析监控项,并返回监控项 ID。
|
|
139
|
+
# DNS 名称会进入 item key,因此拒绝可能改变 key 参数结构的分隔符。
|
|
140
|
+
# @return [Integer]
|
|
141
|
+
def upsert_dns_item(hostid:, interfaceid:, dns_name:)
|
|
142
|
+
name = dns_name.to_s.strip
|
|
143
|
+
raise Invalid, "dns_name is required" if name.blank?
|
|
144
|
+
raise Invalid, "dns_name contains unsupported item key delimiters" if name.match?(/[\[\],]/)
|
|
145
|
+
|
|
146
|
+
itemid = upsert_by_key(
|
|
147
|
+
hostid: hostid,
|
|
148
|
+
interfaceid: interfaceid,
|
|
149
|
+
name: "【DNS域名解析监控】#{name}",
|
|
150
|
+
key_: "net.dns.record[,#{name},A,2,2]",
|
|
151
|
+
type: 0,
|
|
152
|
+
value_type: 1,
|
|
153
|
+
delay: "1m",
|
|
154
|
+
history: "90d",
|
|
155
|
+
timeout: "3s"
|
|
156
|
+
)
|
|
157
|
+
log "Ensured DNS monitoring item for #{name}"
|
|
158
|
+
itemid
|
|
159
|
+
rescue StandardError => e
|
|
160
|
+
log "Failed to ensure DNS monitoring item: #{e.class}"
|
|
161
|
+
raise
|
|
96
162
|
end
|
|
163
|
+
|
|
164
|
+
private
|
|
165
|
+
|
|
166
|
+
# 规范化并校验幂等写入所需字段。
|
|
167
|
+
# @return [Hash]
|
|
168
|
+
# @api private
|
|
169
|
+
def validate_item!(data)
|
|
170
|
+
attributes = data.deep_symbolize_keys
|
|
171
|
+
raise Invalid, "hostid is required" if attributes[:hostid].blank?
|
|
172
|
+
raise Invalid, "key_ is required" if attributes[:key_].blank?
|
|
173
|
+
raise Invalid, "name is required" if attributes[:name].blank?
|
|
174
|
+
|
|
175
|
+
attributes
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# 校验 item.create 的通用必填字段和 SNMP 条件字段。
|
|
179
|
+
def validate_create_item!(attributes)
|
|
180
|
+
raise Invalid, "type is required when creating an item" if attributes[:type].blank?
|
|
181
|
+
raise Invalid, "value_type is required when creating an item" if attributes[:value_type].blank?
|
|
182
|
+
|
|
183
|
+
type = Integer(attributes[:type])
|
|
184
|
+
raise Invalid, "unsupported item type #{type}" unless ITEM_TYPES.include?(type)
|
|
185
|
+
|
|
186
|
+
if REQUIRED_INTERFACE_TYPES.include?(type) && attributes[:interfaceid].blank?
|
|
187
|
+
raise Invalid, "interfaceid is required for item type #{type}"
|
|
188
|
+
end
|
|
189
|
+
if REQUIRED_PARAMS_TYPES.include?(type) && attributes[:params].blank?
|
|
190
|
+
raise Invalid, "params is required for item type #{type}"
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
raise Invalid,
|
|
194
|
+
"master_itemid is required for a dependent item" if type == 18 && attributes[:master_itemid].blank?
|
|
195
|
+
raise Invalid, "url is required for an HTTP item" if type == 19 && attributes[:url].blank?
|
|
196
|
+
return unless type == 20
|
|
197
|
+
|
|
198
|
+
raise Invalid, "snmp_oid is required for an SNMP item" if attributes[:snmp_oid].blank?
|
|
199
|
+
rescue ArgumentError, TypeError
|
|
200
|
+
raise Invalid, "type must be a supported integer"
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# 回查监控项归属,阻止共享高权限令牌跨主机修改。
|
|
204
|
+
def verify_host_ownership!(hostid, ids)
|
|
205
|
+
raise Invalid, "hostid is required" if hostid.blank?
|
|
206
|
+
|
|
207
|
+
result = @client.api_request(
|
|
208
|
+
method: "item.get", params: { hostids: hostid, itemids: ids, output: ["itemid"] }
|
|
209
|
+
)
|
|
210
|
+
owned_ids = result.map { |item| item.fetch("itemid").to_s }
|
|
211
|
+
foreign_ids = ids - owned_ids
|
|
212
|
+
raise Conflict, "items do not belong to host #{hostid}: #{foreign_ids.join(", ")}" if foreign_ids.any?
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# 统一批量 ID 并拒绝空集合。
|
|
216
|
+
# @return [Array<String>]
|
|
217
|
+
# @api private
|
|
218
|
+
def normalized_ids(values, name)
|
|
219
|
+
ids = Array(values).filter_map { |value| value.to_s.strip.presence }.uniq
|
|
220
|
+
raise Invalid, "#{name} are required" if ids.empty?
|
|
221
|
+
|
|
222
|
+
ids
|
|
223
|
+
end
|
|
97
224
|
end
|
|
98
225
|
end
|
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
class ZabbixManager
|
|
4
4
|
class Maintenance < Basic
|
|
5
|
-
#
|
|
5
|
+
# 返回维护期对象对应的 Zabbix API 方法前缀。
|
|
6
|
+
#
|
|
7
|
+
# @return [String]
|
|
6
8
|
def method_name
|
|
7
9
|
"maintenance"
|
|
8
10
|
end
|
|
9
11
|
|
|
10
|
-
#
|
|
12
|
+
# 返回维护期对象用于业务识别的字段名。
|
|
13
|
+
#
|
|
14
|
+
# @return [String]
|
|
11
15
|
def identify
|
|
12
16
|
"name"
|
|
13
17
|
end
|