vayacondios-server 0.1.2 → 0.1.6

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.
Files changed (33) hide show
  1. data/Gemfile +1 -0
  2. data/app/http_shim.rb +1 -5
  3. data/lib/vayacondios-client.rb +2 -0
  4. data/lib/vayacondios-server.rb +1 -0
  5. data/lib/vayacondios/client/cube_client.rb +39 -0
  6. data/lib/vayacondios/client/itemset.rb +43 -28
  7. data/lib/vayacondios/client/notifier.rb +24 -1
  8. data/lib/vayacondios/client/zabbix_client.rb +148 -0
  9. data/lib/vayacondios/server/handlers/itemset_handler.rb +0 -1
  10. data/lib/vayacondios/server/model/itemset_document.rb +8 -4
  11. data/lib/vayacondios/server/rack/assume_json.rb +13 -0
  12. data/lib/vayacondios/server/rack/extract_methods.rb +11 -1
  13. data/lib/vayacondios/version.rb +1 -1
  14. data/pom.xml +97 -0
  15. data/scripts/hadoop_monitor/configurable.rb +1 -1
  16. data/scripts/hadoop_monitor/hadoop_attempt_scraper.rb +6 -3
  17. data/scripts/hadoop_monitor/hadoop_client.rb +20 -19
  18. data/scripts/hadoop_monitor/hadoop_monitor.rb +3 -3
  19. data/scripts/hadoop_monitor/hadoopable.rb +3 -3
  20. data/scripts/hadoop_monitor/machine_monitor.rb +2 -2
  21. data/spec/client/itemset_spec.rb +8 -8
  22. data/spec/server/itemset_spec.rb +4 -4
  23. data/src/main/java/com/infochimps/util/CurrentClass.java +26 -0
  24. data/src/main/java/com/infochimps/util/DebugUtil.java +38 -0
  25. data/src/main/java/com/infochimps/util/HttpHelper.java +112 -0
  26. data/src/main/java/com/infochimps/vayacondios/ItemSets.java +456 -0
  27. data/src/main/java/com/infochimps/vayacondios/Organization.java +49 -0
  28. data/src/main/java/com/infochimps/vayacondios/PathBuilder.java +13 -0
  29. data/src/main/java/com/infochimps/vayacondios/VCDIntegrationTest.java +68 -0
  30. data/src/main/java/com/infochimps/vayacondios/VayacondiosClient.java +88 -0
  31. data/vayacondios-client.gemspec +2 -2
  32. data/vayacondios-server.gemspec +4 -2
  33. metadata +37 -9
data/Gemfile CHANGED
@@ -9,4 +9,5 @@ group :hadoop_monitor do
9
9
  gem 'json'
10
10
  gem 'nokogiri', '~> 1.5'
11
11
  gem 'nibbler', '~> 1.3'
12
+ gem "swineherd-fs", "~> 0.0.3"
12
13
  end
data/app/http_shim.rb CHANGED
@@ -3,12 +3,8 @@
3
3
  require 'vayacondios-server'
4
4
 
5
5
  class HttpShim < Goliath::API
6
+ use Vayacondios::Rack::AssumeJSON # assume application/json content type
6
7
  use Goliath::Rack::Tracer, 'X-Tracer' # log trace statistics
7
- # /v1/infochimps/itemset/foo/1 -d '["foo","bar","baz"]'
8
- # use Vayacondios::Rack::Versionator
9
- # /v1/infochimps/itemset/foo/1 -d '{"items":["foo","bar","baz"]}' ... env[:vayacondios_version] = 1
10
- # post_process()
11
- # extract results = results[:results] from body for legacy
12
8
  use Goliath::Rack::Params # parse query string and message body into params hash
13
9
  use Goliath::Rack::Validation::RequestMethod, %w[GET PUT PATCH DELETE] # only allow these methods
14
10
  use Vayacondios::Rack::ExtractMethods # interpolate GET, PUT into :create, :update, etc
@@ -16,5 +16,7 @@ require 'gorillib/string/constantize'
16
16
  require 'gorillib/string/inflections'
17
17
 
18
18
  require 'vayacondios/client/http_client'
19
+ require 'vayacondios/client/cube_client'
20
+ require 'vayacondios/client/zabbix_client'
19
21
  require 'vayacondios/client/notifier'
20
22
  require 'vayacondios/client/configliere'
@@ -22,6 +22,7 @@ require 'vayacondios/server/handlers/config_handler'
22
22
  require 'vayacondios/server/handlers/event_handler'
23
23
  require 'vayacondios/server/handlers/itemset_handler'
24
24
 
25
+ require 'vayacondios/server/rack/assume_json'
25
26
  require 'vayacondios/server/rack/extract_methods'
26
27
  require 'vayacondios/server/rack/path'
27
28
  require 'vayacondios/server/rack/path_validation'
@@ -0,0 +1,39 @@
1
+ class Vayacondios
2
+ class CubeClient
3
+ include Gorillib::Model
4
+
5
+ field :host, String, :default => 'localhost'
6
+ field :port, Integer, :default => 6000
7
+
8
+ class Error < StandardError; end
9
+
10
+ def uri
11
+ return @uri if @uri
12
+
13
+ uri_str = "http://#{host}:#{port}/1.0"
14
+ @uri ||= URI(uri_str)
15
+ end
16
+
17
+ def event(topic, document = {})
18
+ request(:post, File.join(uri.path, 'event'), MultiJson.dump(document))
19
+ end
20
+
21
+ private
22
+
23
+ def request(method, path, document=nil)
24
+ http = Net::HTTP.new(uri.host, uri.port)
25
+
26
+ params = [method.to_sym, path]
27
+ params += [document, {'Content-Type' => 'application/json'}] unless document.nil?
28
+
29
+ response = http.send *params
30
+
31
+ if Net::HTTPSuccess === response
32
+ MultiJson.load(response.body) rescue response.body
33
+ else
34
+ raise Error.new("Error (#{response.code}) while #{method.to_s == 'get' ? 'fetching' : 'inserting'} document: " + response.body)
35
+ end
36
+ end
37
+ end
38
+ end
39
+
@@ -4,42 +4,29 @@ require 'multi_json'
4
4
  class Vayacondios
5
5
  class Client
6
6
  class ItemSet
7
- def initialize host, port, organization, topic, id
8
- @host = host
9
- @port = port
10
-
11
- @path = "/v1/#{organization}/itemset/#{topic}/#{id}"
7
+ def initialize host, port, organization=nil, topic=nil, id=nil
8
+ @host = host
9
+ @port = port
10
+ @organization = organization
11
+ @topic = topic
12
+ @id = id
12
13
  end
13
14
 
14
- # Exposed for testing.
15
- def _req type, ary = nil
16
- case type
17
- when :fetch then
18
- req = Net::HTTP::Get.new(@path)
19
- when :create then
20
- (req = Net::HTTP::Put.new(@path)).body = MultiJson.encode(ary)
21
- when :update then
22
- (req = Net::HTTP::Put.new(@path, {"x-method" => "PATCH"})).body = MultiJson.encode(ary)
23
- when :remove then
24
- (req = Net::HTTP::Delete.new(@path)).body = MultiJson.encode(ary)
25
- end
26
- req
15
+ def fetch organization=nil, topic=nil, id=nil
16
+ resp = execute_request(_req(:fetch, nil, organization, topic, id)) and
17
+ resp["contents"]
27
18
  end
28
19
 
29
- def fetch
30
- execute_request(_req(:fetch))
20
+ def update ary, organization=nil, topic=nil, id=nil
21
+ execute_request(_req(:update, ary, organization, topic, id))
31
22
  end
32
23
 
33
- def update ary
34
- execute_request(_req(:update, ary))
24
+ def create ary, organization=nil, topic=nil, id=nil
25
+ execute_request(_req(:create, ary, organization, topic, id))
35
26
  end
36
27
 
37
- def create ary
38
- execute_request(_req(:create, ary))
39
- end
40
-
41
- def remove ary
42
- execute_request(_req(:remove, ary))
28
+ def remove ary, organization=nil, topic=nil, id=nil
29
+ execute_request(_req(:remove, ary, organization, topic, id))
43
30
  end
44
31
 
45
32
 
@@ -52,6 +39,34 @@ class Vayacondios
52
39
  result = MultiJson.decode(resp) unless resp.nil? or resp.empty?
53
40
  (result.respond_to?(:has_key?) and result.has_key? "error") ? nil : result
54
41
  end
42
+
43
+ def path organization, topic, id
44
+ if ((the_organization = (organization || @organization)).nil? ||
45
+ (the_topic = (topic || @topic )).nil? ||
46
+ (the_id = (id || @id )).nil?)
47
+ raise ArgumentError.new("must provide organization, topic, and id!")
48
+ end
49
+
50
+ ['/v1', the_organization, 'itemset', the_topic, the_id].join("/")
51
+ end
52
+
53
+ # This is the only private method that is tested.
54
+ def _req type, ary=nil, organization=nil, topic=nil, id=nil
55
+
56
+ the_path = path(organization, topic, id)
57
+ headers = {"content-type" => "application/json"}
58
+ headers.merge!("x-method" => "PATCH") if type == :update
59
+
60
+ case type
61
+ when :fetch then Net::HTTP::Get
62
+ when :create then Net::HTTP::Put
63
+ when :update then Net::HTTP::Put
64
+ when :remove then Net::HTTP::Delete
65
+ else raise ArgumentError.new("invalid type: #{type}")
66
+ end.new(the_path, headers).tap do |req|
67
+ req.body = MultiJson.encode(contents: ary) unless type == :fetch
68
+ end
69
+ end
55
70
  end
56
71
  end
57
72
  end
@@ -56,11 +56,34 @@ class Vayacondios
56
56
  end
57
57
  end
58
58
 
59
+ class CubeNotifier < Notifier
60
+ def initialize(options={})
61
+ @client = Vayacondios::CubeClient.receive(options)
62
+ end
63
+ def notify topic, cargo={}
64
+ prepped = prepare(cargo)
65
+ client.event(topic, prepped)
66
+ nil
67
+ end
68
+ end
69
+
70
+ class ZabbixNotifier < Notifier
71
+ def initialize options={}
72
+ @client = Vayacondios::ZabbixClient.receive(options)
73
+ end
74
+ def notify(topic, cargo={})
75
+ prepped = prepare(cargo)
76
+ client.insert(topic, prepped)
77
+ end
78
+ end
79
+
59
80
  class NotifierFactory
60
81
  def self.receive(attrs = {})
61
82
  type = attrs[:type]
62
83
  case type
63
84
  when 'http' then HttpNotifier.new(attrs)
85
+ when 'cube' then CubeNotifier.new(attrs)
86
+ when 'zabbix' then ZabbixNotifier.new(attrs)
64
87
  when 'log' then LogNotifier.new(attrs)
65
88
  when 'none','null' then NullNotifier.new(attrs)
66
89
  else
@@ -80,7 +103,7 @@ class Vayacondios
80
103
  def self.included klass
81
104
  if klass.ancestors.include? Gorillib::Model
82
105
  klass.class_eval do
83
- field :notifier, Vayacondios::NotifierFactory, default: Vayacondios.default_notifier
106
+ field :notifier, Vayacondios::NotifierFactory, default: Vayacondios.default_notifier, :doc => "Notifier used to notify out of band data"
84
107
 
85
108
  def receive_notifier params
86
109
  params.merge!(log: try(:log)) if params[:type] == 'log'
@@ -0,0 +1,148 @@
1
+ require 'socket'
2
+
3
+ class Vayacondios
4
+
5
+ # Used for sending events to a Zabbix server.
6
+ #
7
+ # An 'event' from Vayacondios' perspective is an arbitrary Hash.
8
+ #
9
+ # An 'event' from Zabbix's perspective is a tuple of values:
10
+ #
11
+ # * time
12
+ # * host
13
+ # * key
14
+ # * value
15
+ #
16
+ # This client will accept a Vayacondios event and internally
17
+ # translate it into a set of Zabbix events.
18
+ #
19
+ # @example A CPU monitoring notification
20
+ #
21
+ # notify "foo-server.example.com", cpu: {
22
+ # util: {
23
+ # user: 0.20,
24
+ # idle: 0.70,
25
+ # sys: 0.10
26
+ # },
27
+ # load: 1.3
28
+ # }
29
+ #
30
+ # would get turned into the following events when written to Zabbix:
31
+ #
32
+ # @example The CPU monitoring notification translated to Zabbix events
33
+ #
34
+ # [
35
+ # { host: "foo-server.example.com", key: "cpu.util.user", value: 0.20 }
36
+ # { host: "foo-server.example.com", key: "cpu.util.idle", value: 0.70 },
37
+ # { host: "foo-server.example.com", key: "cpu.util.sys", value: 0.10 },
38
+ # { host: "foo-server.example.com", key: "cpu.load", value: 1.3 }
39
+ # ]
40
+ #
41
+ # Zabbix will interpret the time as the time it receives each event.
42
+ #
43
+ # The following links provide details on the protocol used by Zabbix
44
+ # to receive events:
45
+ #
46
+ # * https://www.zabbix.com/forum/showthread.php?t=20047&highlight=sender
47
+ # * https://gist.github.com/1170577
48
+ # * http://spin.atomicobject.com/2012/10/30/collecting-metrics-from-ruby-processes-using-zabbix-trappers/?utm_source=rubyflow&utm_medium=ao&utm_campaign=collecting-metrics-zabix
49
+ class ZabbixClient
50
+ include Gorillib::Builder
51
+
52
+ attr_accessor :socket
53
+
54
+ field :host, String, :default => 'localhost', :doc => "Host for the Zabbix server"
55
+ field :port, Integer, :default => 10051, :doc => "Port for the Zabbix server"
56
+
57
+ # Insert events to a Zabbix server.
58
+ #
59
+ # The `topic` will be used as the name of the Zabbix host to
60
+ # associate event data to.
61
+ #
62
+ # As per the documentation for the [Zabbix sender
63
+ # protocol](https://www.zabbix.com/wiki/doc/tech/proto/zabbixsenderprotocol),
64
+ # a new TCP connection will be created for each event.
65
+ #
66
+ # @param [String] topic
67
+ # @param [Hash] cargo
68
+ # Array<Hash>] text
69
+ def insert topic, cargo={}
70
+ self.socket = TCPSocket.new(host, port)
71
+ send_request(topic, cargo)
72
+ handle_response
73
+ self.socket.close
74
+ end
75
+
76
+ private
77
+
78
+ # :nodoc
79
+ def send_request topic, cargo
80
+ socket.write(payload(topic, cargo))
81
+ end
82
+
83
+ # :nodoc
84
+ def handle_response
85
+ header = socket.recv(5)
86
+ if header == "ZBXD\1"
87
+ data_header = socket.recv(8)
88
+ length = data_header[0,4].unpack("i")[0]
89
+ response = MultiJson.load(socket.recv(length))
90
+ puts response["info"]
91
+ else
92
+ puts "Invalid response: #{header}"
93
+ end
94
+ end
95
+
96
+ # :nodoc
97
+ def payload topic, cargo={}
98
+ body = body_for(topic, cargo)
99
+ header_for(body) + body
100
+ end
101
+
102
+ # :nodoc
103
+ def body_for topic, cargo={}
104
+ MultiJson.dump({request: "sender data", data: zabbix_events_from(topic, cargo) })
105
+ end
106
+
107
+ # :nodoc
108
+ def header_for body
109
+ length = body.bytesize
110
+ "ZBXD\1".encode("ascii") + [length].pack("i") + "\x00\x00\x00\x00"
111
+ end
112
+
113
+ # :nodoc
114
+ def zabbix_events_from topic, cargo, scope=''
115
+ events = []
116
+ case cargo
117
+ when Hash
118
+ cargo.each_pair do |key, value|
119
+ events += zabbix_events_from(topic, value, new_scope(scope, key))
120
+ end
121
+ when Array
122
+ cargo.each_with_index do |item, index|
123
+ events += zabbix_events_from(topic, item, new_scope(scope, index))
124
+ end
125
+ else
126
+ events << event_body(topic, scope, cargo)
127
+ end
128
+ events
129
+ end
130
+
131
+ # :nodoc
132
+ def new_scope(current_scope, new_scope)
133
+ [current_scope, new_scope].map(&:to_s).reject(&:empty?).join('.')
134
+ end
135
+
136
+ # :nodoc
137
+ def event_body topic, scope, cargo
138
+ value = case cargo
139
+ when Hash then cargo[:value]
140
+ when Array then cargo.first
141
+ else cargo
142
+ end
143
+ { host: topic, key: scope, value: value }
144
+ end
145
+
146
+ end
147
+ end
148
+
@@ -41,7 +41,6 @@ class Vayacondios
41
41
  validate_options options
42
42
 
43
43
  existing_document = ItemsetDocument.find(@mongo, options)
44
- puts "destroy existing"
45
44
  existing_document.destroy(document)
46
45
  end
47
46
 
@@ -10,7 +10,7 @@ require 'vayacondios/server/model/document'
10
10
  # work while Goliath is in a streaming context.
11
11
 
12
12
  class Vayacondios::ItemsetDocument < Vayacondios::Document
13
- attr_reader :organization, :topic, :body, :id
13
+ attr_reader :organization, :topic, :id
14
14
 
15
15
  def initialize(mongodb, options = {})
16
16
  super options
@@ -33,13 +33,17 @@ class Vayacondios::ItemsetDocument < Vayacondios::Document
33
33
  self.new(mongodb, options).find
34
34
  end
35
35
 
36
+ def body
37
+ {contents: @body}
38
+ end
39
+
36
40
  def find
37
41
  result = @collection.find_one({_id: @id})
38
42
 
39
43
  if result
40
44
  result.delete("_id")
41
45
  @body = result["d"]
42
- self
46
+ self
43
47
  else
44
48
  nil
45
49
  end
@@ -48,7 +52,7 @@ class Vayacondios::ItemsetDocument < Vayacondios::Document
48
52
  def update(document)
49
53
  raise Vayacondios::Error::BadRequest.new unless document.is_a?(Hash)
50
54
 
51
- @body = document['contents'] # should be items
55
+ @body = document['contents']
52
56
 
53
57
 
54
58
  @collection.update({:_id => @id}, {:_id => @id, 'd' => @body }, {upsert: true})
@@ -61,7 +65,7 @@ class Vayacondios::ItemsetDocument < Vayacondios::Document
61
65
 
62
66
  # Merge ourselves
63
67
  if @body
64
- @body = body + document['contents']
68
+ @body = @body + document['contents']
65
69
  else
66
70
  @body = document['contents']
67
71
  end
@@ -0,0 +1,13 @@
1
+ class Vayacondios
2
+ module Rack
3
+ class AssumeJSON
4
+ include Goliath::Rack::AsyncMiddleware
5
+
6
+ def call(env)
7
+ env['CONTENT_TYPE'] =
8
+ 'application/json' unless env.has_key? 'CONTENT_TYPE'
9
+ super env
10
+ end
11
+ end
12
+ end
13
+ end
@@ -12,7 +12,17 @@ class Vayacondios
12
12
  return unless env['REQUEST_METHOD']
13
13
  case env['REQUEST_METHOD'].upcase
14
14
  when 'PUT' then
15
- (env['HTTP_X_METHOD'] && env['HTTP_X_METHOD'].upcase == 'PATCH') ? :patch : :update
15
+ if env.has_key? 'HTTP_X_METHOD'
16
+ if env['HTTP_X_METHOD'].upcase == 'PATCH'
17
+ :patch
18
+ elsif env['HTTP_X_METHOD'].upcase == 'DELETE'
19
+ :delete
20
+ else
21
+ :update
22
+ end
23
+ else
24
+ :update
25
+ end
16
26
  when 'GET' then :show
17
27
  when 'POST' then :create
18
28
  when 'PATCH' then :patch