wakame-dolphin 0.0.2
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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +4 -0
- data/.travis.yml +25 -0
- data/Gemfile +18 -0
- data/Makefile +56 -0
- data/README.md +77 -0
- data/Rakefile +67 -0
- data/bin/dolphin_server +98 -0
- data/config/db/cassandra_clear.txt +1 -0
- data/config/db/cassandra_schema.txt +14 -0
- data/config/db/sequel/migrations/0001_add_notification.rb +15 -0
- data/config/db/sequel/migrations/0002_add_event.rb +15 -0
- data/config/dolphin-mysql.conf.travis +26 -0
- data/config/dolphin.conf.example +26 -0
- data/lib/dolphin.rb +148 -0
- data/lib/dolphin/data_store.rb +55 -0
- data/lib/dolphin/data_stores/base_rdb.rb +57 -0
- data/lib/dolphin/data_stores/cassandra.rb +98 -0
- data/lib/dolphin/data_stores/mysql.rb +31 -0
- data/lib/dolphin/helpers/message/zabbix_helper.rb +16 -0
- data/lib/dolphin/helpers/request_helper.rb +72 -0
- data/lib/dolphin/mailer.rb +83 -0
- data/lib/dolphin/manager.rb +35 -0
- data/lib/dolphin/message_builder.rb +98 -0
- data/lib/dolphin/models/base.rb +8 -0
- data/lib/dolphin/models/cassandra/base.rb +11 -0
- data/lib/dolphin/models/cassandra/event.rb +42 -0
- data/lib/dolphin/models/cassandra/notification.rb +28 -0
- data/lib/dolphin/models/rdb/base.rb +12 -0
- data/lib/dolphin/models/rdb/event.rb +47 -0
- data/lib/dolphin/models/rdb/notification.rb +27 -0
- data/lib/dolphin/models/rdb/orm/base.rb +10 -0
- data/lib/dolphin/models/rdb/orm/event.rb +8 -0
- data/lib/dolphin/models/rdb/orm/notification.rb +8 -0
- data/lib/dolphin/query_processor.rb +50 -0
- data/lib/dolphin/request_handler.rb +150 -0
- data/lib/dolphin/sender.rb +47 -0
- data/lib/dolphin/util.rb +18 -0
- data/lib/dolphin/version.rb +3 -0
- data/lib/dolphin/worker.rb +149 -0
- data/script/console +13 -0
- data/spec/files/cassandra_models_spec.rb +127 -0
- data/spec/files/dolphin_spec.rb +110 -0
- data/spec/files/endpoint/event_spec.rb +123 -0
- data/spec/files/endpoint/notification_spec.rb +54 -0
- data/spec/files/message_builder_spec.rb +15 -0
- data/spec/helpers/test_helper.rb +21 -0
- data/spec/helpers/web_request_helper.rb +40 -0
- data/spec/spec_helper.rb +18 -0
- data/templates/email/alert_port.erb +24 -0
- data/templates/email/default.erb +3 -0
- data/tests/test_dolphin +10 -0
- data/tests/test_get_event +31 -0
- data/tests/test_get_notification +27 -0
- data/tests/test_post_event +37 -0
- data/tests/test_post_notification +43 -0
- data/wakame-dolphin.gemspec +40 -0
- metadata +311 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
[server]
|
2
|
+
host = 127.0.0.1
|
3
|
+
port = 9004
|
4
|
+
|
5
|
+
[agent]
|
6
|
+
request_handler = 1
|
7
|
+
query_processor = 1
|
8
|
+
worker = 1
|
9
|
+
sender = 1
|
10
|
+
|
11
|
+
[database]
|
12
|
+
adapter = cassandra
|
13
|
+
hosts = 127.0.0.1
|
14
|
+
port = 9160
|
15
|
+
max_retry_count = 3
|
16
|
+
retry_interval = 3
|
17
|
+
|
18
|
+
[mail]
|
19
|
+
from = demo@example.com
|
20
|
+
# host = localhost
|
21
|
+
# port = 25
|
22
|
+
type = file
|
23
|
+
|
24
|
+
[logger]
|
25
|
+
format = human_readable
|
26
|
+
# format = ltsv
|
data/lib/dolphin.rb
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'parseconfig'
|
4
|
+
require 'ostruct'
|
5
|
+
require 'extlib/blank'
|
6
|
+
|
7
|
+
Signal.trap(:INT, "EXIT")
|
8
|
+
|
9
|
+
$LOAD_PATH.unshift File.expand_path('../', __FILE__)
|
10
|
+
|
11
|
+
module Dolphin
|
12
|
+
def self.settings(config='')
|
13
|
+
|
14
|
+
if @settings.nil?
|
15
|
+
|
16
|
+
# overwrite
|
17
|
+
if !ENV['CONFIG_FILE'].blank?
|
18
|
+
config = File.join(Dolphin.config_path, ENV['CONFIG_FILE'])
|
19
|
+
elsif config.blank?
|
20
|
+
config = File.join(Dolphin.config_path, 'dolphin.conf')
|
21
|
+
end
|
22
|
+
|
23
|
+
if !File.exists?(config)
|
24
|
+
puts "File not found #{config}"
|
25
|
+
exit!
|
26
|
+
end
|
27
|
+
|
28
|
+
# TODO:validation
|
29
|
+
@config = config
|
30
|
+
@settings = ParseConfig.new(config)
|
31
|
+
end
|
32
|
+
@settings
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.config
|
36
|
+
@config
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.root_path
|
40
|
+
File.expand_path('../../', __FILE__)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.templates_path
|
44
|
+
File.join(root_path, '/templates')
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.config_path
|
48
|
+
File.join(root_path, '/config')
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.db_path
|
52
|
+
File.join(config_path, '/db')
|
53
|
+
end
|
54
|
+
|
55
|
+
class EventObject < OpenStruct;end
|
56
|
+
class NotificationObject < OpenStruct; end
|
57
|
+
class ResponseObject
|
58
|
+
attr_accessor :message
|
59
|
+
def initialize
|
60
|
+
@success = nil
|
61
|
+
@message = ''
|
62
|
+
end
|
63
|
+
|
64
|
+
def success!
|
65
|
+
@success = true
|
66
|
+
end
|
67
|
+
|
68
|
+
def success?
|
69
|
+
warn 'Does not happened anything.' if @success.nil?
|
70
|
+
@success === true
|
71
|
+
end
|
72
|
+
|
73
|
+
def fail!
|
74
|
+
@success = false
|
75
|
+
end
|
76
|
+
|
77
|
+
def fail?
|
78
|
+
warn 'Does not happened anything.' if @success.nil?
|
79
|
+
@success === false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class FailureObject < ResponseObject
|
84
|
+
def initialize(message = '')
|
85
|
+
fail!
|
86
|
+
@message = message
|
87
|
+
freeze
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class SuccessObject < ResponseObject
|
92
|
+
def initialize(message = '')
|
93
|
+
success!
|
94
|
+
@message = message
|
95
|
+
freeze
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
autoload :VERSION, 'dolphin/version'
|
100
|
+
|
101
|
+
autoload :Util, 'dolphin/util'
|
102
|
+
autoload :MessageBuilder, 'dolphin/message_builder'
|
103
|
+
autoload :TemplateBuilder, 'dolphin/message_builder'
|
104
|
+
autoload :Mailer, 'dolphin/mailer'
|
105
|
+
autoload :DataStore, 'dolphin/data_store'
|
106
|
+
|
107
|
+
module Models
|
108
|
+
autoload :Base, 'dolphin/models/base'
|
109
|
+
module Cassandra
|
110
|
+
autoload :Base, 'dolphin/models/cassandra/base'
|
111
|
+
autoload :Event, 'dolphin/models/cassandra/event'
|
112
|
+
autoload :Notification, 'dolphin/models/cassandra/notification'
|
113
|
+
end
|
114
|
+
|
115
|
+
module Rdb
|
116
|
+
autoload :Base, 'dolphin/models/rdb/base'
|
117
|
+
autoload :Event, 'dolphin/models/rdb/event'
|
118
|
+
autoload :Notification, 'dolphin/models/rdb/notification'
|
119
|
+
module Orm
|
120
|
+
autoload :Event, 'dolphin/models/rdb/orm/event'
|
121
|
+
autoload :Notification, 'dolphin/models/rdb/orm/notification'
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
module Helpers
|
127
|
+
autoload :RequestHelper, 'dolphin/helpers/request_helper'
|
128
|
+
module Message
|
129
|
+
autoload :ZabbixHelper, 'dolphin/helpers/message/zabbix_helper'
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
module DataStore
|
134
|
+
autoload :Cassandra, 'dolphin/data_stores/cassandra'
|
135
|
+
autoload :Mysql, 'dolphin/data_stores/mysql'
|
136
|
+
autoload :BaseRdb, 'dolphin/data_stores/base_rdb'
|
137
|
+
end
|
138
|
+
|
139
|
+
# Celluloid supervisor
|
140
|
+
autoload :Manager, 'dolphin/manager'
|
141
|
+
|
142
|
+
# Celluloid actors
|
143
|
+
autoload :RequestHandler, 'dolphin/request_handler'
|
144
|
+
autoload :Worker, 'dolphin/worker'
|
145
|
+
autoload :QueryProcessor, 'dolphin/query_processor'
|
146
|
+
autoload :Sender, 'dolphin/sender'
|
147
|
+
|
148
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Dolphin
|
2
|
+
module DataStore
|
3
|
+
include Dolphin::Util
|
4
|
+
|
5
|
+
DATABASE = 'dolphin'.freeze
|
6
|
+
|
7
|
+
def self.current_store
|
8
|
+
create(Dolphin.settings['database']['adapter'].to_sym)
|
9
|
+
end
|
10
|
+
|
11
|
+
def hosts
|
12
|
+
Dolphin.settings['database']['hosts']
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.create(adapter)
|
16
|
+
config = {}
|
17
|
+
case adapter
|
18
|
+
when :cassandra
|
19
|
+
klass = Dolphin::DataStore::Cassandra
|
20
|
+
config.merge!({
|
21
|
+
:keyspace => DATABASE,
|
22
|
+
:hosts => Dolphin.settings['database']['hosts'],
|
23
|
+
:port => Dolphin.settings['database']['port'],
|
24
|
+
:max_retry_count => Dolphin.settings['database']['max_retry_count'].to_i,
|
25
|
+
:retry_interval => Dolphin.settings['database']['retry_interval'].to_i
|
26
|
+
})
|
27
|
+
when :mysql
|
28
|
+
klass = Dolphin::DataStore::Mysql
|
29
|
+
config.merge!({
|
30
|
+
:adapter => 'mysql2',
|
31
|
+
:database => DATABASE,
|
32
|
+
:host => Dolphin.settings['database']['host'],
|
33
|
+
:port => Dolphin.settings['database']['port'],
|
34
|
+
:user => Dolphin.settings['database']['user'],
|
35
|
+
:password => Dolphin.settings['database']['password'],
|
36
|
+
})
|
37
|
+
else
|
38
|
+
raise NotImplementedError
|
39
|
+
end
|
40
|
+
klass.new(config)
|
41
|
+
end
|
42
|
+
|
43
|
+
class ConncetionBase
|
44
|
+
include Dolphin::Util
|
45
|
+
|
46
|
+
def connect
|
47
|
+
raise NotImplementedError
|
48
|
+
end
|
49
|
+
|
50
|
+
def path
|
51
|
+
raise NotImplementedError
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Dolphin
|
2
|
+
module DataStore
|
3
|
+
class BaseRdb
|
4
|
+
|
5
|
+
def initialize(config)
|
6
|
+
@adapter = config[:adapter]
|
7
|
+
@host = config[:host]
|
8
|
+
@user = config[:user]
|
9
|
+
@password = config[:password]
|
10
|
+
@database = config[:database]
|
11
|
+
|
12
|
+
# Set timezone to UTC
|
13
|
+
Sequel.default_timezone = :utc
|
14
|
+
end
|
15
|
+
|
16
|
+
def current_database
|
17
|
+
Sequel.connect(connect_path)
|
18
|
+
end
|
19
|
+
|
20
|
+
def closed?
|
21
|
+
@connection.nil?
|
22
|
+
end
|
23
|
+
|
24
|
+
ORM = Dolphin::Models::Rdb::Orm
|
25
|
+
|
26
|
+
def get_notification(id)
|
27
|
+
n = Dolphin::Models::Rdb::Notification.new(ORM::Notification)
|
28
|
+
n.get(id)
|
29
|
+
end
|
30
|
+
|
31
|
+
def put_event(event)
|
32
|
+
e = Dolphin::Models::Rdb::Event.new(ORM::Event)
|
33
|
+
e.put(event)
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_event(params)
|
37
|
+
e = Dolphin::Models::Rdb::Event.new(ORM::Event)
|
38
|
+
e.get(params)
|
39
|
+
end
|
40
|
+
|
41
|
+
def put_notification(id, methods)
|
42
|
+
n = Dolphin::Models::Rdb::Notification.new(ORM::Notification)
|
43
|
+
n.put(id, methods)
|
44
|
+
end
|
45
|
+
|
46
|
+
def delete_notification(notification)
|
47
|
+
n = Dolphin::Models::Rdb::Notification.new(ORM::Notification)
|
48
|
+
n.delete(notification)
|
49
|
+
end
|
50
|
+
|
51
|
+
def connect_path
|
52
|
+
"#{@adapter}://#{@host}/#{@database}?user=#{@user}&password=#{@password}"
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'cassandra/1.1'
|
4
|
+
|
5
|
+
module Thrift
|
6
|
+
class FramedTransport < BaseTransport
|
7
|
+
def write(buf,sz=nil)
|
8
|
+
if !['US-ASCII', 'ASCII-8BIT'].include?(buf.encoding.to_s)
|
9
|
+
buf = buf.unpack("a*").first
|
10
|
+
end
|
11
|
+
return @transport.write(buf) unless @write
|
12
|
+
|
13
|
+
@wbuf << (sz ? buf[0...sz] : buf)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module Dolphin
|
19
|
+
module DataStore
|
20
|
+
class Cassandra
|
21
|
+
include Dolphin::Util
|
22
|
+
|
23
|
+
class UnAvailableNodeException < Exception; end
|
24
|
+
|
25
|
+
PATH_SEPARATOR = ':'.freeze
|
26
|
+
|
27
|
+
def initialize(config)
|
28
|
+
@keyspace = config[:keyspace]
|
29
|
+
raise "database hosts is blank" if config[:hosts].blank?
|
30
|
+
@hosts = config[:hosts].split(',')
|
31
|
+
@port = config[:port]
|
32
|
+
@max_retry_count = config[:max_retry_count] || 3
|
33
|
+
@retry_interval = config[:retry_interval] || 3
|
34
|
+
@retry_count = 0
|
35
|
+
end
|
36
|
+
|
37
|
+
def connect
|
38
|
+
begin
|
39
|
+
if @connection.nil?
|
40
|
+
@connection = ::Cassandra.new(@keyspace, seeds)
|
41
|
+
|
42
|
+
# test connecting..
|
43
|
+
@connection.ring
|
44
|
+
return @connection
|
45
|
+
end
|
46
|
+
rescue ThriftClient::NoServersAvailable => e
|
47
|
+
logger :error, e
|
48
|
+
@connection = nil
|
49
|
+
if @retry_count < @max_retry_count
|
50
|
+
@retry_count += 1
|
51
|
+
logger :error, "retry connection..#{@retry_count}"
|
52
|
+
sleep @retry_interval
|
53
|
+
retry
|
54
|
+
end
|
55
|
+
rescue UnAvailableNodeException => e
|
56
|
+
logger :error, e
|
57
|
+
rescue CassandraThrift::InvalidRequestException => e
|
58
|
+
logger :error, e
|
59
|
+
end
|
60
|
+
@connection
|
61
|
+
end
|
62
|
+
|
63
|
+
def closed?
|
64
|
+
@connection.nil?
|
65
|
+
end
|
66
|
+
|
67
|
+
def get_notification(id)
|
68
|
+
n = Dolphin::Models::Cassandra::Notification.new(@connection)
|
69
|
+
n.get(id)
|
70
|
+
end
|
71
|
+
|
72
|
+
def put_event(event)
|
73
|
+
e = Dolphin::Models::Cassandra::Event.new(@connection)
|
74
|
+
e.put(event)
|
75
|
+
end
|
76
|
+
|
77
|
+
def get_event(params)
|
78
|
+
e = Dolphin::Models::Cassandra::Event.new(@connection)
|
79
|
+
e.get(params)
|
80
|
+
end
|
81
|
+
|
82
|
+
def put_notification(id, methods)
|
83
|
+
n = Dolphin::Models::Cassandra::Notification.new(@connection)
|
84
|
+
n.put(id, methods)
|
85
|
+
end
|
86
|
+
|
87
|
+
def delete_notification(notification)
|
88
|
+
n = Dolphin::Models::Cassandra::Notification.new(@connection)
|
89
|
+
n.delete(notification)
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
def seeds
|
94
|
+
@hosts.collect{|host| "#{host}:#{@port}"}
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'sequel'
|
4
|
+
|
5
|
+
module Dolphin
|
6
|
+
module DataStore
|
7
|
+
class Mysql < BaseRdb
|
8
|
+
def connect
|
9
|
+
@connection = current_database
|
10
|
+
case @connection.adapter_scheme
|
11
|
+
when :mysql, :mysql2
|
12
|
+
Sequel::MySQL.default_charset = 'utf8'
|
13
|
+
Sequel::MySQL.default_collate = 'utf8_general_ci'
|
14
|
+
Sequel::MySQL.default_engine = ENV['MYSQL_DB_ENGINE'] || 'InnoDB'
|
15
|
+
|
16
|
+
# this is the mysql adapter specific constants. won't work with mysql2.
|
17
|
+
if @connection.adapter_scheme == :mysql
|
18
|
+
# Disable TEXT to Sequel::SQL::Blob translation.
|
19
|
+
# see the thread: MySQL text turning into blobs
|
20
|
+
# http://groups.google.com/group/sequel-talk/browse_thread/thread/d0f4c85abe9b3227/9ceaf291f90111e6
|
21
|
+
# lib/sequel/adapters/mysql.rb
|
22
|
+
[249, 250, 251, 252].each { |v|
|
23
|
+
Sequel::MySQL::MYSQL_TYPES.delete(v)
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
@connection
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'multi_json'
|
3
|
+
require 'extlib/blank'
|
4
|
+
|
5
|
+
module Dolphin
|
6
|
+
module Helpers
|
7
|
+
module RequestHelper
|
8
|
+
|
9
|
+
def attach_request_params(request)
|
10
|
+
raise "Unsuppoted Content-Type: #{request.env['Content-Type']}" unless request.env['Content-Type'] === 'application/json'
|
11
|
+
@params = {}
|
12
|
+
@notification_id = request.env['X-Notification-Id']
|
13
|
+
@message_type = request.env['X-Message-Type']
|
14
|
+
case request.method
|
15
|
+
when "POST"
|
16
|
+
v = request.input.to_a[0]
|
17
|
+
@params = MultiJson.load(v)
|
18
|
+
when "GET"
|
19
|
+
@params = parse_query_string(request.env["QUERY_STRING"])
|
20
|
+
end
|
21
|
+
@params
|
22
|
+
end
|
23
|
+
|
24
|
+
def run(request, &blk)
|
25
|
+
begin
|
26
|
+
attach_request_params(request)
|
27
|
+
logger :info, @params
|
28
|
+
blk.call
|
29
|
+
rescue LoadError => e
|
30
|
+
logger :error, e
|
31
|
+
[400, {}, MultiJson.dump({
|
32
|
+
:message => 'Request faild.'
|
33
|
+
})]
|
34
|
+
rescue => e
|
35
|
+
logger :error, e
|
36
|
+
[400, {}, MultiJson.dump({
|
37
|
+
:message => e.message
|
38
|
+
})]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def respond_with(data)
|
43
|
+
[200, {}, MultiJson.dump(data)]
|
44
|
+
end
|
45
|
+
|
46
|
+
def required(name)
|
47
|
+
case name
|
48
|
+
when 'notification_id'
|
49
|
+
raise 'Not found X-Notification-Id' if @notification_id.nil?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
def parse_query_string(query_string)
|
55
|
+
params = {}
|
56
|
+
unless query_string.blank?
|
57
|
+
parts = query_string.split('&')
|
58
|
+
parts.collect do |part|
|
59
|
+
key, value = part.split('=')
|
60
|
+
params.store(key, value)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
params
|
64
|
+
end
|
65
|
+
|
66
|
+
def parse_time(time)
|
67
|
+
return nil if time.blank? || !time.is_a?(String)
|
68
|
+
Time.parse(URI.decode(time)).to_time
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|