pmux-gw 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/Gemfile +4 -0
  2. data/LICENSE.txt +22 -0
  3. data/Makefile +18 -0
  4. data/README.md +145 -0
  5. data/Rakefile +4 -0
  6. data/bin/pmux-gw +6 -0
  7. data/examples/password +4 -0
  8. data/examples/pmux-gw.conf +15 -0
  9. data/lib/pmux-gw/application.rb +226 -0
  10. data/lib/pmux-gw/client_context.rb +40 -0
  11. data/lib/pmux-gw/history.rb +149 -0
  12. data/lib/pmux-gw/http_handler.rb +454 -0
  13. data/lib/pmux-gw/logger_wrapper.rb +75 -0
  14. data/lib/pmux-gw/pmux_handler.rb +86 -0
  15. data/lib/pmux-gw/static/css/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  16. data/lib/pmux-gw/static/css/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  17. data/lib/pmux-gw/static/css/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  18. data/lib/pmux-gw/static/css/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  19. data/lib/pmux-gw/static/css/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  20. data/lib/pmux-gw/static/css/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  21. data/lib/pmux-gw/static/css/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  22. data/lib/pmux-gw/static/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  23. data/lib/pmux-gw/static/css/images/ui-icons_222222_256x240.png +0 -0
  24. data/lib/pmux-gw/static/css/images/ui-icons_2e83ff_256x240.png +0 -0
  25. data/lib/pmux-gw/static/css/images/ui-icons_454545_256x240.png +0 -0
  26. data/lib/pmux-gw/static/css/images/ui-icons_888888_256x240.png +0 -0
  27. data/lib/pmux-gw/static/css/images/ui-icons_cd0a0a_256x240.png +0 -0
  28. data/lib/pmux-gw/static/css/jquery-ui-1.9.2.custom.css +462 -0
  29. data/lib/pmux-gw/static/js/jquery-1.8.3.js +9472 -0
  30. data/lib/pmux-gw/static/js/jquery-ui-1.9.2.custom.js +14879 -0
  31. data/lib/pmux-gw/syslog_wrapper.rb +58 -0
  32. data/lib/pmux-gw/template/history.tmpl +101 -0
  33. data/lib/pmux-gw/version.rb +5 -0
  34. data/lib/pmux-gw.rb +12 -0
  35. data/pmux-gw.gemspec +26 -0
  36. data/rpm/Makefile +19 -0
  37. data/rpm/pmux-gw.spec +100 -0
  38. metadata +173 -0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pmux-gw.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Hiroyuki Kakine
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Makefile ADDED
@@ -0,0 +1,18 @@
1
+ RUBY_VERID?=default
2
+
3
+ default: build
4
+
5
+ build: clean
6
+ rake build
7
+
8
+ install: clean
9
+ rake install
10
+
11
+ rpmbuild: clean
12
+ cd rpm && make clean
13
+ rake build
14
+ cd rpm && make RUBY_VERID=$(RUBY_VERID)
15
+
16
+ clean:
17
+ cd rpm && make clean
18
+ rm -rf pkg
data/README.md ADDED
@@ -0,0 +1,145 @@
1
+ ## Pmux::Gateway
2
+
3
+ Pmux gateway is an executor for Pmux (https://github.com/iij/pmux) through HTTP request.
4
+
5
+ ## Requirements
6
+
7
+ * ruby 1.8.7, 1.9.3 or higher
8
+ * pmux
9
+ * gflocator
10
+ * eventmachine
11
+ * em_pessimistic
12
+ * eventmacnine/evma_httpserver https://github.com/eventmachine/evma_httpserver.git
13
+
14
+ ## Installation
15
+
16
+ ### Install dependency programs
17
+
18
+ gem install gflocator
19
+ gem install pmux
20
+ gem install eventmachine
21
+ gem install em_pessimistic
22
+ git clone https://github.com/eventmachine/evma_httpserver.git
23
+ cd evma_httpserver
24
+ rake gem:build
25
+ gem install eventmachine_httpserver-0.2.1.gem
26
+ (I do not recommend "gem install eventmachine_httpserver")
27
+
28
+ ### Install pmux-gw
29
+
30
+ gem install pmux-gw
31
+
32
+ ## Usage
33
+
34
+ pmux-gw [-c config] [-F] [-h]
35
+ -c : specified config file
36
+ -F : foreground mode
37
+ -h : print usage
38
+
39
+ ## Quick start
40
+
41
+ ### Install glusterfs client (http://www.gluster.org/)
42
+
43
+ #### CentOS/RHEL
44
+
45
+ rpm -ivh glusterfs-3.3.1-1.el6.x86_64.rpm
46
+ rpm -ivh glusterfs-fuse-3.3.1-1.el6.x86_64.rpm
47
+
48
+ ### Create an environment that can use pmux
49
+
50
+ mkdir /mnt/volume
51
+ mount -t glusterfs gfsnode1:volume /mnt/volume
52
+ gflocator
53
+ useradd -m admin
54
+ sudo -u admin ssh-keygen
55
+ (copy publickey to glusterfs nodes)
56
+
57
+ ### Make sure that the following command will work
58
+
59
+ sudo -u admin pmux --status -h 127.0.0.1
60
+ sudo -u admin pmux --mapper="ls -al" --storage=glusterfs --locator-host=127.0.0.1 /mnt/volume
61
+
62
+ ### Create an environment that can use pmux-gw
63
+
64
+ mkdir /etc/pmux-gw
65
+ cp <pmux-geteway-install-path>/examples/pmux-gw.conf /etc/pmux-gw/pmux-gw.conf
66
+ cp <pmux-geteway-install-path>/examples/password /etc/pmux-gw/password
67
+ chown -R admin:admin /etc/pmux-gw/password
68
+ chmod -R 600 /etc/pmux-gw/password
69
+ pmux-gw
70
+ curl --basic -u user:pass -iv 'http://127.0.0.1:18080/pmux?mapper=ls&file=/'
71
+
72
+ ## Resource URL
73
+
74
+ ### http://<server_name>:18080/pmux
75
+
76
+ Resource to execute the Pmux
77
+
78
+ #### method
79
+
80
+ * GET
81
+ * POST
82
+
83
+ #### parameters
84
+
85
+ * mapper
86
+ * Specified as a string to exected mapper command.
87
+ * Must be placed on the glusterfs mapper program
88
+ * Be distributed to each glusterfs nodes using ship-file is possible mapper program on glusterfs
89
+ * file
90
+ * Specifies the file to be processed
91
+ * This parameter can be specified multiple
92
+ * Can also be used with file-glob
93
+ * The default program is not distributed reducer
94
+ * Specifies the path on the volume that was mounted
95
+ * file-glob
96
+ * Specified in the expression pattern of the shell files to be processed
97
+ * Specification of the pattern, according to the specifications of the pattern glob (3)
98
+ * This parameter can be specified multiple
99
+ * Can also be used with file-glob
100
+ * The default program is not distributed reducer
101
+ * Specifies the path on the volume that was mounted
102
+ * ship-file
103
+ * Specifies the path of the file to be distributed to each node of the pmux
104
+ * You use this if you want to distribute to each node in the mapper program on the glusterfs pmux
105
+ * Can be distributed more files than mapper script (configuration files, for example)
106
+ * This parameter can be specified multiple
107
+ * Specifies the path on the volume that was mounted
108
+ * reducer
109
+ * specified as a string to be executed reducer command
110
+ * Reducer is not used the program default
111
+ * num-r
112
+ * Reducer to run the program number.
113
+ * Default 0 is used
114
+ * ff
115
+ * Number of tasks if you want to be lumped fine fusion task
116
+ * I do not fusion when omitted
117
+ * storage
118
+ * Specify the type of storage you want to use
119
+ * Default is "glusterfs"
120
+ * locator-host
121
+ * Specifies the hostname or address of the locator to return the position of the entity in the file
122
+ * default is "127.0.0.1"
123
+ * locator-port
124
+ * Specifies the port number of the locator that returns the position of the entity in the file
125
+ * Default is automatically set according to the type of storage
126
+ * detect-error
127
+ * Specifies the on / off
128
+ * if on, Return a response waiting for the completion of the execution of pmux, it checks whether an error occurred.
129
+ * The default is off
130
+
131
+ ### http://<server_name>:18080/history
132
+
133
+ Resources of the request to refer to the history
134
+
135
+ ### http://<server_name>:18080/existence
136
+
137
+ Resources to monitor the presence of process
138
+
139
+ ## Links
140
+ * Glusterfs
141
+ * http://www.gluster.org/
142
+ * Gflocator
143
+ * https://github.com/iij/gflocator
144
+ * Pmux
145
+ * https://github.com/iij/pmux
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new
data/bin/pmux-gw ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pmux-gw'
4
+
5
+ app = Pmux::Gateway::Application.new()
6
+ app.run()
data/examples/password ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ user:
3
+ volume-prefix: volume
4
+ password: pass
@@ -0,0 +1,15 @@
1
+ ---
2
+ bind_address: "0.0.0.0"
3
+ bind_port: 18080
4
+ user: "admin"
5
+ group: "admin"
6
+ glusterfs_root: "/mnt"
7
+ max_tasks: 512
8
+ max_content_size: 1048576
9
+ use_basic_auth: true
10
+ password_file_path: "/etc/pmux-w/password"
11
+ history_file_path: "/var/log/pmux-gw/history"
12
+ log_file_path: "/var/log/pmux-w/pmux-gw.log"
13
+ log_level: "info"
14
+ use_syslog: false
15
+ syslog_facility: "local0"
@@ -0,0 +1,226 @@
1
+ require 'yaml'
2
+ require 'etc'
3
+ require 'optparse'
4
+ require 'fileutils'
5
+
6
+ module Pmux
7
+ module Gateway
8
+ class Application
9
+ def initialize
10
+ @config_file_path = "/etc/pmux-gw/pmux-gw.conf"
11
+ @foreground = false
12
+ @reload = false
13
+ @term = false
14
+ @logger = nil
15
+ end
16
+
17
+ def argparse
18
+ OptionParser.new do |opt|
19
+ opt.on('-c [config_file_path]', '--config [config_file_path]') {|v| @config_file_path = v}
20
+ opt.on('-F', '--foreground') {|v| @foreground = true}
21
+ opt.parse!(ARGV)
22
+ end
23
+ end
24
+
25
+ def daemonize
26
+ if !@foreground
27
+ exit!(0) if Process.fork
28
+ Process.setsid
29
+ exit!(0) if Process.fork
30
+ STDIN.reopen("/dev/null", "r")
31
+ STDOUT.reopen("/dev/null", "w")
32
+ STDERR.reopen("/dev/null", "w")
33
+ end
34
+ end
35
+
36
+ def load_config
37
+ # コンフィグのロード、リロード処理
38
+ # グローバル変数 $config はここで作る
39
+ begin
40
+ $config = YAML.load_file(@config_file_path)
41
+ rescue Errno::ENOENT
42
+ if @logger.nil?
43
+ puts "not found config file (#{@config_file_path})"
44
+ else
45
+ @logger.logging("error", "not found config file (#{@config_file_path})")
46
+ end
47
+ rescue Errno::EACCES
48
+ if @logger.nil?
49
+ puts "can not access config file (#{@config_file_path})"
50
+ else
51
+ @logger.logging("error", "can not access config file (#{@config_file_path})")
52
+ end
53
+ rescue Exception => e
54
+ # コンフィグ読み込み中に予期しないエラーが起きた
55
+ if @logger.nil?
56
+ puts "error occurred in config loading: #{e}"
57
+ puts e.backtrace.join("\n")
58
+ else
59
+ @logger.logging("error", "error occurred in config loading: #{e}")
60
+ @logger.logging("error", e.backtrace.join("\n"))
61
+ end
62
+ end
63
+ # 必要なディレクトリを作る
64
+ # 作れなければ、ログに残して続行
65
+ begin
66
+ histdir = File.dirname($config["history_file_path"])
67
+ logdir = File.dirname($config["log_file_path"])
68
+ FileUtils.mkdir_p(histdir) unless File.exist?(histdir)
69
+ FileUtils.mkdir_p(logdir) unless File.exist?(logdir)
70
+ FileUtils.chown_R($config["user"], $config["group"], histdir)
71
+ FileUtils.chown_R($config["user"], $config["group"], logdir)
72
+ rescue Exception => e
73
+ if @logger.nil?
74
+ puts "error occurred in directory creating: #{e}"
75
+ puts e.backtrace.join("\n")
76
+ else
77
+ @logger.logging("error", "error occurred in directory creating: #{e}")
78
+ @logger.logging("error", e.backtrace.join("\n"))
79
+ end
80
+ end
81
+ end
82
+
83
+ def load_userdb
84
+ $userdb = {} if $userdb.nil?
85
+ # パスワードファイルの読み込み
86
+ # パーミッションをチェックする
87
+ # グローバル変数 $userdb はここで作る
88
+ # $userdbがnilなら空のマップにする
89
+ # 読み込みに何らかの理由で失敗したらログに残し、前の状態から変更しない
90
+ if $config["use_basic_auth"]
91
+ return false if $config["password_file_path"].nil? || $config["password_file_path"] == ""
92
+ stat = File.stat($config["password_file_path"])
93
+ mode = "%o" % stat.mode
94
+ if mode[-3, 3] != "600"
95
+ if @logger.nil?
96
+ puts "password file permission is not 600 (#{$config['password_file_path']})"
97
+ else
98
+ @logger.logging("error", "password file permission is not 600 (#{$config['password_file_path']})")
99
+ end
100
+ return false
101
+ end
102
+ begin
103
+ $userdb = YAML.load_file($config["password_file_path"])
104
+ rescue Errno::ENOENT
105
+ if @logger.nil?
106
+ puts "not found password file (#{$config['password_file_path']})"
107
+ else
108
+ @logger.logging("error", "not found password file (#{$config['password_file_path']})")
109
+ end
110
+ rescue Errno::EACCES
111
+ if @logger.nil?
112
+ puts "can not access password file (#{$config['password_file_path']})"
113
+ else
114
+ @logger.logging("error", "can not access password file (#{$config['password_file_path']})")
115
+ end
116
+ rescue Exception => e
117
+ # password読み込み中に予期しないエラーが起きた
118
+ if @logger.nil?
119
+ puts "error occurred in password loading: #{e}"
120
+ puts e.backtrace.join("\n")
121
+ else
122
+ @logger.logging("error", "error occurred in password loading: #{e}")
123
+ @logger.logging("error", e.backtrace.join("\n"))
124
+ end
125
+ end
126
+ end
127
+ return true
128
+ end
129
+
130
+ def run
131
+ argparse()
132
+ daemonize()
133
+
134
+ # シグナルを受るとフラグを設定するようにする
135
+ Signal.trap(:INT) { @term = true }
136
+ Signal.trap(:TERM) { @term = true }
137
+ Signal.trap(:HUP) { @reload = true }
138
+
139
+ # コンフィグの読み込み
140
+ load_config()
141
+
142
+ # ユーザーとグループの情報を取得する
143
+ user = Etc.getpwnam($config["user"])
144
+ group = Etc.getgrnam($config["group"])
145
+
146
+ # ユーザー情報読み込み
147
+ exit(1) if !load_userdb()
148
+
149
+ # syslogラッパーインスタンス作成
150
+ @syslog = SyslogWrapper.instance()
151
+
152
+ # ロガーラッパーインスタンスの作成と初期化
153
+ @logger = LoggerWrapper.instance()
154
+ @logger.init(@foreground)
155
+
156
+ # 履歴処理インスタンスの作成と初期化
157
+ @history = History.instance()
158
+ @history.init($config["history_file_path"], @logger)
159
+
160
+ begin
161
+ EM.run do
162
+ # httpサーバーを開始する
163
+ EM.start_server($config["bind_address"], $config["bind_port"], HttpHandler)
164
+
165
+ # ユーザー権限を変更
166
+ Process::GID.change_privilege(group.gid)
167
+ Process::UID.change_privilege(user.uid)
168
+
169
+ # pmuxが読み込む環境変数を上書き
170
+ ENV["USER"] = $config["user"]
171
+ ENV["LOGNAME"] = $config["user"]
172
+ ENV["HOME"] = user.dir
173
+
174
+ # リソースリミットの設定
175
+ soft, hard = Process.getrlimit(Process::RLIMIT_NPROC)
176
+ proc_margin = 32
177
+ if soft - proc_margin < $config["max_tasks"] || hard - proc_margin < $config["max_tasks"]
178
+ proc_limit = $config["max_tasks"] + proc_margin
179
+ Process.setrlimit(Process::RLIMIT_NPROC, proc_limit, proc_limit)
180
+ end
181
+
182
+ # syslogのオープン
183
+ @syslog.open($config["use_syslog"], $config["syslog_facility"])
184
+
185
+ # ロガーのオープン
186
+ @logger.open($config["log_file_path"], $config["log_level"])
187
+
188
+ # シグナル受信フラグを処理する定期実行タイマー
189
+ @periodic_timer = EM::PeriodicTimer.new(1) do
190
+ # reloadフラグが立っていればリロードする
191
+ if @reload
192
+ @logger.logging("info", "config reloading...")
193
+ load_config()
194
+ load_userdb()
195
+ # syslogとロガーをリオープンと履歴処理インスタンスのリセット
196
+ @syslog.open($config["use_syslog"], $config["syslog_facility"])
197
+ @logger.open($config["log_file_path"], $config["log_level"])
198
+ @history.reset($config["history_file_path"])
199
+ @reload = false
200
+ end
201
+ # 終了フラグが立っていると終了処理
202
+ if @term
203
+ HttpHandler.set_term()
204
+ if HttpHandler.get_task_cnt() == 0:
205
+ @logger.logging("info", "shutdown...")
206
+ @periodic_timer.cancel()
207
+ @history.finish()
208
+ @logger.close()
209
+ EM.stop()
210
+ end
211
+ end
212
+ sleep(1)
213
+ end
214
+ end
215
+ rescue Exception => e
216
+ # event machine内で予期せぬ例外で死んだ場合ログに残す
217
+ @logger.logging("error", "error occurred in eventmachine: #{e}")
218
+ @logger.logging("error", e.backtrace.join("\n"))
219
+ @history.finish()
220
+ @logger.close()
221
+ raise e
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,40 @@
1
+ module Pmux
2
+ module Gateway
3
+ class ClientContext
4
+ # 処理に必要なクライアントの情報を保持するクラス
5
+ def initialize request, response, mapper, command, detect_error
6
+ @request = request
7
+ @response = response
8
+ @mapper = mapper
9
+ @command = command
10
+ @detect_error = detect_error
11
+ @stdout_data = ""
12
+ @stderr_data = ""
13
+ @pmux_terminated = false
14
+ @force_pmux_terminated = false
15
+ @content_too_big = false
16
+ @pid = nil
17
+ @start_datetime = nil
18
+ @end_datetime = nil
19
+ @peername = nil
20
+ @user = nil
21
+ @status = "init"
22
+ end
23
+
24
+ def set_pmux_handler pmux_handler
25
+ @pmux_handler = pmux_handler
26
+ end
27
+
28
+ def append_stdout_data data
29
+ @stdout_data << data
30
+ end
31
+
32
+ def append_stderr_data data
33
+ @stderr_data << data
34
+ end
35
+
36
+ attr_reader :request, :response, :pmux_handler, :mapper, :command, :detect_error, :stdout_data, :stderr_data
37
+ attr_accessor :pmux_terminated, :force_pmux_terminated, :content_too_big, :pid, :start_datetime, :end_datetime, :status, :peername, :user
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,149 @@
1
+ require 'singleton'
2
+ require 'date'
3
+ require 'cgi'
4
+
5
+ module Pmux
6
+ module Gateway
7
+ class History
8
+ include Singleton
9
+
10
+ @@date_format = "%Y_%m_%d"
11
+
12
+ def init history_file_path, logger
13
+ @syslog_wrapper = SyslogWrapper.instance()
14
+ @history_file_path = history_file_path
15
+ @logger = logger
16
+ @last_rotate = Date.today()
17
+ @file_path = build_file_path(@last_rotate)
18
+ @fp = nil
19
+ @reset = false
20
+ end
21
+
22
+ def reset history_file_path
23
+ @history_file_path = history_file_path
24
+ @reset = true
25
+ end
26
+
27
+ def finish
28
+ @fp.close() if !@fp.nil?
29
+ end
30
+
31
+ def build_file_path d
32
+ return "#{@history_file_path}-#{d.strftime(@@date_format)}"
33
+ end
34
+
35
+ def build_id cc
36
+ # peername, pid, mapper, start_datetimeからユニークなidを作る
37
+ # 内部的に使うだけなので文字列をつなげただけのもの
38
+ return "#{cc.peername}#{cc.pid}#{cc.mapper}#{cc.start_datetime.to_s}"
39
+ end
40
+
41
+ def rotate
42
+ # 日付が変わっていればローテート処理
43
+ if @fp.nil?
44
+ begin
45
+ @fp = open(@file_path, "a")
46
+ rescue Errno::ENOENT => e
47
+ @logger.logging("error", "not found history file (#{@file_path})")
48
+ @logger.logging("error", "error: #{e}")
49
+ rescue Errno::EACCES => e
50
+ @logger.logging("error", "can not access history file (#{@file_path})")
51
+ @logger.logging("error", "error: #{e}")
52
+ end
53
+ end
54
+ new_last_rotate = Date.today()
55
+ if new_last_rotate.day != @last_rotate.day || @reset
56
+ @file_path = build_file_path(@last_rotate)
57
+ if !@fp.nil?
58
+ @fp.close()
59
+ @fp = nil
60
+ else
61
+ @logger.logging("error", "can not close file, file object is nil")
62
+ end
63
+ begin
64
+ @fp = open(@file_path, "a")
65
+ @last_rotate = new_last_rotate
66
+ @reset = false
67
+ rescue Errno::ENOENT => e
68
+ @logger.logging("error", "not found history file (#{@file_path})")
69
+ @logger.logging("error", "error: #{e}")
70
+ rescue Errno::EACCES => e
71
+ @logger.logging("error", "can not access history file (#{@file_path})")
72
+ @logger.logging("error", "error: #{e}")
73
+ end
74
+ end
75
+ end
76
+
77
+ def save cc
78
+ # idを作成する
79
+ id = build_id(cc)
80
+ # rotateを行う
81
+ rotate()
82
+ # historyに追加書き込み
83
+ # フォーマット(タブ区切り):
84
+ # id\tpeername\tpid\tmapper\tstart_datetime\tend_datetime\tstatus\tcommand\n
85
+ if !cc.end_datetime.nil?
86
+ elapsed = ((cc.end_datetime - cc.start_datetime) * 86400).to_f
87
+ else
88
+ elapsed = nil
89
+ end
90
+ if !@fp.nil?
91
+ msg = "#{id}\t#{cc.peername}\t#{cc.user}\t#{cc.pid}\t#{cc.mapper}\t#{cc.start_datetime.to_s}\t#{cc.end_datetime.to_s}\t#{elapsed}\t#{cc.status}\t#{cc.command}\n"
92
+ @fp.write(msg)
93
+ @fp.flush()
94
+ @syslog_wrapper.logging("history", "info", msg);
95
+ else
96
+ @logger.logging("error", "can not write file, file object is nil")
97
+ end
98
+ end
99
+
100
+ def is_skip elems, peername, pid, mapper, start_datetime
101
+ return true if !peername.nil? && peername != "" && /#{peername}/ !~ elems[1]
102
+ return true if !pid.nil? && pid != "" && /#{pid}/ !~ elems[3]
103
+ return true if !mapper.nil? && mapper != "" && /#{mapper}/ !~ elems[4]
104
+ return true if !start_datetime.nil? && start_datetime != "" && start_datetime != elems[5]
105
+ end
106
+
107
+ def load peername, pid, mapper, start_datetime, start_date, end_date, need_command = false, html_escape = false
108
+ # 指定された期間のログファイルからデータを読み込む
109
+ # フォーマット(タブ区切り):
110
+ # id\tpeername\tuser\tpid\tmapper\tstart_datetime\tend_datetime\telapsed\tstatus\tcommand\n
111
+ # ヒストリの順番はhistory_id_orderに保存し
112
+ # ヒストリの内容はhistoryに保存する
113
+ history_id_order = []
114
+ history = {}
115
+ while (end_date - start_date).to_i >= 0 do
116
+ file_path = build_file_path(start_date)
117
+ begin
118
+ open(file_path) {|file|
119
+ while line = file.gets() do
120
+ elems = line.chomp().split("\t")
121
+ next if is_skip(elems, peername, pid, mapper, start_datetime)
122
+ id = elems.shift()
123
+ command = elems.pop() if !need_command
124
+ elems.unshift(start_date.to_s)
125
+ elems.each_with_index { |e, i| elems[i] = CGI.escapeHTML(e) } if html_escape
126
+ if history.key?(id)
127
+ # すでにidが存在しているのでmapだけを更新
128
+ history[id] = elems
129
+ else
130
+ # idが存在していないのでmapを更新してリストにidを追加
131
+ history_id_order << id
132
+ history[id] = elems
133
+ end
134
+ end
135
+ }
136
+ rescue Errno::ENOENT => e
137
+ @logger.logging("info", "not found history file (#{file_path})")
138
+ @logger.logging("info", "error: #{e}")
139
+ rescue Errno::EACCES => e
140
+ @logger.logging("info", "can not access history file (#{file_path})")
141
+ @logger.logging("info", "error: #{e}")
142
+ end
143
+ start_date += 1
144
+ end
145
+ return [ history, history_id_order.reverse! ]
146
+ end
147
+ end
148
+ end
149
+ end