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,261 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class ZabbixManager
|
|
4
|
+
# 封装高频使用的 Zabbix 主机接口查询与幂等更新。
|
|
5
|
+
class HostInterfaces < Resource
|
|
6
|
+
# 返回对应的 Zabbix API 模块名。
|
|
7
|
+
# @return [String]
|
|
8
|
+
def method_name
|
|
9
|
+
"hostinterface"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# 返回接口对象的主键字段。
|
|
13
|
+
# @return [String]
|
|
14
|
+
def key
|
|
15
|
+
"interfaceid"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# 使用接口 ID 作为通用查询标识。
|
|
19
|
+
# @return [String]
|
|
20
|
+
def identify
|
|
21
|
+
"interfaceid"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# 查询指定主机的全部接口,可选择同时返回关联监控项。
|
|
25
|
+
# @return [Array<Hash>]
|
|
26
|
+
def for_host(hostid, select_items: nil)
|
|
27
|
+
raise Invalid, "hostid is required" if hostid.blank?
|
|
28
|
+
|
|
29
|
+
params = { hostids: hostid, output: "extend" }
|
|
30
|
+
params[:selectItems] = select_items if select_items
|
|
31
|
+
@client.api_request(method: "hostinterface.get", params: params)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# 规范化并校验接口定义,不执行远端查询或写入。
|
|
35
|
+
# @return [Array<Hash>]
|
|
36
|
+
def validate(interfaces)
|
|
37
|
+
desired = Array.wrap(interfaces).map { |interface| normalize_definition(interface) }
|
|
38
|
+
validate_definitions!(desired)
|
|
39
|
+
desired
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# 校验 host.create 中全部接口的创建必填合同。
|
|
43
|
+
# @return [Array<Hash>]
|
|
44
|
+
def validate_for_create(interfaces)
|
|
45
|
+
validate(interfaces).each { |attributes| validate_create_definition!(attributes) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# 只读取并校验接口归属和创建必填字段,供多资源编排在首个写入前预检。
|
|
49
|
+
# @param hostid [Integer, String] 目标主机 ID
|
|
50
|
+
# @param interfaces [Array<Hash>] 期望接口定义
|
|
51
|
+
# @return [Array<Hash>] 已验证的接口属性;执行时仍需重新核实远端状态
|
|
52
|
+
def validate_for_host(hostid:, interfaces:)
|
|
53
|
+
plan_for_host(hostid: hostid, interfaces: interfaces).map(&:first)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# 幂等创建或更新一个主机的接口集合。
|
|
57
|
+
# @return [Array<Integer>]
|
|
58
|
+
def reconcile_for_host(hostid:, interfaces:)
|
|
59
|
+
@client.with_upsert_lock("host-interfaces:#{hostid}") do
|
|
60
|
+
plan = plan_for_host(hostid: hostid, interfaces: interfaces)
|
|
61
|
+
apply_plan(hostid: hostid, plan: plan)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# 删除明确指定的主机接口;关联监控项由 Zabbix 按官方规则处理。
|
|
66
|
+
# @return [Array<Integer>]
|
|
67
|
+
def delete_many(hostid:, interfaceids:)
|
|
68
|
+
ids = normalized_ids(interfaceids)
|
|
69
|
+
|
|
70
|
+
owned_ids = for_host(hostid).map { |interface| interface.fetch("interfaceid").to_s }
|
|
71
|
+
foreign_ids = ids - owned_ids
|
|
72
|
+
raise Conflict, "interfaces do not belong to host #{hostid}: #{foreign_ids.join(", ")}" if foreign_ids.any?
|
|
73
|
+
|
|
74
|
+
result = @client.api_request(method: "hostinterface.delete", params: ids)
|
|
75
|
+
response_ids(result, expected: ids)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
# 预先校验并生成仅供当前对象执行的接口变更计划。
|
|
81
|
+
def plan_for_host(hostid:, interfaces:)
|
|
82
|
+
desired = validate(interfaces)
|
|
83
|
+
existing = for_host(hostid)
|
|
84
|
+
plans = desired.map do |attributes|
|
|
85
|
+
current = matching_interface(existing, attributes)
|
|
86
|
+
response_identifier(current["interfaceid"]) if current
|
|
87
|
+
validate_create_definition!(attributes) unless current
|
|
88
|
+
[attributes, current]
|
|
89
|
+
end
|
|
90
|
+
targets = plans.filter_map { |_attributes, current| current && response_identifier(current["interfaceid"]) }
|
|
91
|
+
raise Invalid, "duplicate resolved interface target" unless targets.uniq == targets
|
|
92
|
+
|
|
93
|
+
plans
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# 执行内部生成的接口变更计划,避免调用方伪造跨主机接口 ID。
|
|
97
|
+
def apply_plan(hostid:, plan:)
|
|
98
|
+
plan.map do |attributes, current|
|
|
99
|
+
current ? update_interface(attributes, current) : create_interface(hostid, attributes)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# 把接口字段递归转换为统一符号键。
|
|
104
|
+
def normalize_definition(interface)
|
|
105
|
+
raise Invalid, "interface must be a hash" unless interface.is_a?(Hash)
|
|
106
|
+
|
|
107
|
+
attributes = interface.deep_symbolize_keys
|
|
108
|
+
if attributes.key?(:type)
|
|
109
|
+
type = integer_attribute(attributes[:type], "interface type")
|
|
110
|
+
raise Invalid, "interface type must be 1, 2, 3, or 4" unless [1, 2, 3, 4].include?(type)
|
|
111
|
+
end
|
|
112
|
+
%i[main useip].each do |field|
|
|
113
|
+
next unless attributes.key?(field)
|
|
114
|
+
|
|
115
|
+
unless [0, 1].include?(integer_attribute(attributes[field], "interface #{field}"))
|
|
116
|
+
raise Invalid, "interface #{field} must be 0 or 1"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
%i[ip dns].each do |field|
|
|
120
|
+
if attributes.key?(field) && !attributes[field].is_a?(String)
|
|
121
|
+
raise Invalid, "interface #{field} must be a string"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
validate_port!(attributes[:port]) if attributes.key?(:port)
|
|
125
|
+
details = attributes[:details]
|
|
126
|
+
if attributes.key?(:details) && !details.is_a?(Hash)
|
|
127
|
+
raise Invalid, "interface details must be a hash"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
validate_supplied_snmp_details!(details) if details
|
|
131
|
+
attributes
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def validate_supplied_snmp_details!(details)
|
|
135
|
+
validate_snmp_version!(details[:version]) if details.key?(:version)
|
|
136
|
+
%i[community securityname authpassphrase privpassphrase contextname].each do |field|
|
|
137
|
+
if details.key?(field) && !details[field].is_a?(String)
|
|
138
|
+
raise Invalid, "SNMP #{field} must be a string"
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
{ bulk: [0, 1], securitylevel: [0, 1, 2] }.each do |field, allowed|
|
|
142
|
+
next unless details.key?(field)
|
|
143
|
+
|
|
144
|
+
unless allowed.include?(integer_attribute(details[field], "SNMP #{field}"))
|
|
145
|
+
raise Invalid, "SNMP #{field} is invalid"
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def validate_port!(value)
|
|
151
|
+
text = value.to_s
|
|
152
|
+
numeric = (value.is_a?(String) || value.is_a?(Integer)) && text.match?(/\A[1-9]\d*\z/)
|
|
153
|
+
macro = value.is_a?(String) && text.match?(/\A\{\$[^{}\r\n]+\}\z/)
|
|
154
|
+
return if macro || (numeric && value.to_i <= 65_535)
|
|
155
|
+
|
|
156
|
+
raise Invalid, "interface port must be between 1 and 65535 or a user macro"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# 校验新接口的通用字段及 SNMP 条件字段。
|
|
160
|
+
def validate_create_definition!(attributes)
|
|
161
|
+
raise Invalid, "new interfaces cannot reference an existing interfaceid" if attributes.key?(:interfaceid)
|
|
162
|
+
|
|
163
|
+
attributes[:ip] = "" unless attributes.key?(:ip)
|
|
164
|
+
attributes[:dns] = "" unless attributes.key?(:dns)
|
|
165
|
+
endpoint_identity(attributes)
|
|
166
|
+
validate_snmp_details!(attributes) if attributes[:type].to_i == 2
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# 校验 SNMP 版本和 v1/v2c community 必填合同。
|
|
170
|
+
def validate_snmp_details!(attributes)
|
|
171
|
+
details = attributes[:details]&.deep_symbolize_keys
|
|
172
|
+
raise Invalid, "SNMP interface details are required" unless details
|
|
173
|
+
|
|
174
|
+
version = validate_snmp_version!(details[:version])
|
|
175
|
+
if [1, 2].include?(version) && details[:community].blank?
|
|
176
|
+
raise Invalid, "SNMP v1/v2 interface community is required"
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
attributes[:details] = details
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def validate_snmp_version!(value)
|
|
183
|
+
version = integer_attribute(value, "SNMP interface version")
|
|
184
|
+
raise Invalid, "SNMP interface version must be 1, 2, or 3" unless [1, 2, 3].include?(version)
|
|
185
|
+
|
|
186
|
+
version
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# 校验期望接口是否包含重复身份。
|
|
190
|
+
# @return [void]
|
|
191
|
+
# @api private
|
|
192
|
+
def validate_definitions!(interfaces)
|
|
193
|
+
identities = interfaces.map { |interface| interface_identity(interface) }
|
|
194
|
+
duplicate = identities.tally.find { |_identity, count| count > 1 }&.first
|
|
195
|
+
raise Invalid, "duplicate desired interface identity #{duplicate}" if duplicate
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# 按显式 ID 或稳定端点身份匹配已有接口。
|
|
199
|
+
# @return [Hash, nil]
|
|
200
|
+
# @api private
|
|
201
|
+
def matching_interface(existing, attributes)
|
|
202
|
+
if attributes[:interfaceid]
|
|
203
|
+
match = existing.find { |item| item["interfaceid"].to_s == attributes[:interfaceid].to_s }
|
|
204
|
+
raise Invalid, "interfaceid #{attributes[:interfaceid]} does not belong to this host" unless match
|
|
205
|
+
|
|
206
|
+
return match
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
candidates = existing.select { |item| endpoint_identity(item) == endpoint_identity(attributes) }
|
|
210
|
+
raise Conflict, "interface identity is ambiguous; provide interfaceid" if candidates.length > 1
|
|
211
|
+
|
|
212
|
+
candidates.first
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# 生成不依赖数组顺序的接口身份。
|
|
216
|
+
# @return [String]
|
|
217
|
+
# @api private
|
|
218
|
+
def interface_identity(attributes)
|
|
219
|
+
values = attributes.with_indifferent_access
|
|
220
|
+
return "id:#{values[:interfaceid]}" if values[:interfaceid].present?
|
|
221
|
+
|
|
222
|
+
endpoint_identity(values)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# 使用类型、端点和端口匹配接口,不把可变的 main 或远端 ID 作为端点身份。
|
|
226
|
+
def endpoint_identity(attributes)
|
|
227
|
+
values = attributes.with_indifferent_access
|
|
228
|
+
|
|
229
|
+
%i[type main useip port].each do |field|
|
|
230
|
+
raise Invalid, "interface #{field} is required" if values[field].blank?
|
|
231
|
+
end
|
|
232
|
+
endpoint_field = values[:useip].to_i == 1 ? :ip : :dns
|
|
233
|
+
raise Invalid, "interface #{endpoint_field} is required" if values[endpoint_field].blank?
|
|
234
|
+
|
|
235
|
+
"endpoint:#{values[:type]}:#{values[:useip]}:#{values[endpoint_field]}:#{values[:port]}"
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# 更新已属于目标主机的接口。
|
|
239
|
+
# @return [Integer]
|
|
240
|
+
# @api private
|
|
241
|
+
def update_interface(attributes, current)
|
|
242
|
+
interfaceid = current.fetch("interfaceid")
|
|
243
|
+
result = @client.api_request(
|
|
244
|
+
method: "hostinterface.update",
|
|
245
|
+
params: attributes.except(:hostid).merge(interfaceid: interfaceid)
|
|
246
|
+
)
|
|
247
|
+
response_id(result, expected: [interfaceid])
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# 为目标主机创建缺失接口。
|
|
251
|
+
# @return [Integer]
|
|
252
|
+
# @api private
|
|
253
|
+
def create_interface(hostid, attributes)
|
|
254
|
+
result = @client.api_request(
|
|
255
|
+
method: "hostinterface.create",
|
|
256
|
+
params: attributes.except(:interfaceid).merge(hostid: hostid)
|
|
257
|
+
)
|
|
258
|
+
response_id(result)
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class ZabbixManager
|
|
4
|
-
|
|
4
|
+
# 主机身份解析、状态对账与模板关联;技术名称和显示名称分别处理。
|
|
5
|
+
class Hosts < Resource
|
|
5
6
|
# 返回主机对应的 Zabbix API 模块名。
|
|
6
7
|
def method_name
|
|
7
8
|
"host"
|
|
@@ -22,7 +23,6 @@ class ZabbixManager
|
|
|
22
23
|
|
|
23
24
|
# 按主机 ID 查询完整主机及接口、群组和模板。
|
|
24
25
|
def dump_by_id(data)
|
|
25
|
-
log "[DEBUG] Call dump_by_id with parameters: #{data.inspect}"
|
|
26
26
|
@client.api_request(
|
|
27
27
|
method: "host.get",
|
|
28
28
|
params: {
|
|
@@ -35,16 +35,25 @@ class ZabbixManager
|
|
|
35
35
|
)
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
-
#
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
38
|
+
# 向指定主机追加模板关联,保留现有模板。
|
|
39
|
+
# @return [Array<Integer>] 已更新主机 ID
|
|
40
|
+
def link_templates(host_ids:, template_ids:)
|
|
41
|
+
write_templates("massadd", host_ids, template_ids)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# 完整替换指定主机的模板关联,空模板集合明确清除全部关联。
|
|
45
|
+
# @return [Array<Integer>] 已更新主机 ID
|
|
46
|
+
def replace_templates(host_ids:, template_ids:)
|
|
47
|
+
write_templates("massupdate", host_ids, template_ids, allow_empty: true)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# 从指定主机解除选定模板的关联,保留模板生成的实体。
|
|
51
|
+
# @return [Array<Integer>] 已更新主机 ID
|
|
52
|
+
def unlink_templates(host_ids:, template_ids:)
|
|
53
|
+
ids = normalized_ids(host_ids)
|
|
54
|
+
templates = normalized_ids(template_ids)
|
|
55
|
+
result = @client.api_request(method: "host.massremove", params: { hostids: ids, templateids: templates })
|
|
56
|
+
response_ids(result, expected: ids)
|
|
48
57
|
end
|
|
49
58
|
|
|
50
59
|
# 使用显式群组和接口创建主机,不注入隐藏凭据。
|
|
@@ -52,7 +61,7 @@ class ZabbixManager
|
|
|
52
61
|
attributes = default_options.merge(data.deep_symbolize_keys)
|
|
53
62
|
validate_create_attributes!(attributes)
|
|
54
63
|
result = @client.api_request(method: "host.create", params: attributes)
|
|
55
|
-
|
|
64
|
+
response_id(result)
|
|
56
65
|
end
|
|
57
66
|
|
|
58
67
|
# 规范化并校验设备定义,不执行远端查询或写入。
|
|
@@ -62,7 +71,7 @@ class ZabbixManager
|
|
|
62
71
|
attributes = data.deep_symbolize_keys
|
|
63
72
|
raise Invalid, "host is required" if attributes[:host].blank?
|
|
64
73
|
|
|
65
|
-
|
|
74
|
+
host_interfaces.validate(attributes[:interfaces]) if attributes.key?(:interfaces)
|
|
66
75
|
attributes
|
|
67
76
|
end
|
|
68
77
|
|
|
@@ -90,19 +99,19 @@ class ZabbixManager
|
|
|
90
99
|
attributes = attributes.dup
|
|
91
100
|
|
|
92
101
|
interfaces = Array.wrap(attributes.delete(:interfaces))
|
|
93
|
-
|
|
102
|
+
host_interfaces.validate(interfaces)
|
|
94
103
|
update_attributes = attributes.merge(hostid: hostid)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
hostid
|
|
104
|
+
host_interfaces.reconcile_for_host(hostid: hostid, interfaces: interfaces) if interfaces.any?
|
|
105
|
+
result = @client.api_request(method: "host.update", params: update_attributes)
|
|
106
|
+
response_id(result, expected: [hostid])
|
|
98
107
|
end
|
|
99
108
|
|
|
100
109
|
# 查询指定主机的全部接口。
|
|
101
110
|
def interfaces_for(hostid)
|
|
102
|
-
|
|
111
|
+
host_interfaces.for_host(hostid)
|
|
103
112
|
end
|
|
104
113
|
|
|
105
|
-
# 返回主机的第一个接口 ID;高频业务应优先使用
|
|
114
|
+
# 返回主机的第一个接口 ID;高频业务应优先使用 host_interfaces。
|
|
106
115
|
def get_interface_id(hostid)
|
|
107
116
|
interfaces_for(hostid).first&.fetch("interfaceid", nil)
|
|
108
117
|
end
|
|
@@ -139,95 +148,132 @@ class ZabbixManager
|
|
|
139
148
|
find_host_by_filter(hostid: hostid)
|
|
140
149
|
end
|
|
141
150
|
|
|
151
|
+
# 解析供流量查询和监控表达式使用的唯一主机;只执行读取。
|
|
152
|
+
# 显式 ID 与技术名必须同时匹配,候选名称不能分别指向不同主机。
|
|
153
|
+
# @param reference [String, Array<String>, Hash] 名称/IP/DNS 候选,或同时包含 hostid 和 host 的 Hash
|
|
154
|
+
# @return [Hash] 符号键主机属性,包含 hostid 和技术名 host
|
|
155
|
+
# @raise [Invalid] 本地主机引用无效
|
|
156
|
+
# @raise [ApiError] 主机不存在或当前 API 用户不可见
|
|
157
|
+
# @raise [Conflict] 候选不唯一,或 ID 与技术名不匹配
|
|
158
|
+
# @raise [ProtocolError] API 返回无效的主机身份
|
|
159
|
+
def resolve(reference)
|
|
160
|
+
if reference.is_a?(Hash)
|
|
161
|
+
expected = Monitoring::Validation.hash!(reference, "host")
|
|
162
|
+
Monitoring::Validation.host!(expected)
|
|
163
|
+
found = find_by_id(expected[:hostid])
|
|
164
|
+
else
|
|
165
|
+
candidates = Array.wrap(reference).map do |candidate|
|
|
166
|
+
Monitoring::Validation.text!(candidate, "host candidate").strip
|
|
167
|
+
end
|
|
168
|
+
raise Invalid, "host candidates are required" if candidates.empty?
|
|
169
|
+
|
|
170
|
+
found = find_by_candidates(candidates.uniq)
|
|
171
|
+
end
|
|
172
|
+
raise ApiError, "Zabbix host was not found for the supplied reference" unless found
|
|
173
|
+
|
|
174
|
+
resolved = resolved_host(found)
|
|
175
|
+
if expected && (resolved[:hostid].to_s != expected[:hostid].to_s || resolved[:host] != expected[:host])
|
|
176
|
+
raise Conflict, "hostid and technical host name do not refer to the same Zabbix host"
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
resolved
|
|
180
|
+
end
|
|
181
|
+
|
|
142
182
|
# 批量启用或停用主机。
|
|
143
183
|
# @return [Array<Integer>]
|
|
144
184
|
def set_status(hostids, enabled:)
|
|
145
|
-
|
|
146
|
-
|
|
185
|
+
validate_boolean!(enabled, "enabled")
|
|
186
|
+
ids = normalized_ids(hostids)
|
|
147
187
|
|
|
148
188
|
params = ids.map { |hostid| { hostid: hostid, status: enabled ? 0 : 1 } }
|
|
149
189
|
result = @client.api_request(method: "host.update", params: params)
|
|
150
|
-
|
|
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
|
|
190
|
+
response_ids(result, expected: ids)
|
|
162
191
|
end
|
|
163
192
|
|
|
164
193
|
private
|
|
165
194
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
195
|
+
# 远端身份损坏是协议错误,不能当作调用方输入错误或不存在处理。
|
|
196
|
+
def resolved_host(found)
|
|
197
|
+
resolved = Monitoring::Validation.hash!(found, "Zabbix host")
|
|
198
|
+
Monitoring::Validation.host!(resolved)
|
|
199
|
+
resolved
|
|
200
|
+
rescue Invalid
|
|
201
|
+
raise ProtocolError, "invalid host response: hostid and technical host are required", cause: nil
|
|
202
|
+
end
|
|
171
203
|
|
|
172
|
-
|
|
173
|
-
|
|
204
|
+
def write_templates(operation, host_ids, template_ids, allow_empty: false)
|
|
205
|
+
ids = normalized_ids(host_ids)
|
|
206
|
+
templates = normalized_ids(template_ids, allow_empty: allow_empty)
|
|
207
|
+
result = @client.api_request(
|
|
208
|
+
method: "host.#{operation}",
|
|
209
|
+
params: {
|
|
210
|
+
hosts: ids.map { |id| { hostid: id } },
|
|
211
|
+
templates: templates.map { |id| { templateid: id } }
|
|
212
|
+
}
|
|
213
|
+
)
|
|
214
|
+
response_ids(result, expected: ids)
|
|
215
|
+
end
|
|
174
216
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
|
217
|
+
# 校验创建主机所需的技术名、群组和接口。
|
|
218
|
+
def validate_create_attributes!(attributes)
|
|
219
|
+
raise Invalid, "host is required" if attributes[:host].blank?
|
|
220
|
+
raise Invalid, "groups are required when creating a host" if Array.wrap(attributes[:groups]).empty?
|
|
221
|
+
raise Invalid, "interfaces are required when creating a host" if Array.wrap(attributes[:interfaces]).empty?
|
|
186
222
|
|
|
187
|
-
|
|
188
|
-
|
|
223
|
+
attributes[:interfaces] = host_interfaces.validate_for_create(attributes[:interfaces])
|
|
224
|
+
end
|
|
189
225
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
raise Conflict, "interface endpoint #{candidate} belongs to multiple hosts" if matches.length > 1
|
|
226
|
+
# 使用精确过滤查询唯一主机。
|
|
227
|
+
def find_host_by_filter(filter)
|
|
228
|
+
result = @client.api_request(
|
|
229
|
+
method: "host.get",
|
|
230
|
+
params: {
|
|
231
|
+
output: %w[hostid host name status],
|
|
232
|
+
filter: filter,
|
|
233
|
+
selectInterfaces: %w[interfaceid ip dns]
|
|
234
|
+
}
|
|
235
|
+
)
|
|
236
|
+
raise Conflict, "host lookup is ambiguous for #{filter.inspect}" if result.length > 1
|
|
202
237
|
|
|
203
|
-
|
|
204
|
-
|
|
238
|
+
response_identifier(result.first["hostid"]) if result.first
|
|
239
|
+
result.first
|
|
240
|
+
end
|
|
205
241
|
|
|
206
|
-
|
|
207
|
-
|
|
242
|
+
# 按 IP 和 DNS 端点查询唯一主机接口。
|
|
243
|
+
def find_interface_by_endpoint(candidate)
|
|
244
|
+
matches = []
|
|
245
|
+
%i[ip dns].each do |field|
|
|
208
246
|
result = @client.api_request(
|
|
209
|
-
method: "
|
|
210
|
-
params: {
|
|
211
|
-
output: ["hostid"],
|
|
212
|
-
filter: filter
|
|
213
|
-
}
|
|
247
|
+
method: "hostinterface.get",
|
|
248
|
+
params: { filter: { field => candidate }, output: ["hostid"] }
|
|
214
249
|
)
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
result.first&.fetch("hostid", nil)
|
|
250
|
+
matches.concat(result)
|
|
218
251
|
end
|
|
252
|
+
matches.uniq! { |interface| interface.fetch("hostid") }
|
|
253
|
+
raise Conflict, "interface endpoint #{candidate} belongs to multiple hosts" if matches.length > 1
|
|
219
254
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
value = result.fetch(key_name).first
|
|
223
|
-
value && value.to_i
|
|
224
|
-
end
|
|
255
|
+
matches.first
|
|
256
|
+
end
|
|
225
257
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
258
|
+
# 只查询唯一主机 ID。
|
|
259
|
+
def find_host_id(filter)
|
|
260
|
+
result = @client.api_request(
|
|
261
|
+
method: "host.get",
|
|
262
|
+
params: {
|
|
263
|
+
output: ["hostid"],
|
|
264
|
+
filter: filter
|
|
265
|
+
}
|
|
266
|
+
)
|
|
267
|
+
raise Conflict, "host lookup is ambiguous for #{filter.inspect}" if result.length > 1
|
|
268
|
+
|
|
269
|
+
response_identifier(result.first["hostid"]).to_s if result.first
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# 延迟构建接口模块并复用当前客户端。
|
|
273
|
+
# @return [HostInterfaces]
|
|
274
|
+
# @api private
|
|
275
|
+
def host_interfaces
|
|
276
|
+
@host_interfaces ||= HostInterfaces.new(@client)
|
|
277
|
+
end
|
|
232
278
|
end
|
|
233
279
|
end
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class ZabbixManager
|
|
4
|
-
|
|
4
|
+
# Web 场景以 hostid + name 定位;步骤定义及执行凭据由调用方提供。
|
|
5
|
+
class HttpTests < Resource
|
|
5
6
|
# 返回 Web 场景对象对应的 Zabbix API 方法前缀。
|
|
6
7
|
#
|
|
7
8
|
# @return [String]
|
|
@@ -9,13 +10,6 @@ class ZabbixManager
|
|
|
9
10
|
"httptest"
|
|
10
11
|
end
|
|
11
12
|
|
|
12
|
-
# 返回 Web 场景用于业务识别的字段名。
|
|
13
|
-
#
|
|
14
|
-
# @return [String]
|
|
15
|
-
def identify
|
|
16
|
-
"name"
|
|
17
|
-
end
|
|
18
|
-
|
|
19
13
|
# 返回创建 Web 场景时使用的默认步骤列表。
|
|
20
14
|
#
|
|
21
15
|
# @return [Hash] Web 场景默认属性
|