Class: ZabbixManager::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/zabbix_manager/client.rb

Constant Summary collapse

SUPPORTED_MAJOR_VERSIONS =
(4..7).freeze
UNAUTHENTICATED_METHODS =
%w[apiinfo.version user.login].freeze
BLANK_NORMALIZED_OPTIONS =
%i[api_token username user password http_user http_password].freeze
UPSERT_MUTEX_STRIPES =
64
DEFAULT_UNCERTAIN_WRITE_DELAYS =
[0, 0.25, 1, 2].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Client

构建按服务端版本选择认证方式并复用 HTTP 会话的客户端。



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
# File 'lib/zabbix_manager/client.rb', line 19

def initialize(options = {})
  @raw_options = normalize_options(options)
  validate_credentials!
  validate_uncertain_write_delays!
  @sanitized_options = sanitize_options(@raw_options).freeze
  @options = @sanitized_options
  @logger = build_logger
  @id_mutex = Mutex.new
  @next_id = 0
  @upsert_mutexes = Array.new(UPSERT_MUTEX_STRIPES) { Mutex.new }
  validate_upsert_lock!
  @transport = HttpTransport.new(@raw_options)
  validate_token_transport!
  @api_version = api_version
  validate_api_version!
  validate_http_auth_compatibility!
  @credential_type = @raw_options[:api_token] ? :api_token : :user_session
  @auth_token = @raw_options[:api_token] || auth
  log(:info, "client.connected", version: @api_version, credential_type: @credential_type)
  log(:warn, "tls.verification_disabled", url: @transport.safe_url) if insecure_https?
  log(:warn, "token.sent_over_http", url: @transport.safe_url) if insecure_token_transport?
rescue StandardError
  @transport&.close
  raise
end

Instance Attribute Details

#loggerObject (readonly)

Returns the value of attribute logger.



14
15
16
# File 'lib/zabbix_manager/client.rb', line 14

def logger
  @logger
end

#optionsObject (readonly)

Returns the value of attribute options.



14
15
16
# File 'lib/zabbix_manager/client.rb', line 14

def options
  @options
end

#sanitized_optionsObject (readonly)

Returns the value of attribute sanitized_options.



14
15
16
# File 'lib/zabbix_manager/client.rb', line 14

def sanitized_options
  @sanitized_options
end

Instance Method Details

#_request(body) ⇒ Object

解析 JSON-RPC 响应并统一转换协议错误。

Returns:

  • (Object)


105
106
107
108
109
110
111
112
113
114
115
# File 'lib/zabbix_manager/client.rb', line 105

def _request(body)
  parsed = JSON.parse(http_request(body))
  raise_api_error(parsed, body) if parsed["error"]
  unless parsed.key?("result")
    raise ApiError.new("Invalid JSON-RPC response: missing result", LogSanitizer.sanitize(parsed))
  end

  parsed["result"]
rescue JSON::ParserError => e
  raise ApiError, "Invalid JSON response: #{e.message}"
end

#api_request(body) ⇒ Object Also known as: manager_request

执行 Zabbix JSON-RPC 方法并记录脱敏耗时日志。

Returns:

  • (Object)

    decoded result value



129
130
131
132
133
134
135
136
137
138
139
# File 'lib/zabbix_manager/client.rb', line 129

def api_request(body)
  method = fetch_body_value(body, :method)
  started_at = monotonic_time
  log(:debug, "request.started", method: method)
  result = _request(message_json(body))
  log(:info, "request.completed", method: method, duration_ms: elapsed_ms(started_at))
  result
rescue StandardError => e
  log(:warn, "request.failed", method: method, duration_ms: elapsed_ms(started_at), error: e.class.name)
  raise
end

#api_versionString

返回首次查询后缓存的远端 Zabbix API 版本。

Returns:

  • (String)


55
56
57
# File 'lib/zabbix_manager/client.rb', line 55

def api_version
  @api_version ||= api_request(method: "apiinfo.version", params: {})
end

#authString

使用用户名和密码创建 Zabbix 会话。

Returns:

  • (String)

    Zabbix session token



62
63
64
65
66
67
68
69
70
# File 'lib/zabbix_manager/client.rb', line 62

def auth
  api_request(
    method: "user.login",
    params: {
       => username,
      password: @raw_options[:password]
    }
  )
end

#closeObject

关闭持久 HTTP 连接。

Returns:

  • (Object)


156
157
158
# File 'lib/zabbix_manager/client.rb', line 156

def close
  @transport.close
end

#debug?Boolean

判断是否启用调试日志。

Returns:

  • (Boolean)


75
76
77
# File 'lib/zabbix_manager/client.rb', line 75

def debug?
  @raw_options[:debug] == true
end

#http_request(body) ⇒ String

通过持久连接发送请求,并按版本加入 Bearer 令牌。

Returns:

  • (String)

    raw response body



97
98
99
100
# File 'lib/zabbix_manager/client.rb', line 97

def http_request(body)
  method = JSON.parse(body).fetch("method")
  @transport.request(body, bearer_token: bearer_token_for(method))
end

#idInteger

分配线程安全的 JSON-RPC 请求编号。

Returns:

  • (Integer)


48
49
50
# File 'lib/zabbix_manager/client.rb', line 48

def id
  @id_mutex.synchronize { @next_id += 1 }
end

#inspectString

返回不包含凭据的客户端摘要,避免对象检查时泄漏配置

Returns:

  • (String)


174
175
176
# File 'lib/zabbix_manager/client.rb', line 174

def inspect
  "#<#{self.class} options=#{@sanitized_options.inspect}>"
end

#log(level, event, data = {}) ⇒ void

This method returns an undefined value.

输出结构化且已脱敏的客户端事件。



163
164
165
166
167
168
169
# File 'lib/zabbix_manager/client.rb', line 163

def log(level, event, data = {})
  return unless @logger

  sanitized = LogSanitizer.sanitize(data)
  details = sanitized.map { |key, value| "#{key}=#{value.inspect}" }.join(" ")
  @logger.public_send(level, ["[zabbix_manager]", event, details].reject(&:empty?).join(" "))
end

#logoutObject

注销用户名会话并关闭持久连接;API 令牌无需远端注销。

Returns:

  • (Object)

    logout result



145
146
147
148
149
150
151
# File 'lib/zabbix_manager/client.rb', line 145

def logout
  result = @credential_type == :user_session && @auth_token ? api_request(method: "user.logout", params: []) : true
  result
ensure
  @auth_token = nil
  close
end

#message_json(body) ⇒ String

序列化 JSON-RPC 请求并按版本加入请求体认证。

Returns:

  • (String)


82
83
84
85
86
87
88
89
90
91
92
# File 'lib/zabbix_manager/client.rb', line 82

def message_json(body)
  method = fetch_body_value(body, :method)
  message = {
    method: method,
    params: fetch_body_value(body, :params) || {},
    id: id,
    jsonrpc: "2.0"
  }
  message[:auth] = @auth_token if @auth_token && body_authentication?(method)
  JSON.generate(message)
end

#pretty_body(body) ⇒ String

格式化请求摘要并隐藏全部参数值。

Returns:

  • (String)


120
121
122
123
124
# File 'lib/zabbix_manager/client.rb', line 120

def pretty_body(body)
  parsed = JSON.parse(body)
  parsed["params"] = LogSanitizer::REDACTED if parsed.key?("params")
  JSON.pretty_generate(LogSanitizer.sanitize(parsed))
end

#with_upsert_lock(key, &block) ⇒ Object

以业务键串行执行幂等写入,并可接入跨进程锁实现

Returns:

  • (Object)

    代码块结果



181
182
183
184
185
186
187
# File 'lib/zabbix_manager/client.rb', line 181

def with_upsert_lock(key, &block)
  mutex = @upsert_mutexes[key.hash % UPSERT_MUTEX_STRIPES]
  mutex.synchronize do
    coordinator = @raw_options[:upsert_lock]
    coordinator ? coordinator.call(key, &block) : yield
  end
end