rubix 0.0.1
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.
- data/LICENSE +20 -0
- data/README.rdoc +262 -0
- data/VERSION +1 -0
- data/bin/zabbix_api +60 -0
- data/bin/zabbix_pipe +77 -0
- data/lib/rubix.rb +42 -0
- data/lib/rubix/connection.rb +111 -0
- data/lib/rubix/examples/es_monitor.rb +130 -0
- data/lib/rubix/examples/hbase_monitor.rb +87 -0
- data/lib/rubix/examples/mongo_monitor.rb +125 -0
- data/lib/rubix/log.rb +70 -0
- data/lib/rubix/model.rb +56 -0
- data/lib/rubix/models/application.rb +76 -0
- data/lib/rubix/models/host.rb +127 -0
- data/lib/rubix/models/host_group.rb +74 -0
- data/lib/rubix/models/item.rb +122 -0
- data/lib/rubix/models/template.rb +81 -0
- data/lib/rubix/monitor.rb +167 -0
- data/lib/rubix/monitors/chef_monitor.rb +82 -0
- data/lib/rubix/monitors/cluster_monitor.rb +84 -0
- data/lib/rubix/response.rb +124 -0
- data/lib/rubix/sender.rb +301 -0
- data/spec/rubix/connection_spec.rb +43 -0
- data/spec/rubix/models/host_group_spec.rb +56 -0
- data/spec/rubix/monitor_spec.rb +81 -0
- data/spec/rubix/monitors/chef_monitor_spec.rb +11 -0
- data/spec/rubix/monitors/cluster_monitor_spec.rb +11 -0
- data/spec/rubix/response_spec.rb +35 -0
- data/spec/rubix/sender_spec.rb +9 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/support/response_helper.rb +17 -0
- metadata +140 -0
data/lib/rubix/log.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Rubix
|
4
|
+
|
5
|
+
def self.logger= l
|
6
|
+
@logger = l
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.logger
|
10
|
+
return @logger unless @logger.nil?
|
11
|
+
@logger = default_logger
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.default_logger
|
15
|
+
severity = Logger::INFO
|
16
|
+
file = $stdout
|
17
|
+
|
18
|
+
if defined?(Settings) && Settings[:log_level]
|
19
|
+
begin
|
20
|
+
severity_name = Settings[:log_level].to_s.upcase
|
21
|
+
severity = Logger.const_get(severity_name)
|
22
|
+
rescue NameError => e
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
if defined?(Settings) && Settings[:log]
|
27
|
+
begin
|
28
|
+
file = Settings[:log]
|
29
|
+
rescue NameError => e
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
@logger = Logger.new(file)
|
34
|
+
@logger.level = severity
|
35
|
+
@logger
|
36
|
+
end
|
37
|
+
|
38
|
+
module Logs
|
39
|
+
|
40
|
+
def log_name
|
41
|
+
@log_name
|
42
|
+
end
|
43
|
+
|
44
|
+
def debug *args
|
45
|
+
return unless Rubix.logger
|
46
|
+
Rubix.logger.log(Logger::DEBUG, args.join(' '), log_name)
|
47
|
+
end
|
48
|
+
|
49
|
+
def info *args
|
50
|
+
return unless Rubix.logger
|
51
|
+
Rubix.logger.log(Logger::INFO, args.join(' '), log_name)
|
52
|
+
end
|
53
|
+
|
54
|
+
def warn *args
|
55
|
+
return unless Rubix.logger
|
56
|
+
Rubix.logger.log(Logger::WARN, args.join(' '), log_name)
|
57
|
+
end
|
58
|
+
|
59
|
+
def error *args
|
60
|
+
return unless Rubix.logger
|
61
|
+
Rubix.logger.log(Logger::ERROR, args.join(' '), log_name)
|
62
|
+
end
|
63
|
+
|
64
|
+
def fatal *args
|
65
|
+
return unless Rubix.logger
|
66
|
+
Rubix.logger.log(Logger::FATAL, args.join(' '), log_name)
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
data/lib/rubix/model.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rubix/log'
|
2
|
+
|
3
|
+
module Rubix
|
4
|
+
|
5
|
+
# It might be worth using ActiveModel -- but maybe not. The goal is
|
6
|
+
# to keep dependencies low while still retaining expressiveness.
|
7
|
+
class Model
|
8
|
+
|
9
|
+
attr_accessor :properties, :id
|
10
|
+
|
11
|
+
include Logs
|
12
|
+
|
13
|
+
def initialize properties={}
|
14
|
+
@properties = properties
|
15
|
+
@id = properties[:id]
|
16
|
+
@log_name = self.class.to_s.split('::').last
|
17
|
+
end
|
18
|
+
|
19
|
+
def loaded?
|
20
|
+
@loaded
|
21
|
+
end
|
22
|
+
|
23
|
+
def load
|
24
|
+
raise NotImplementedError.new("Override the 'load' method in a subclass.")
|
25
|
+
end
|
26
|
+
|
27
|
+
def exists?
|
28
|
+
load unless loaded?
|
29
|
+
@exists
|
30
|
+
end
|
31
|
+
|
32
|
+
def register
|
33
|
+
exists? ? update : create
|
34
|
+
end
|
35
|
+
|
36
|
+
def unregister
|
37
|
+
destroy if exists?
|
38
|
+
end
|
39
|
+
|
40
|
+
def request method, params
|
41
|
+
Rubix.connection && Rubix.connection.request(method, params)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.find_by_id id
|
45
|
+
instance = new(:id => id)
|
46
|
+
instance if instance.exists?
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.find_by_name name
|
50
|
+
instance = new(:name => name)
|
51
|
+
instance if instance.exists?
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Rubix
|
2
|
+
|
3
|
+
class Application < Model
|
4
|
+
|
5
|
+
attr_accessor :name, :host
|
6
|
+
|
7
|
+
def initialize properties={}
|
8
|
+
super(properties)
|
9
|
+
@name = properties[:name]
|
10
|
+
@host = properties[:host]
|
11
|
+
end
|
12
|
+
|
13
|
+
def params
|
14
|
+
{
|
15
|
+
:name => name,
|
16
|
+
:hostid => host.id
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def log_name
|
21
|
+
"APP #{name || id}@#{host.name}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def load
|
25
|
+
response = request('application.get', 'hostids' => [host.id], 'filter' => {'name' => name, 'id' => id}, "output" => "extend")
|
26
|
+
case
|
27
|
+
when response.has_data?
|
28
|
+
app = response.first
|
29
|
+
@id = app['applicationid'].to_i
|
30
|
+
@name = app['name']
|
31
|
+
@exists = true
|
32
|
+
@loaded = true
|
33
|
+
when response.success?
|
34
|
+
@exists = false
|
35
|
+
@loaded = true
|
36
|
+
else
|
37
|
+
error("Could not load: #{response.error_message}")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def create
|
42
|
+
response = request('application.create', params)
|
43
|
+
if response.has_data?
|
44
|
+
@id = response['applicationids'].first.to_i
|
45
|
+
@exists = true
|
46
|
+
info("Created")
|
47
|
+
else
|
48
|
+
error("Could not create: #{response.error_message}")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def update
|
53
|
+
# noop
|
54
|
+
info("Updated")
|
55
|
+
end
|
56
|
+
|
57
|
+
def destroy
|
58
|
+
response = request('application.delete', [id])
|
59
|
+
case
|
60
|
+
when response.has_data? && response['applicationids'].first.to_i == id
|
61
|
+
info("Deleted")
|
62
|
+
when response.zabbix_error? && response.error_message =~ /does not exist/i
|
63
|
+
# was never there...
|
64
|
+
else
|
65
|
+
error("Could not delete: #{response.error_message}.")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.find_or_create_by_name_and_host name, host
|
70
|
+
new(:name => name, :host => host).tap do |app|
|
71
|
+
app.create unless app.exists?
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module Rubix
|
2
|
+
|
3
|
+
class Host < Model
|
4
|
+
|
5
|
+
# The IP for a Host that not supposed to be polled by the Zabbix
|
6
|
+
# server.
|
7
|
+
BLANK_IP = '0.0.0.0'
|
8
|
+
|
9
|
+
# The default port.
|
10
|
+
DEFAULT_PORT = 10050
|
11
|
+
|
12
|
+
attr_accessor :name, :ip, :port, :profile, :status, :host_groups, :templates
|
13
|
+
|
14
|
+
#
|
15
|
+
# Initialization and properties.
|
16
|
+
#
|
17
|
+
def initialize properties={}
|
18
|
+
super(properties)
|
19
|
+
@name = properties[:name]
|
20
|
+
@ip = properties[:ip]
|
21
|
+
@port = properties[:port]
|
22
|
+
@profile = properties[:profile]
|
23
|
+
@status = properties[:status]
|
24
|
+
@host_groups = properties[:host_groups]
|
25
|
+
@templates = properties[:templates]
|
26
|
+
end
|
27
|
+
|
28
|
+
def log_name
|
29
|
+
"HOST #{name || id}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def params
|
33
|
+
{}.tap do |hp|
|
34
|
+
hp['host'] = name
|
35
|
+
|
36
|
+
hp['profile'] = profile if profile
|
37
|
+
hp['status'] = status if status
|
38
|
+
|
39
|
+
if ip
|
40
|
+
hp['ip'] = ip
|
41
|
+
hp['useip'] = true
|
42
|
+
hp['port'] = port || self.class::DEFAULT_PORT
|
43
|
+
else
|
44
|
+
hp['ip'] = self.class::BLANK_IP
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# Actions
|
52
|
+
#
|
53
|
+
|
54
|
+
def validate
|
55
|
+
raise ValidationError.new("A host must have at least one host group.") if host_groups.nil? || host_groups.empty?
|
56
|
+
end
|
57
|
+
|
58
|
+
def load
|
59
|
+
response = request('host.get', 'filter' => {'host' => name}, 'select_groups' => 'refer', 'selectParentTemplates' => 'refer', 'select_profile' => 'refer', 'output' => 'extend')
|
60
|
+
case
|
61
|
+
when response.has_data?
|
62
|
+
host = response.result.first
|
63
|
+
@id = host['hostid'].to_i
|
64
|
+
@host_groups = host['groups'].map { |group| HostGroup.new(:id => group['groupid'].to_i) }
|
65
|
+
@templates = host['parentTemplates'].map { |template| Template.new((template['templateid'] || template['hostid']).to_i) }
|
66
|
+
@profile = host['profile']
|
67
|
+
@port = host['port']
|
68
|
+
@exists = true
|
69
|
+
@loaded = true
|
70
|
+
when response.success?
|
71
|
+
@exists = false
|
72
|
+
@loaded = true
|
73
|
+
else
|
74
|
+
error("Could not load: #{response.error_message}")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def create
|
79
|
+
validate
|
80
|
+
|
81
|
+
host_group_ids = (host_groups || []).map { |g| { 'groupid' => g.id } }
|
82
|
+
template_ids = (templates || []).map { |t| { 'templateid' => t.id } }
|
83
|
+
response = request('host.create', params.merge('groups' => host_group_ids, 'templates' => template_ids))
|
84
|
+
|
85
|
+
if response.has_data?
|
86
|
+
@exists = true
|
87
|
+
@id = response.result['hostids'].first.to_i
|
88
|
+
info("Created")
|
89
|
+
else
|
90
|
+
error("Could not create: #{response.error_message}.")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def update
|
95
|
+
validate
|
96
|
+
response = request('host.update', params.merge('hostid' => id))
|
97
|
+
if response.has_data?
|
98
|
+
info("Updated")
|
99
|
+
else
|
100
|
+
error("Could not update: #{response.error_message}.")
|
101
|
+
end
|
102
|
+
mass_update_templates_and_host_groups
|
103
|
+
end
|
104
|
+
|
105
|
+
def mass_update_templates_and_host_groups
|
106
|
+
response = request('host.massUpdate', { 'groupids' => (host_groups || []).map(&:id), 'templateids' => (templates || []).map(&:id) })
|
107
|
+
if response.has_data?
|
108
|
+
info("Updated templates and host groups")
|
109
|
+
else
|
110
|
+
error("Could not update all templates and/or host groups: #{response.error_message}")
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def destroy
|
115
|
+
response = request('host.delete', [{'hostid' => id}])
|
116
|
+
case
|
117
|
+
when response.has_data? && response.result['hostids'].first.to_i == id
|
118
|
+
info("Deleted")
|
119
|
+
when response.error_message =~ /does not exist/i
|
120
|
+
# was never there...
|
121
|
+
else
|
122
|
+
error("Could not delete: #{response.error_message}")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Rubix
|
2
|
+
|
3
|
+
class HostGroup < Model
|
4
|
+
|
5
|
+
attr_accessor :name, :host_ids
|
6
|
+
|
7
|
+
def initialize properties={}
|
8
|
+
super(properties)
|
9
|
+
@name = properties[:name]
|
10
|
+
end
|
11
|
+
|
12
|
+
def log_name
|
13
|
+
"GROUP #{name || id}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def load
|
17
|
+
response = request('hostgroup.get', 'filter' => {'groupid' => id, 'name' => name}, 'select_hosts' => 'refer', 'output' => 'extend')
|
18
|
+
case
|
19
|
+
when response.has_data?
|
20
|
+
@id = response.first['groupid'].to_i
|
21
|
+
@name = response.first['name']
|
22
|
+
@host_ids = response.first['hosts'].map { |host_info| host_info['hostid'].to_i }
|
23
|
+
@exists = true
|
24
|
+
@loaded = true
|
25
|
+
when response.success?
|
26
|
+
@exists = false
|
27
|
+
@loaded = true
|
28
|
+
else
|
29
|
+
error("Could not load: #{response.error_message}")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def create
|
34
|
+
response = request('hostgroup.create', [{'name' => name}])
|
35
|
+
if response.has_data?
|
36
|
+
@id = response['groupids'].first.to_i
|
37
|
+
@exists = true
|
38
|
+
@loaded = true
|
39
|
+
info("Created")
|
40
|
+
else
|
41
|
+
error("Could not create: #{response.error_message}")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def update
|
46
|
+
# noop
|
47
|
+
info("Updated")
|
48
|
+
end
|
49
|
+
|
50
|
+
def destroy
|
51
|
+
response = request('hostgroup.delete', [{'groupid' => id}])
|
52
|
+
case
|
53
|
+
when response.has_data? && response['groupids'].first.to_i == id
|
54
|
+
info("Deleted")
|
55
|
+
when response.zabbix_error? && response.error_message =~ /does not exist/i
|
56
|
+
# was never there...
|
57
|
+
else
|
58
|
+
error("Could not delete: #{response.error_message}.")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def contains? host
|
63
|
+
return unless exists?
|
64
|
+
host_ids.include?(host.id)
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.find_or_create_by_name name
|
68
|
+
new(:name => name).tap do |group|
|
69
|
+
group.create unless group.exists?
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module Rubix
|
2
|
+
|
3
|
+
class Item < Model
|
4
|
+
|
5
|
+
# The numeric code for a Zabbix item of type 'Zabbix trapper'. The
|
6
|
+
# item must have this type in order for the Zabbix server to listen
|
7
|
+
# and accept data submitted by +zabbix_sender+.
|
8
|
+
TRAPPER_TYPE = 2.freeze
|
9
|
+
|
10
|
+
# The numeric codes for the value types of a Zabbix item. This Hash
|
11
|
+
# is used by ZabbixPipe#value_code_from_value to dynamically set the
|
12
|
+
# type of a value when creating a new Zabbix item.
|
13
|
+
VALUE_CODES = {
|
14
|
+
:float => 0, # Numeric (float)
|
15
|
+
:character => 1, # Character
|
16
|
+
:log_line => 2, # Log
|
17
|
+
:unsigned_int => 3, # Numeric (unsigned)
|
18
|
+
:text => 4 # Text
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
VALUE_NAMES = {
|
22
|
+
0 => :float, # Numeric (float)
|
23
|
+
1 => :character, # Character
|
24
|
+
2 => :log_line, # Log
|
25
|
+
3 => :unsigned_int, # Numeric (unsigned)
|
26
|
+
4 => :text # Text
|
27
|
+
}.freeze
|
28
|
+
|
29
|
+
attr_accessor :host, :applications, :key, :description, :value_type
|
30
|
+
|
31
|
+
def initialize properties={}
|
32
|
+
super(properties)
|
33
|
+
@host = properties[:host]
|
34
|
+
@key = properties[:key]
|
35
|
+
@description = properties[:description]
|
36
|
+
@value_type = properties[:value_type]
|
37
|
+
@applications = properties[:applications]
|
38
|
+
end
|
39
|
+
|
40
|
+
def log_name
|
41
|
+
"ITEM #{key}@#{host.name}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def params
|
45
|
+
{
|
46
|
+
:hostid => host.id,
|
47
|
+
:description => (description || 'Unknown'),
|
48
|
+
:type => self.class::TRAPPER_TYPE,
|
49
|
+
:key_ => key,
|
50
|
+
:value_type => self.class::VALUE_CODES[value_type],
|
51
|
+
}.tap do |p|
|
52
|
+
p[:applications] = applications.map(&:id) if applications
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def load
|
57
|
+
response = request('item.get', 'host' => host.name, 'filter' => {'key_' => key, 'id' => id}, "output" => "extend")
|
58
|
+
case
|
59
|
+
when response.has_data?
|
60
|
+
item = response.first
|
61
|
+
@id = item['itemid'].to_i
|
62
|
+
@host = Host.new(:id => item['hostid'])
|
63
|
+
@description = item['description']
|
64
|
+
@value_type = self.class::VALUE_NAMES[item['value_type']] # it's actually a 'code' that's returned...
|
65
|
+
@key = item['key_']
|
66
|
+
@exists = true
|
67
|
+
@loaded = true
|
68
|
+
when response.success?
|
69
|
+
@exists = false
|
70
|
+
@loaded = true
|
71
|
+
else
|
72
|
+
error("Could not load: #{response.error_message}")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def create
|
77
|
+
response = request('item.create', params)
|
78
|
+
if response.has_data?
|
79
|
+
@id = response['itemids'].first.to_i
|
80
|
+
@exists = true
|
81
|
+
info("Created")
|
82
|
+
else
|
83
|
+
error("Could not create: #{response.error_message}.")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def update
|
88
|
+
# noop
|
89
|
+
info("Updated")
|
90
|
+
end
|
91
|
+
|
92
|
+
def destroy
|
93
|
+
response = request('item.delete', [id])
|
94
|
+
case
|
95
|
+
when response.has_data? && response['itemids'].first.to_i == id
|
96
|
+
info("Deleted")
|
97
|
+
when response.zabbix_error? && response.error_message =~ /does not exist/i
|
98
|
+
# was never there...
|
99
|
+
else
|
100
|
+
error("Could not delete: #{response.error_message}.")
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Return the +value_type+ name (:float, :text, &c.) for a Zabbix
|
105
|
+
# item's value type by examining the given +value+.
|
106
|
+
def self.value_type_from_value value
|
107
|
+
case
|
108
|
+
when value =~ /\d+/ then :unsigned_int
|
109
|
+
when value =~ /-?[\d\.]+/ then :float
|
110
|
+
when value.include?("\n") then :text
|
111
|
+
else :character
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Return the +value_type+'s numeric code for a Zabbix item's value
|
116
|
+
# type by examining the given +value+.
|
117
|
+
def self.value_code_from_value value
|
118
|
+
self::VALUE_CODES[value_type_from_value(value)]
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|