rubix 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.1
1
+ 0.4.0
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ RUBIX_ROOT = File.expand_path('../../../../lib', __FILE__)
4
+ $: << RUBIX_ROOT unless $:.include?(RUBIX_ROOT)
5
+
6
+ require 'rubix'
7
+ require 'net/http'
8
+ require 'timeout'
9
+
10
+ class HttpAvailabilityMonitor < Rubix::Monitor
11
+
12
+ include Rubix::ChefMonitor
13
+
14
+ def chef_node
15
+ begin
16
+ @chef_node ||= chef_node_from_node_name(Chef::Config[:node_name])
17
+ rescue => e
18
+ puts "Could not find a Chef node named #{Chef::Config[:node_name]} -- are you sure your Chef settings are correct?"
19
+ end
20
+ end
21
+
22
+ def measure
23
+ begin
24
+ timeout(1) do
25
+ if Net::HTTP.get_response(URI.parse("http://#{chef_node['fqdn']}/")).code.to_i == 200
26
+ write [host.name, 'webserver.available', 1]
27
+ return
28
+ end
29
+ end
30
+ rescue => e
31
+ puts e.message
32
+ end
33
+ write [host.name, 'webserver.available', 0]
34
+ end
35
+ end
36
+
37
+ HttpAvailabilityMonitor.run if $0 == __FILE__
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ RUBIX_ROOT = File.expand_path('../../../../lib', __FILE__)
4
+ $: << RUBIX_ROOT unless $:.include?(RUBIX_ROOT)
5
+
6
+ require 'rubix'
7
+ require 'net/http'
8
+ require 'timeout'
9
+
10
+ class HttpAvailabilityMonitor < Rubix::Monitor
11
+
12
+ include Rubix::ZabbixMonitor
13
+ include Rubix::ClusterMonitor
14
+
15
+ def host_group_name
16
+ 'Zabbix servers'
17
+ end
18
+
19
+ def measure_cluster cluster_name
20
+ hosts_by_cluster[cluster_name].each do |host|
21
+ measure_host(host)
22
+ end
23
+ write [cluster_name, 'something', 1]
24
+ end
25
+
26
+ def measure_host host
27
+ begin
28
+ timeout(1) do
29
+ if Net::HTTP.get_response(URI.parse("http://#{host.ip}/")).code.to_i == 200
30
+ write [host.name, 'webserver.available', 1]
31
+ return
32
+ end
33
+ end
34
+ rescue => e
35
+ puts e.message
36
+ end
37
+ write [host.name, 'webserver.available', 0]
38
+ end
39
+
40
+ end
41
+
42
+ HttpAvailabilityMonitor.run if $0 == __FILE__
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ RUBIX_ROOT = File.expand_path('../../../../lib', __FILE__)
4
+ $: << RUBIX_ROOT unless $:.include?(RUBIX_ROOT)
5
+
6
+ require 'rubix'
7
+ require 'net/http'
8
+ require 'timeout'
9
+
10
+ class HttpAvailabilityMonitor < Rubix::Monitor
11
+
12
+ include Rubix::ZabbixMonitor
13
+
14
+ def host_group_name
15
+ 'Zabbix servers'
16
+ end
17
+
18
+ def measure
19
+ hosts.each do |host|
20
+ measure_host(host)
21
+ end
22
+ end
23
+
24
+ def measure_host host
25
+ begin
26
+ timeout(1) do
27
+ if Net::HTTP.get_response(URI.parse("http://#{host.ip}/")).code.to_i == 200
28
+ write [host.name, 'webserver.available', 1]
29
+ return
30
+ end
31
+ end
32
+ rescue => e
33
+ puts e.message
34
+ end
35
+ write [host.name, 'webserver.available', 0]
36
+ end
37
+ end
38
+
39
+ HttpAvailabilityMonitor.run if $0 == __FILE__
@@ -362,5 +362,20 @@ module Rubix
362
362
  end
363
363
  end
364
364
 
365
+ def self.list ids
366
+ return [] if ids.nil? || ids.empty?
367
+ response = request("#{zabbix_name}.get", get_params.merge((id_field + 's') => ids))
368
+ case
369
+ when response.has_data?
370
+ response.result.map do |obj|
371
+ build(obj)
372
+ end
373
+ when response.success?
374
+ []
375
+ else
376
+ error("Error listing Zabbix #{resource_name}s: #{response.error_message}")
377
+ end
378
+ end
379
+
365
380
  end
366
381
  end
@@ -1,5 +1,6 @@
1
1
  module Rubix
2
2
  autoload :Monitor, 'rubix/monitors/monitor'
3
3
  autoload :ChefMonitor, 'rubix/monitors/chef_monitor'
4
+ autoload :ZabbixMonitor, 'rubix/monitors/zabbix_monitor'
4
5
  autoload :ClusterMonitor, 'rubix/monitors/cluster_monitor'
5
6
  end
@@ -1,6 +1,6 @@
1
1
  module Rubix
2
- # A generic monitor class for constructing Zabbix monitors that need
3
- # to talk to Chef servers.
2
+
3
+ # A module that lets monitors talk to Chef servers.
4
4
  #
5
5
  # This class handles the low-level logic of connecting to Chef and
6
6
  # parsing results from searches.
@@ -15,8 +15,10 @@ module Rubix
15
15
  #
16
16
  # require 'net/http'
17
17
  #
18
- # class WebserverMonitor < Rubix::ChefMonitor
18
+ # class WebserverMonitor < Rubix::Monitor
19
19
  #
20
+ # include Rubix::ChefMonitor
21
+ #
20
22
  # def measure
21
23
  # webserver = chef_node_from_node_name('webserver')
22
24
  # begin
@@ -31,16 +33,13 @@ module Rubix
31
33
  # end
32
34
  #
33
35
  # WebserverMonitor.run if $0 == __FILE__
34
- #
35
- # See documentation for Rubix::Monitor to understand how to run this
36
- # script.
37
- class ChefMonitor < Monitor
36
+ module ChefMonitor
38
37
 
39
- def self.default_settings
40
- super().tap do |s|
41
- s.define :chef_server_url, :description => "Chef server URL" , :required => true
42
- s.define :chef_node_name, :description => "Node name to identify to Chef server", :required => true
43
- s.define :chef_client_key, :description => "Path to Chef client private key", :required => true
38
+ def self.included klass
39
+ klass.default_settings.tap do |s|
40
+ s.define :chef_server_url, :description => "Chef server URL" , :required => true, :default => 'http://localhost'
41
+ s.define :chef_node_name, :description => "Node name to identify to Chef server", :required => true, :default => ENV["HOSTNAME"]
42
+ s.define :chef_client_key, :description => "Path to Chef client private key", :required => true, :default => '/etc/chef/client.pem'
44
43
  end
45
44
  end
46
45
 
@@ -67,11 +66,16 @@ module Rubix
67
66
  results.first.first
68
67
  end
69
68
 
70
- def chef_node_name_from_ip ip
69
+ def chef_node_from_ip ip
71
70
  return if ip.nil? || ip.empty?
72
71
  results = search_nodes("ipaddress:#{ip} OR fqdn:#{ip}")
73
72
  return unless results.first.size > 0
74
- results.first.first['node_name']
73
+ results.first.first
74
+ end
75
+
76
+ def chef_node_name_from_ip ip
77
+ node = chef_node_from_ip(ip)
78
+ return node['node_name'] if node
75
79
  end
76
80
 
77
81
  end
@@ -1,93 +1,85 @@
1
1
  module Rubix
2
2
 
3
- # A generic monitor class for constructing Zabbix monitors that
4
- # monitor whole clusters.
3
+ # A module for building monitors which measure items for several
4
+ # hosts in a cluster as well as items for the cluster itself.
5
5
  #
6
- # This class handles the low-level logic of finding a set of nodes and
7
- # then grouping them by cluster.
6
+ # This module assumes that an existing +hosts+ method returns an
7
+ # Array of Zabbix hosts that can be grouped into clusters.
8
8
  #
9
- # It's still up to a subclass to determine how to make a measurement
10
- # on the cluster.
11
- #
12
- # Here's an example of a script which finds the average uptime of
13
- # nodes a value of 'bar' set for property 'foo', grouped by cluster.
9
+ # Here's an example:
14
10
  #
15
11
  # #!/usr/bin/env ruby
16
- # # in cluster_uptime_monitor
17
- #
18
- # class ClusterUptimeMonitor < Rubix::ClusterMonitor
19
12
  #
20
- # def node_query
21
- # 'role:nginx'
22
- # end
23
- #
13
+ # class ClusterPingMonitor < Rubix::Monitor
14
+ #
15
+ # include Rubix::ClusterMonitor
16
+ #
24
17
  # def measure_cluster cluster_name
25
- # total_seconds = nodes_by_cluster[cluster_name].inject(0.0) do |sum, node|
26
- # sum += node['uptime_seconds']
27
- # end
28
- # average_uptime = total_seconds.to_f / nodes_by_cluster[cluster_name].size.to_f
29
- # write(:hostname => 'cluster_name') do |data|
30
- # data << ['uptime.average', average_uptime]
18
+ # total_ping = 0.0
19
+ # num_hosts = 0
20
+ # hosts_by_cluster[cluster_name].each do |host|
21
+ # total_ping += measure_host(host)
22
+ # num_hosts += 1
31
23
  # end
24
+ # write [cluster_name, 'average_ping', total_ping / num_hosts] unless num_hosts == 0
25
+ # end
26
+ #
27
+ # def measure_host host
28
+ # ping = measure_ping_to(host.ip)
29
+ # write [host.name, 'ping', ping]
30
+ # ping # return this so the measure_cluster method can use it
32
31
  # end
33
32
  # end
34
33
  #
35
- # ClusterUptimeMonitor.run if $0 == __FILE__
34
+ # ClusterPingMonitor.run if $0 == __FILE__
36
35
  #
37
- # See documentation for Rubix::Monitor to understand how to run this
38
- # script.
39
- class ClusterMonitor < ChefMonitor
36
+ # You may want to override the +cluster_name_from_host+ method. By
37
+ # defaul it assumes that hosts in Zabbix are named
38
+ # 'cluster-facet-index', a la Ironfan.
39
+ module ClusterMonitor
40
+
41
+ # The name of the default cluster.
42
+ DEFAULT_CLUSTER = 'All Hosts'
43
+
44
+ attr_reader :hosts_by_cluster
40
45
 
41
- attr_reader :all_private_ips_by_cluster, :private_ips_by_cluster, :all_nodes_by_cluster, :nodes_by_cluster
46
+ def default_cluster
47
+ ::Rubix::ClusterMonitor::DEFAULT_CLUSTER
48
+ end
42
49
 
43
50
  def initialize settings
44
51
  super(settings)
45
- group_nodes_by_cluster
52
+ @hosts_by_cluster = {}
53
+ group_hosts_by_cluster
46
54
  end
47
55
 
48
- def node_query
49
- ''
56
+ def measure
57
+ clusters.each do |cluster_name|
58
+ measure_cluster(cluster_name)
59
+ end
50
60
  end
51
61
 
52
- def matching_chef_nodes
53
- search_nodes(node_query)
62
+ def group_hosts_by_cluster
63
+ hosts.each do |host|
64
+ cluster_name = cluster_name_from_host(host)
65
+ @hosts_by_cluster[cluster_name] ||= []
66
+ @hosts_by_cluster[cluster_name] << host
67
+ end
54
68
  end
55
69
 
56
- def group_nodes_by_cluster
57
- @all_private_ips_by_cluster = {}
58
- @private_ips_by_cluster = {}
59
- @all_nodes_by_cluster = {}
60
- @nodes_by_cluster = {}
61
- matching_chef_nodes.first.each do |node|
62
- @all_nodes_by_cluster[node['cluster_name']] ||= []
63
- @nodes_by_cluster[node['cluster_name']] ||= []
64
-
65
- @all_nodes_by_cluster[node['cluster_name']] << node
66
- @nodes_by_cluster[node['cluster_name']] << node unless %w[stopped].include?(node['state'])
67
-
68
-
69
- @all_private_ips_by_cluster[node['cluster_name']] ||= []
70
- @private_ips_by_cluster[node['cluster_name']] ||= []
71
-
72
- @all_private_ips_by_cluster[node['cluster_name']] << node['ipaddress']
73
- @private_ips_by_cluster[node['cluster_name']] << node['ipaddress'] unless %w[stopped].include?(node['state'])
70
+ def cluster_name_from_host host
71
+ return default_cluster if host.name.nil? || host.name.empty?
72
+ parts = host.name.split("-")
73
+ if parts.size == 3
74
+ parts.first
75
+ else
76
+ default_cluster
74
77
  end
75
78
  end
76
79
 
77
80
  def clusters
78
- private_ips_by_cluster.keys
79
- end
80
-
81
- def measure
82
- clusters.each do |cluster_name|
83
- measure_cluster(cluster_name)
84
- end
85
- end
86
-
87
- def measure_cluster cluster_name
88
- raise NotImplementedError.new("Override the 'measure_cluster' method to make measurements of a given cluster.")
81
+ @hosts_by_cluster.keys
89
82
  end
90
83
 
91
84
  end
92
-
93
85
  end
@@ -55,7 +55,8 @@ module Rubix
55
55
  #
56
56
 
57
57
  def self.default_settings
58
- Configliere::Param.new.tap do |s|
58
+ @default_settings ||= Configliere::Param.new.tap do |s|
59
+
59
60
  s.use :commandline
60
61
 
61
62
  s.define :loop, :description => "Run every this many seconds", :required => false, :type => Integer
@@ -63,10 +64,10 @@ module Rubix
63
64
  # The following options are only used when sending directly
64
65
  # with <tt>zabbix_sender</tt>
65
66
  s.define :server, :description => "IP of a Zabbix server", :required => false, :default => 'localhost'
66
- s.define :port, :description => "Port of a Zabbix server", :required => false, :type => Integer, :default => 10051
67
- s.define :host, :description => "Name of a Zabbix host", :required => false
67
+ s.define :port, :description => "Port of a Zabbix server", :required => false, :default => 10051, :type => Integer
68
+ s.define :host, :description => "Name of a Zabbix host", :required => false, :default => ENV["HOSTNAME"]
68
69
  s.define :config, :description => "Local Zabbix agentd configuration file", :required => false, :default => "/etc/zabbix/zabbix_agentd.conf"
69
- s.define :send, :description => "Send data directlyt to Zabbix using 'zabbix_sender'", :required => false, :type => :boolean, :default => false
70
+ s.define :send, :description => "Send data directlyt to Zabbix using 'zabbix_sender'", :required => false, :default => false, :type => :boolean
70
71
  end
71
72
  end
72
73
 
@@ -0,0 +1,71 @@
1
+ module Rubix
2
+
3
+ # A module for finding hosts for a monitor from Zabbix templates or
4
+ # host groups.
5
+ #
6
+ # Here's an example of a monitor which makes a measurement of all
7
+ # hosts with +Template_Foo+ by making a web request to the physical
8
+ # host.
9
+ #
10
+ # #!/usr/bin/env ruby
11
+ # # in cluster_uptime_monitor
12
+ #
13
+ # class FooMonitor < Rubix::Monitor
14
+ #
15
+ # include Rubix::ZabbixMonitor
16
+ #
17
+ # # Define either 'template' or 'host_group' to select hosts (or
18
+ # # define 'hosts').
19
+ # def template
20
+ # 'Template_Foo'
21
+ # end
22
+ #
23
+ # def measure
24
+ # self.hosts.each do |host|
25
+ # measure_host(host)
26
+ # end
27
+ # end
28
+ #
29
+ # def measure_host host
30
+ # ...
31
+ # end
32
+ #
33
+ # FooMonitor.run if $0 == __FILE__
34
+ module ZabbixMonitor
35
+
36
+ attr_accessor :template, :host_group, :hosts
37
+
38
+ def self.included klass
39
+ klass.default_settings.tap do |s|
40
+ s.define :zabbix_api_url, :description => "Zabbix API URL" , :required => true, :default => 'http://localhost/api_jsonrpc.php'
41
+ s.define :username, :description => "Username for Zabbix API", :required => true, :default => 'admin'
42
+ s.define :password, :description => "Password for Zabbix API", :required => true, :default => 'zabbix'
43
+ end
44
+ end
45
+
46
+ def initialize settings
47
+ super(settings)
48
+ Rubix.connect(settings[:zabbix_api_url], settings[:username], settings[:password])
49
+ find_hosts
50
+ end
51
+
52
+ def template_name
53
+ end
54
+
55
+ def host_group_name
56
+ end
57
+
58
+ def find_hosts
59
+ case
60
+ when template_name
61
+ self.template = Rubix::Template.find(:name => template_name)
62
+ self.hosts = Rubix::Host.list(self.template.host_ids).find_all(&:monitored)
63
+ when host_group_name
64
+ self.host_group = Rubix::HostGroup.find(:name => host_group_name)
65
+ self.hosts = Rubix::Host.list(self.host_group.host_ids).find_all(&:monitored)
66
+ else
67
+ raise Rubix::Error.new("Must define either a 'template_name' or a 'host_group_name' property for a Zabbix monitor.")
68
+ end
69
+ end
70
+ end
71
+ end
@@ -5,7 +5,6 @@ describe "Hosts" do
5
5
  before do
6
6
  integration_test
7
7
  @host_group_1 = ensure_save(Rubix::HostGroup.new(:name => 'rubix_spec_host_group_1'))
8
-
9
8
  end
10
9
 
11
10
  after do
@@ -18,6 +17,14 @@ describe "Hosts" do
18
17
  Rubix::Host.find(:name => 'rubix_spec_host_1').should be_nil
19
18
  end
20
19
 
20
+ it "returns an empty array when listing without IDs" do
21
+ Rubix::Host.list([1,2,3]).should == []
22
+ end
23
+
24
+ it "returns an empty array when listing with IDs" do
25
+ Rubix::Host.list([1,2,3]).should == []
26
+ end
27
+
21
28
  it "can be created" do
22
29
  host = Rubix::Host.new(:name => 'rubix_spec_host_1', :host_groups => [@host_group_1])
23
30
  host.save.should be_true
@@ -34,6 +41,13 @@ describe "Hosts" do
34
41
  @template_2 = ensure_save(Rubix::Template.new(:name => 'rubix_spec_template_2', :host_groups => [@host_group_2]))
35
42
  end
36
43
 
44
+ it "can be listed by ID" do
45
+ hosts = Rubix::Host.list([@host.id])
46
+ hosts.should_not be_nil
47
+ hosts.should_not be_empty
48
+ hosts.first.name.should == @host.name
49
+ end
50
+
37
51
  it "can have its name changed" do
38
52
  @host.name = 'rubix_spec_host_2'
39
53
  @host.save
@@ -78,4 +92,5 @@ describe "Hosts" do
78
92
  end
79
93
 
80
94
  end
95
+
81
96
  end
@@ -2,10 +2,59 @@ require 'spec_helper'
2
2
 
3
3
  describe Rubix::ChefMonitor do
4
4
 
5
- before do
6
- @wrapper = Class.new(Rubix::ChefMonitor)
5
+ def mock_query query, nodes=[]
6
+ require 'chef'
7
+ chef_query = mock("Chef::Search::Query")
8
+ ::Chef::Search::Query.should_receive(:new).and_return(chef_query)
9
+ chef_query.should_receive(:search).with('node', query).and_return([nodes, nodes.length])
7
10
  end
8
11
 
12
+ before do
13
+ @wrapper = Class.new(Rubix::Monitor)
14
+ @wrapper.send(:include, Rubix::ChefMonitor)
15
+ end
16
+
17
+ it "has options for talking to Chef" do
18
+ @wrapper.default_settings.should include(:chef_server_url)
19
+ @wrapper.default_settings.should include(:chef_node_name)
20
+ @wrapper.default_settings.should include(:chef_client_key)
21
+ end
22
+
23
+ describe "finding nodes in Chef" do
9
24
 
25
+ describe 'when a node exists' do
26
+ before do
27
+ @node = { 'node_name' => 'foobar', 'ipaddress' => '123', 'fdqn' => '456' }
28
+ end
29
+
30
+ it "can find it based on its node name" do
31
+ mock_query('name:foobar', [@node])
32
+ @wrapper.new(@wrapper.default_settings).chef_node_from_node_name('foobar').should == @node
33
+ end
34
+
35
+ it "can find it based on its IP" do
36
+ mock_query('ipaddress:123 OR fqdn:123', [@node])
37
+ @wrapper.new(@wrapper.default_settings).chef_node_from_ip('123').should == @node
38
+ end
39
+
40
+ it "can find it based on its FQDN" do
41
+ mock_query('ipaddress:456 OR fqdn:456', [@node])
42
+ @wrapper.new(@wrapper.default_settings).chef_node_from_ip('456').should == @node
43
+ end
44
+ end
45
+
46
+ describe "when a node doesn't exist" do
47
+
48
+ it "returns nil when searching by node name" do
49
+ mock_query('name:foobar')
50
+ @wrapper.new(@wrapper.default_settings).chef_node_from_node_name('foobar').should be_nil
51
+ end
52
+
53
+ it "returns nil when searching by IP" do
54
+ mock_query('ipaddress:123 OR fqdn:123')
55
+ @wrapper.new(@wrapper.default_settings).chef_node_from_ip('123').should be_nil
56
+ end
57
+ end
58
+ end
10
59
  end
11
60
 
@@ -2,32 +2,34 @@ require 'spec_helper'
2
2
 
3
3
  describe Rubix::ClusterMonitor do
4
4
 
5
- def mock_chef_query nodes
6
- query = mock("Chef::Search::Query")
7
- require 'chef'
8
- ::Chef::Search::Query.should_receive(:new).and_return(query)
9
- query.should_receive(:search).and_return([nodes, nodes.size])
10
- end
11
-
12
- it "should correctly carve up a group of nodes into clusters" do
13
- mock_chef_query([
14
- { 'cluster_name' => 'foo', 'state' => 'started', 'ipaddress' => '123' },
15
- { 'cluster_name' => 'foo', 'state' => 'stopped', 'ipaddress' => '456' },
16
- { 'cluster_name' => 'bar', 'state' => 'started', 'ipaddress' => '789' },
17
- { 'cluster_name' => 'bar', 'state' => 'stopped', 'ipaddress' => '321' }
18
- ])
19
- cm = Rubix::ClusterMonitor.new({})
20
- cm.clusters.should include('foo', 'bar')
21
- cm.private_ips_by_cluster.should == { 'foo' => ['123'], 'bar' => ['789'] }
22
- cm.all_private_ips_by_cluster.should == { 'foo' => ['123', '456'], 'bar' => ['789', '321'] }
5
+ before do
6
+ @wrapper = Class.new(Rubix::Monitor)
7
+ @wrapper.class_eval do
8
+ include Rubix::ClusterMonitor
9
+ def hosts
10
+ [
11
+ Rubix::Host.new(:name => 'cluster1-facet1-host1'),
12
+ Rubix::Host.new(:name => 'cluster1-facet1-host2'),
13
+ Rubix::Host.new(:name => 'cluster1-facet2-host3'),
14
+
15
+ Rubix::Host.new(:name => 'cluster2-facet1-host1'),
16
+ Rubix::Host.new(:name => 'cluster2-facet1-host2'),
17
+ Rubix::Host.new(:name => 'cluster2-facet2-host3'),
23
18
 
24
- cm.nodes_by_cluster['foo'].size.should == 1
25
- cm.nodes_by_cluster['bar'].size.should == 1
19
+ Rubix::Host.new(:name => 'malformed')
20
+ ]
21
+ end
22
+ end
23
+ end
26
24
 
27
- cm.all_nodes_by_cluster['foo'].size.should == 2
28
- cm.all_nodes_by_cluster['bar'].size.should == 2
29
-
25
+ it "should be able to filter hosts into clusters" do
26
+ monitor = @wrapper.new(@wrapper.default_settings)
27
+ monitor.clusters.should include('cluster1', 'cluster2', Rubix::ClusterMonitor::DEFAULT_CLUSTER)
28
+ monitor.hosts_by_cluster['cluster1'].map(&:name).should include('cluster1-facet1-host1', 'cluster1-facet1-host2', 'cluster1-facet2-host3')
29
+ monitor.hosts_by_cluster['cluster2'].map(&:name).should include('cluster2-facet1-host1', 'cluster2-facet1-host2', 'cluster2-facet2-host3')
30
+ monitor.hosts_by_cluster[Rubix::ClusterMonitor::DEFAULT_CLUSTER].map(&:name).should include('malformed')
30
31
  end
32
+
31
33
 
32
34
 
33
35
  end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rubix::ZabbixMonitor do
4
+
5
+ before do
6
+ @wrapper = Class.new(Rubix::Monitor)
7
+ @wrapper.class_eval do
8
+ include Rubix::ZabbixMonitor
9
+ end
10
+ @hosts = [Rubix::Host.new(:name => 'host1'), Rubix::Host.new(:name => 'host2'), Rubix::Host.new(:name => 'host3', :monitored => false)]
11
+ end
12
+
13
+ it "will raise an error when no template name or host group is defined" do
14
+ lambda { @wrapper.new(@wrapper.default_settings) }.should raise_error(Rubix::Error)
15
+ end
16
+
17
+ it "can find hosts based on a template" do
18
+ @wrapper.class_eval do
19
+ def template_name
20
+ 'Template_Foo'
21
+ end
22
+ end
23
+ @template = Rubix::Template.new(:name => 'Template_Foo')
24
+ Rubix::Template.should_receive(:find).with(:name => 'Template_Foo').and_return(@template)
25
+ @template.should_receive(:host_ids).and_return([1,2,3])
26
+ Rubix::Host.should_receive(:list).with([1,2,3]).and_return(@hosts)
27
+ @wrapper.new(@wrapper.default_settings).hosts.should == @hosts[0..1]
28
+ end
29
+
30
+ it "can find hosts based on a host group" do
31
+ @wrapper.class_eval do
32
+ def host_group_name
33
+ 'Foos'
34
+ end
35
+ end
36
+ @host_group = Rubix::HostGroup.new(:name => 'Foos')
37
+ Rubix::HostGroup.should_receive(:find).with(:name => 'Foos').and_return(@host_group)
38
+ @host_group.should_receive(:host_ids).and_return([1,2,3])
39
+ Rubix::Host.should_receive(:list).with([1,2,3]).and_return(@hosts)
40
+ @wrapper.new(@wrapper.default_settings).hosts.should == @hosts[0..1]
41
+ end
42
+
43
+ end
44
+
45
+
46
+
@@ -1,8 +1,12 @@
1
1
  module Rubix
2
2
  module IntegrationHelper
3
-
3
+
4
4
  def integration_test
5
- pending("A live Zabbix API to test against") unless $RUBIX_INTEGRATION_TEST
5
+ if $RUBIX_INTEGRATION_TEST
6
+ Rubix.connect($RUBIX_INTEGRATION_TEST['url'], $RUBIX_INTEGRATION_TEST['username'], $RUBIX_INTEGRATION_TEST['password'])
7
+ else
8
+ pending("A live Zabbix API to test against")
9
+ end
6
10
  end
7
11
 
8
12
  def ensure_save(obj)
@@ -61,7 +65,7 @@ module Rubix
61
65
 
62
66
  truncate_all_tables
63
67
 
64
- $RUBIX_INTEGRATION_TEST = true
68
+ $RUBIX_INTEGRATION_TEST = api_connection
65
69
  end
66
70
 
67
71
  RUBIX_TABLES_TO_TRUNCATE = %w[applications groups hostmacro hosts hosts_groups hosts_profiles hosts_profiles_ext hosts_templates items items_applications profiles triggers trigger_depends]
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 3
8
- - 1
9
- version: 0.3.1
7
+ - 4
8
+ - 0
9
+ version: 0.4.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Dhruv Bansal
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2012-02-10 00:00:00 -06:00
17
+ date: 2012-02-13 00:00:00 -06:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -96,10 +96,10 @@ files:
96
96
  - lib/rubix/models/model.rb
97
97
  - lib/rubix/models/time_series.rb
98
98
  - lib/rubix/models/application.rb
99
- - lib/rubix/examples/es_monitor.rb
100
- - lib/rubix/examples/mongo_monitor.rb
101
- - lib/rubix/examples/uptime_monitor.rb
102
- - lib/rubix/examples/hbase_monitor.rb
99
+ - lib/rubix/examples/simple_zabbix_monitor.rb
100
+ - lib/rubix/examples/simple_uptime_monitor.rb
101
+ - lib/rubix/examples/simple_cluster_monitor.rb
102
+ - lib/rubix/examples/simple_chef_monitor.rb
103
103
  - lib/rubix/auto_sender.rb
104
104
  - lib/rubix/sender.rb
105
105
  - lib/rubix/log.rb
@@ -117,6 +117,7 @@ files:
117
117
  - lib/rubix/associations/has_many_user_macros.rb
118
118
  - lib/rubix/monitors/chef_monitor.rb
119
119
  - lib/rubix/monitors/cluster_monitor.rb
120
+ - lib/rubix/monitors/zabbix_monitor.rb
120
121
  - lib/rubix/monitors/monitor.rb
121
122
  - lib/rubix/connection.rb
122
123
  - lib/rubix/associations.rb
@@ -124,6 +125,7 @@ files:
124
125
  - spec/rubix/auto_sender_spec.rb
125
126
  - spec/rubix/monitors/monitor_spec.rb
126
127
  - spec/rubix/monitors/chef_monitor_spec.rb
128
+ - spec/rubix/monitors/zabbix_monitor_spec.rb
127
129
  - spec/rubix/monitors/cluster_monitor_spec.rb
128
130
  - spec/rubix/response_spec.rb
129
131
  - spec/rubix/sender_spec.rb
@@ -1,134 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- RUBIX_ROOT = File.expand_path('../../../../lib', __FILE__)
4
- $: << RUBIX_ROOT unless $:.include?(RUBIX_ROOT)
5
-
6
- require 'rubix'
7
- require 'open-uri'
8
-
9
- class ESMonitor < Rubix::ClusterMonitor
10
-
11
- # Hostgroup for any hosts that needs to be created.
12
- CLUSTER_HOSTGROUPS = 'Elasticsearch clusters'
13
-
14
- # Templates for any hosts that need to be created.
15
- CLUSTER_TEMPLATES = 'Template_Elasticsearch_Cluster'
16
- NODE_TEMPLATES = 'Template_Elasticsearch_Node'
17
-
18
- # Applications for new items
19
- CLUSTER_APPLICATIONS = '_cluster'
20
- NODE_APPLICATIONS = 'Elasticsearch'
21
-
22
- def node_query
23
- 'provides_service:*-elasticsearch'
24
- end
25
-
26
- def es_url private_ip, *args
27
- "http://" + File.join(private_ip + ":9200", *args)
28
- end
29
-
30
- def measure_cluster cluster_name
31
- measured_cluster_health = false
32
- measured_cluster_indices = false
33
- measured_cluster_nodes = false
34
- private_ips_by_cluster[cluster_name].each do |private_ip|
35
- measured_cluster_health = measure_cluster_health(cluster_name, private_ip) unless measured_cluster_health
36
- measured_cluster_indices = measure_cluster_indices(cluster_name, private_ip) unless measured_cluster_indices
37
- measured_cluster_nodes = measure_cluster_nodes(cluster_name, private_ip) unless measured_cluster_nodes
38
- break if measured_cluster_health && measured_cluster_indices && measured_cluster_nodes
39
- end
40
- end
41
-
42
- # Measure the cluster health metrics -- /_cluster/health
43
- def measure_cluster_health cluster_name, private_ip
44
- begin
45
- cluster_health = JSON.parse(open(es_url(private_ip, '_cluster', 'health')).read)
46
- rescue SocketError, OpenURI::HTTPError, JSON::ParserError, Errno::ECONNREFUSED => e
47
- # This node may not be running a webnode...
48
- return false
49
- end
50
- write({
51
- :hostname => "#{cluster_name}-elasticsearch",
52
- :hostgroup => self.class::CLUSTER_HOSTGROUPS,
53
- :templates => self.class::CLUSTER_TEMPLATES,
54
- :application => self.class::CLUSTER_APPLICATIONS
55
- }) do |d|
56
- d << ['status', cluster_health['status'] ]
57
- d << ['nodes.total', cluster_health['number_of_nodes'] ]
58
- d << ['nodes.data', cluster_health['number_of_data_nodes'] ]
59
- d << ['shards.active', cluster_health['active_shards'] ]
60
- d << ['shards.relocating', cluster_health['relocating_shards'] ]
61
- d << ['shards.unassigned', cluster_health['unassigned_shards'] ]
62
- d << ['shards.initializing', cluster_health['initializing_shards'] ]
63
- end
64
- true
65
- end
66
-
67
- def measure_cluster_indices cluster_name, private_ip
68
- begin
69
- index_data = JSON.parse(open(es_url(private_ip, '_status')).read)
70
- rescue SocketError, OpenURI::HTTPError, JSON::ParserError, Errno::ECONNREFUSED => e
71
- # This node may not be running a webnode...
72
- return false
73
- end
74
- index_data['indices'].each_pair do |index_name, index_data|
75
- write({
76
- :hostname => "#{cluster_name}-elasticsearch",
77
- :hostgroup => self.class::CLUSTER_HOSTGROUPS,
78
- :templates => self.class::CLUSTER_TEMPLATES,
79
- :appliation => index_name
80
- }) do |d|
81
- d << ["#{index_name}.size", index_data["index"]["size_in_bytes"] ]
82
- d << ["#{index_name}.docs.num", index_data["docs"]["num_docs"] ]
83
- d << ["#{index_name}.docs.max", index_data["docs"]["max_doc"] ]
84
- d << ["#{index_name}.docs.deleted", index_data["docs"]["deleted_docs"] ]
85
- d << ["#{index_name}.operations", index_data["translog"]["operations"] ]
86
- d << ["#{index_name}.merges.total", index_data["merges"]["total"] ]
87
- d << ["#{index_name}.merges.current", index_data["merges"]["current"] ]
88
- end
89
- end
90
- true
91
- end
92
-
93
- def measure_cluster_nodes cluster_name, private_ip
94
- begin
95
- nodes_data = JSON.parse(open(es_url(private_ip, '_cluster', 'nodes')).read)
96
- nodes_stats_data = JSON.parse(open(es_url(private_ip, '_cluster', 'nodes', 'stats')).read)
97
- rescue SocketError, OpenURI::HTTPError, JSON::ParserError, Errno::ECONNREFUSED => e
98
- # This node may not be running a webnode...
99
- return false
100
- end
101
-
102
- nodes_stats_data['nodes'].each_pair do |id, stats|
103
-
104
- ip = nodes_data['nodes'][id]['network']['primary_interface']['address']
105
- node_name = chef_node_name_from_ip(ip)
106
- next unless node_name
107
- write({
108
- :hostname => node_name,
109
- :templates => self.class::NODE_TEMPLATES,
110
- :application => self.class::NODE_APPLICATIONS
111
- }) do |d|
112
- # concurrency
113
- d << ['es.jvm.threads.count', stats['jvm']['threads']['count'] ]
114
-
115
- # garbage collection
116
- d << ['es.jvm.gc.coll_time', stats['jvm']['gc']['collection_time_in_millis'] ]
117
- d << ['es.jvm.gc.coll_count', stats['jvm']['gc']['collection_count'] ]
118
-
119
- # memory
120
- d << ['es.jvm.mem.heap_used', stats['jvm']['mem']['heap_used_in_bytes'] ]
121
- d << ['es.jvm.mem.non_heap_used', stats['jvm']['mem']['non_heap_used_in_bytes'] ]
122
- d << ['es.jvm.mem.heap_comm', stats['jvm']['mem']['heap_committed_in_bytes'] ]
123
- d << ['es.jvm.mem.non_heap_comm', stats['jvm']['mem']['non_heap_committed_in_bytes'] ]
124
-
125
- # indices
126
- d << ['es.indices.size', stats['indices']['size_in_bytes'] ]
127
- end
128
- end
129
- true
130
- end
131
-
132
- end
133
-
134
- ESMonitor.run if $0 == __FILE__
@@ -1,94 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- RUBIX_ROOT = File.expand_path('../../../../lib', __FILE__)
4
- $: << RUBIX_ROOT unless $:.include?(RUBIX_ROOT)
5
-
6
- require 'rubix'
7
- require 'net/http'
8
- require 'crack'
9
-
10
- class HBaseMonitor < Rubix::ClusterMonitor
11
-
12
- # Hostgroups for clusters & hosts that need to be created.
13
- CLUSTER_HOSTGROUPS = 'HBase clusters'
14
-
15
- # Templates for any hosts that need to be created.
16
- CLUSTER_TEMPLATES = 'Template_HBase_Cluster'
17
- NODE_TEMPLATES = 'Template_HBase_Node'
18
-
19
- # Applications for items that are written
20
- CLUSTER_APPLICATIONS = '_cluster'
21
- NODE_APPLICATIONS = "Hbase"
22
-
23
- def matching_chef_nodes
24
- Chef::Search::Query.new.search('node', 'provides_service:*hbase-stargate AND facet_name:alpha')
25
- end
26
-
27
- def measure_cluster cluster_name
28
- measured_cluster_status = false
29
- private_ips_by_cluster[cluster_name].each do |private_ip|
30
- measured_cluster_status = measure_cluster_status(cluster_name, private_ip) unless measured_cluster_status
31
- break if measured_cluster_status
32
- end
33
- end
34
-
35
- # Measure the cluster health metrics -- /status/cluster
36
- def measure_cluster_status cluster_name, private_ip
37
- begin
38
- connection = Net::HTTP.new(private_ip, 8080) # FIXME port
39
- request = Net::HTTP::Get.new('/status/cluster', 'Accept' => 'text/xml')
40
- response = connection.request(request)
41
- return false unless response.code.to_i == 200
42
-
43
- data = Crack::XML.parse(response.body)
44
- cluster_status = data['ClusterStatus']
45
- dead_nodes = cluster_status['DeadNodes'] ? cluster_status['DeadNodes']['Node'] : []
46
- live_nodes = cluster_status['LiveNodes']['Node']
47
- rescue NoMethodError, SocketError, REXML::ParseException, Errno::ECONNREFUSED => e
48
- # puts "#{e.class} -- #{e.message}"
49
- # puts e.backtrace
50
- return false
51
- end
52
-
53
- write({
54
- :hostname => "#{cluster_name}-hbase",
55
- :hostgroup => self.class::CLUSTER_HOSTGROUPS,
56
- :application => self.class::CLUSTER_APPLICATIONS,
57
- :templates => self.class::CLUSTER_TEMPLATES
58
- }) do |d|
59
- d << ['requests', cluster_status['requests']]
60
- d << ['regions', cluster_status['regions']]
61
- d << ['load', cluster_status['averageLoad']]
62
- d << ['nodes.dead', dead_nodes.size]
63
- d << ['nodes.alive', live_nodes.size]
64
- end
65
- measure_cluster_tables(cluster_name, data)
66
- measure_cluster_nodes(cluster_name, live_nodes)
67
- true
68
- end
69
-
70
- def measure_cluster_tables cluster_name, data
71
- # FIXME...not sure how best to get information about "tables" in HBase...
72
- end
73
-
74
- def measure_cluster_nodes cluster_name, live_nodes
75
- live_nodes.each do |live_node|
76
- next unless live_node
77
- ip = (live_node['name'] || '').split(':').first
78
- node_name = chef_node_name_from_ip(ip)
79
- next unless node_name
80
- write({
81
- :hostname => node_name,
82
- :application => self.class::NODE_APPLICATIONS,
83
- :templates => self.class::NODE_TEMPLATES
84
- }) do |d|
85
- d << ['hbase.regions', (live_node['Region'] || []).size]
86
- d << ['hbase.heap_size', live_node['heapSizeMB']]
87
- d << ['hbase.requests', live_node['requests']]
88
- end
89
- end
90
- end
91
-
92
- end
93
-
94
- HBaseMonitor.run if $0 == __FILE__
@@ -1,130 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- RUBIX_ROOT = File.expand_path('../../../../lib', __FILE__)
4
- $: << RUBIX_ROOT unless $:.include?(RUBIX_ROOT)
5
-
6
- require 'rubix'
7
- require 'open-uri'
8
- require 'set'
9
- require 'mongo'
10
-
11
- class MongoMonitor < Rubix::ClusterMonitor
12
-
13
- # Hostgroup for any hosts that needs to be created.
14
- CLUSTER_HOSTGROUPS = 'MongoDB clusters'
15
-
16
- # Templates for any hosts that need to be created.
17
- CLUSTER_TEMPLATES = 'Template_MongoDB'
18
-
19
- # Applications
20
- CLUSTER_APPLICATIONS = '_cluster'
21
-
22
- # Names of database to ignore when we find them.
23
- IGNORED_DATABASES = %w[db test admin local].to_set
24
-
25
- def matching_chef_nodes
26
- Chef::Search::Query.new.search('node', 'provides_service:*-mongodb-server')
27
- end
28
-
29
- def measure_cluster cluster_name
30
- measured_mongo_server = false
31
- measured_mongo_databases = false
32
- private_ips_by_cluster[cluster_name].each do |private_ip|
33
- begin
34
- connection = Mongo::Connection.new(private_ip)
35
- rescue Mongo::ConnectionFailure => e
36
- next
37
- end
38
- measured_mongo_server = measure_mongo_server(cluster_name, connection) unless measured_mongo_server
39
- measured_mongo_databases = measure_mongo_databases(cluster_name, connection) unless measured_mongo_databases
40
- break if measured_mongo_server && measured_mongo_databases
41
- end
42
- end
43
-
44
- def measure_mongo_server cluster_name, connection
45
- initial = nil, final = nil
46
- db = connection.db('system') # the name of this db doesn't matter?
47
- command = {:serverStatus => true} # the value of the 'serverStatus' key doesn't matter?
48
-
49
- # gather metrics with a 1.0 second gap
50
- initial = db.command(command) ; sleep 1.0 ; final = db.command(command)
51
- return false unless initial && final
52
- dt = final['localTime'].to_f - initial['localTime'].to_f
53
- write({
54
- :hostname => "#{cluster_name}-mongodb",
55
- :hostgroup => self.class::CLUSTER_HOSTGROUPS,
56
- :templates => self.class::CLUSTER_TEMPLATES,
57
- :application => self.class::CLUSTER_APPLICATIONS
58
- }) do |d|
59
-
60
- # operations
61
- d << ['inserts', (final['opcounters']['insert'] - initial['opcounters']['insert']) / dt]
62
- d << ['queries', (final['opcounters']['query'] - initial['opcounters']['query']) / dt]
63
- d << ['updates', (final['opcounters']['update'] - initial['opcounters']['update']) / dt]
64
- d << ['deletes', (final['opcounters']['delete'] - initial['opcounters']['delete']) / dt]
65
- d << ['getmores', (final['opcounters']['getmore'] - initial['opcounters']['getmore']) / dt]
66
- d << ['commands', (final['opcounters']['command'] - initial['opcounters']['command']) / dt]
67
-
68
- # memory
69
- d << ['mem.resident', final['mem']['resident']]
70
- d << ['mem.virtual', final['mem']['virtual']]
71
- d << ['mem.mapped', final['mem']['mapped']]
72
-
73
- # disk
74
- d << ['flushes', (final['backgroundFlushing']['flushes'] - initial['backgroundFlushing']['flushes']) / dt]
75
- d << ['flush_time', (final['backgroundFlushing']['total_ms'] - initial['backgroundFlushing']['total_ms']) ]
76
- d << ['faults', (final['extra_info']['page_faults'] - initial['extra_info']['page_faults']) / dt]
77
-
78
- # index
79
- d << ['accesses', (final['indexCounters']['btree']['accesses'] - initial['indexCounters']['btree']['accesses']) / dt]
80
- d << ['hits', (final['indexCounters']['btree']['hits'] - initial['indexCounters']['btree']['hits']) / dt]
81
- d << ['misses', (final['indexCounters']['btree']['misses'] - initial['indexCounters']['btree']['misses']) / dt]
82
- d << ['resets', (final['indexCounters']['btree']['resets'] - initial['indexCounters']['btree']['resets']) / dt]
83
-
84
- # read/write load
85
- d << ['queue.total', final['globalLock']['currentQueue']['total']]
86
- d << ['queue.read', final['globalLock']['currentQueue']['readers']]
87
- d << ['queue.write', final['globalLock']['currentQueue']['writers']]
88
- d << ['clients.total', final['globalLock']['activeClients']['total']]
89
- d << ['clients.read', final['globalLock']['activeClients']['readers']]
90
- d << ['clients.write', final['globalLock']['activeClients']['writers']]
91
-
92
- # network
93
- d << ['net.in', (final['network']['bytesIn'] - initial['network']['bytesIn']) / dt]
94
- d << ['net.out', (final['network']['bytesOut'] - initial['network']['bytesOut']) / dt]
95
- d << ['requests', (final['network']['numRequests'] - initial['network']['numRequests']) / dt]
96
- d << ['connections', final['connections']['current']]
97
- end
98
- true
99
- end
100
-
101
- def measure_mongo_databases cluster_name, connection
102
- dbs = connection.database_names
103
- return true if dbs.size == 0 # nothing to do here
104
-
105
- dbs.each do |database_name|
106
- next if self.class::IGNORED_DATABASES.include?(database_name.downcase)
107
- stats = connection.db(database_name).stats()
108
-
109
- write({
110
- :hostname => "#{cluster_name}-mongodb",
111
- :hostgroup => self.class::CLUSTER_HOSTGROUPS,
112
- :templates => self.class::CLUSTER_TEMPLATES,
113
- :application => database_name
114
- }) do |d|
115
- d << ["#{database_name}.collections", stats["collections"] ]
116
- d << ["#{database_name}.objects.count", stats["objects"] ]
117
- d << ["#{database_name}.objects.avg_size", stats["avgObjSize"] ]
118
- d << ["#{database_name}.size.data", stats["dataSize"] ]
119
- d << ["#{database_name}.size.disk", stats["storageSize"] ]
120
- d << ["#{database_name}.size.indexes", stats["indexSize"] ]
121
- d << ["#{database_name}.size.file", stats["fileSize"] ]
122
- d << ["#{database_name}.extents", stats["numExtents"] ]
123
- d << ["#{database_name}.indexes", stats["indexes"] ]
124
- end
125
- end
126
- true
127
- end
128
- end
129
-
130
- MongoMonitor.run if $0 == __FILE__