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.
@@ -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
@@ -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