• R/O
  • HTTP
  • SSH
  • HTTPS

pysilhouette.git: Commit

メインリポジトリ


Commit MetaInfo

Revisionb41993da95fe36e39c444538eba39806741bde9e (tree)
Time2009-05-01 07:54:15
AuthorKei Funagayama <kei.funagayama@hde....>
CommiterKei Funagayama

Log Message

first commit(1)

Change Summary

Incremental Difference

--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,7 @@
1+ Pysilhouette Authors
2+ ====================
3+
4+The pysilhouette project was initiated by:
5+ Kei Funagayama <kei@karesansui-project.info>
6+ Junichi Shinohara <junichi@karesansui-project.info>
7+ Kazuya Hayashi <kazuya@karesansui-project.info>
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,4 @@
1+Sat May 28 00:00:00 +0900 2009 Kei Funagayama <kei@karesansui-project.info>
2+
3+ Version 0.6-1 Release
4+
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,65 @@
1+Installing Pysilhouette
2+==========================
3+
4+Copyright (C) 2009 HDE, Inc.
5+
6+Redistributing, copying, modifying of this file is granted with no restriction.
7+
8+Basic Installation
9+================================================================================
10+
11+Pysilhouette is 100% pure Python so it does not require compling.
12+
13+using RPM:
14+ # rpm -ivh pysilhouette-xxxx.rpm
15+
16+using easy_install:
17+ # easy_install pysilhouette
18+
19+Start Up Command/Options
20+================================================================================
21+
22+ # python silhouette.py --help
23+ usage: silhouette.py [options]
24+
25+ options:
26+ --version show program's version number and exit
27+ -h, --help show this help message and exit
28+ -c CONFIG, --config=CONFIG
29+ configuration file
30+ -d, --daemon Daemon startup
31+ -v, --verbose Has not been used.
32+ -p PIDFILE, --pidfile=PIDFILE
33+ process file path
34+ -k, --uniqkey show unique key
35+
36+
37+ - Start up in foreground:
38+ # python silhouette.py --config=silhouette.conf
39+
40+ - Start up in background:
41+ # python silhouette.py --config=silhouette.conf --pidfile=/var/run/silhouetted.pid
42+
43+ - Display the unique key of the server:
44+ # python silhouette.py --config=silhouette.conf --uniqkey
45+
46+Database Settings/Initialization
47+================================================================================
48+
49+silhouette.conf set, run the following command.
50+ # python tool/cleanupdb.py --config=silhouette.conf
51+
52+
53+Configuration Files
54+================================================================================
55+ silhouette.conf.example -> silhouette.conf (needs rename)
56+ whitelist.conf.example -> whitelist.conf (needs rename)
57+ log.conf.example -> log.conf (needs rename)
58+
59+
60+#TODO
61+Configuration
62+Deploying Init Scripts
63+Start/Stop using Init Scripts
64+Configuration Details
65+How to Register Jobs
--- /dev/null
+++ b/INSTALL.ja
@@ -0,0 +1,65 @@
1+Pysilhouetteのインストール
2+==========================
3+
4+Copyright (C) 2009 HDE, Inc.
5+
6+このファイルは、無制限にコピーし再配布が可能です。また、配布して変更も可能です。
7+
8+基本的なインストール
9+================================================================================
10+
11+100% Pure Pythonで構成されたソフトウェアであるためコンパイルは必要ありません。
12+
13+RPMからのインストール
14+ # rpm -ivh pysilhouette-xxxx.rpm
15+
16+EASY_INSTALLからのインストール
17+ # easy_install pysilhouette
18+
19+起動コマンド/オプション
20+================================================================================
21+
22+ # python silhouette.py --help
23+ usage: silhouette.py [options]
24+
25+ options:
26+ --version show program's version number and exit
27+ -h, --help show this help message and exit
28+ -c CONFIG, --config=CONFIG
29+ configuration file
30+ -d, --daemon Daemon startup
31+ -v, --verbose Has not been used.
32+ -p PIDFILE, --pidfile=PIDFILE
33+ process file path
34+ -k, --uniqkey show unique key
35+
36+
37+ - フォアグラウンドで起動する。
38+ # python silhouette.py --config=silhouette.conf
39+
40+ - バックグラウンドで起動する。
41+ # python silhouette.py --config=silhouette.conf --pidfile=/var/run/silhouetted.pid
42+
43+ - 起動するサーバーのユニークキーを調べる
44+ # python silhouette.py --config=silhouette.conf --uniqkey
45+
46+データベースの設定/初期化
47+================================================================================
48+
49+silhouette.confを設定し、以下のコマンドを実行します。
50+ # python tool/cleanupdb.py --config=silhouette.conf
51+
52+
53+設定ファイル一覧
54+================================================================================
55+ silhouette.conf.example -> silhouette.conf(rename)
56+ whitelist.conf.example -> whitelist.conf(rename)
57+ log.conf.example -> log.conf(rename)
58+
59+
60+#TODO
61+設定ファイルの編集
62+起動スクリプトの設置
63+起動スクリプトでの起動停止方法
64+設定ファイルの各項目の説明
65+JOBの登録方法の説明
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
1+Copyright (c) 2009 HDE, Inc.
2+
3+Permission is hereby granted, free of charge, to any person obtaining a copy
4+of this software and associated documentation files (the "Software"), to deal
5+in the Software without restriction, including without limitation the rights
6+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+copies of the Software, and to permit persons to whom the Software is
8+furnished to do so, subject to the following conditions:
9+
10+The above copyright notice and this permission notice shall be included in
11+all copies or substantial portions of the Software.
12+
13+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+THE SOFTWARE.
20+
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,5 @@
1+include MANIFEST.in
2+include LICENSE
3+recursive-include doc *
4+recursive-include tool *
5+recursive-include example *
--- /dev/null
+++ b/README
@@ -0,0 +1,132 @@
1+Abstract/Features
2+================================================================================
3+Pysilhouette is a 100% pure Python daemon which executes background job commands
4+queued in database. Comes with an easy web-based administration interface.
5+
6+Install
7+================================================================================
8+See 'INSTALL'.
9+
10+
11+License/Copying
12+================================================================================
13+
14+Copyright (c) 2009 HDE, Inc.
15+
16+Permission is hereby granted, free of charge, to any person obtaining a copy
17+of this software and associated documentation files (the "Software"), to deal
18+in the Software without restriction, including without limitation the rights
19+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20+copies of the Software, and to permit persons to whom the Software is
21+furnished to do so, subject to the following conditions:
22+
23+The above copyright notice and this permission notice shall be included in
24+all copies or substantial portions of the Software.
25+
26+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
32+THE SOFTWARE.
33+
34+
35+Packages Pysilhouette depends on
36+================================================================================
37+
38+Python
39+SQLAlchemy
40+
41+Downloads for each DBAPI at the time of this writing are as follows:
42+
43+ * Postgres: psycopg2
44+ * SQLite: pysqlite
45+ * MySQL: MySQLDB
46+ * Oracle: cx_Oracle
47+ * MS-SQL, MSAccess: pyodbc (recommended) adodbapi pymssql
48+ * Firebird: kinterbasdb
49+ * Informix: informixdb
50+ * DB2/Informix IDS: ibm-db
51+
52+
53+Directory Structure
54+================================================================================
55+.
56+|-- AUTHORS
57+|-- ChangeLog
58+|-- INSTALL
59+|-- INSTALL.ja
60+|-- LICENSE
61+|-- MANIFEST.in x # for distutils packaging
62+|-- README
63+|-- README.ja # Japanese version of this file.
64+|-- doc
65+| |-- epydoc.cfg # Configuration file for epydoc.
66+| |-- log.conf.example # Example config file for logging function.
67+| |-- rc.d
68+| | `-- init.d
69+| | |-- performerd # init script for the performer daemon
70+| | |-- schedulerd # init script for the scheduler daemon
71+| | `-- silhouetted # init script for the watch daemon
72+| |-- redhat.spec # Spec file for RPM building.
73+| |-- silhouette.conf.example # Example config file for Pysilhouette
74+| |-- sysconfig # System config file.
75+| | `-- silhouetted
76+| |-- whitelist.conf.example # Example config file for whitelist function
77+| `-- wwwpysilhouette # Web interface
78+| |-- config.py
79+| |-- deletejg.py
80+| |-- form.py
81+| |-- getjg.py
82+| |-- index.py
83+| |-- job_delete.py
84+| |-- job_get.py
85+| |-- job_post.py
86+| |-- job_put.py
87+| |-- postjg.py
88+| |-- putjg.py
89+| |-- statjg.py
90+| |-- style.css
91+| |-- util.py
92+| `-- validate.js
93+|-- example # Sample programs using pysilhouette.
94+| |-- dummy.py
95+| |-- insert_dummy.py
96+| |-- sendmail.py
97+| |-- test_failure.py
98+| `-- test_success.py
99+|-- pysilhouette # Main program.
100+| |-- __init__.py
101+| |-- command.py
102+| |-- daemon.py # Daemonizing function.
103+| |-- db # Database related files.
104+| | |-- __init__.py
105+| | |-- access.py # Database operation.
106+| | `-- model.py # Database table model.
107+| |-- log.py
108+| |-- performer.py # Performer daemon (executes job commands)
109+| |-- prep.py # Initialize functions.
110+| |-- scheduler.py # Scheduler daemon (schedules job command executions)
111+| |-- silhouette.py # Watch daemon (watched performer/scheduler daemons)
112+| |-- tests # Testing related files.
113+| | |-- __init__.py
114+| | |-- suite.py
115+| | |-- testprep.py
116+| | |-- testutil.py
117+| | `-- testworker.py
118+| |-- uniqkey.py # Unique key for the instance.
119+| |-- util.py
120+| `-- worker.py
121+|-- setup.cfg # Configuration for distutils packaging.
122+|-- setup.py # Main command for distutils packaging.
123+`-- tool # Tools for development/operation.
124+ |-- cleanupdb.py # Initializes database.
125+ |-- epydoc.sh # Generates javadoc-like documents.
126+ `-- setdummy.py # Sets some dummy job commands.
127+
128+Acknowledgment
129+================================================================================
130+SQLAlchemy and webpy people.
131+HDE, Inc. and other related members.
132+All people in Python community.
--- /dev/null
+++ b/README.ja
@@ -0,0 +1,136 @@
1+概要
2+================================================================================
3+Pysilhouetteは、システムのバックグラウンドでデータベースに登録されている
4+ジョブコマンドを実行するデーモンです。
5+100% Pure Pythonで構成されたソフトウェアです。
6+Webからの簡易UIも装備されています。
7+
8+
9+インストールについて
10+================================================================================
11+
12+同一フォルダにあるINSTALLを参照してください。
13+
14+
15+著作権/ライセンス
16+================================================================================
17+
18+Copyright (c) 2009 HDE, Inc.
19+
20+Permission is hereby granted, free of charge, to any person obtaining a copy
21+of this software and associated documentation files (the "Software"), to deal
22+in the Software without restriction, including without limitation the rights
23+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
24+copies of the Software, and to permit persons to whom the Software is
25+furnished to do so, subject to the following conditions:
26+
27+The above copyright notice and this permission notice shall be included in
28+all copies or substantial portions of the Software.
29+
30+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
33+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
34+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
35+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
36+THE SOFTWARE.
37+
38+
39+依存パッケージ
40+================================================================================
41+
42+Python
43+SQLAlchemy
44+
45+サポートしているDBAPI一覧
46+
47+ * Postgres: psycopg2
48+ * SQLite: pysqlite
49+ * MySQL: MySQLDB
50+ * Oracle: cx_Oracle
51+ * MS-SQL, MSAccess: pyodbc (recommended) adodbapi pymssql
52+ * Firebird: kinterbasdb
53+ * DB2/Informix IDS: ibm-db
54+
55+
56+ディレクトリ構成
57+================================================================================
58+
59+.
60+|-- AUTHORS # 著作者
61+|-- ChangeLog # チェンジログ
62+|-- INSTALL # 英語版インストールマニュアル
63+|-- INSTALL.ja # 日本語版インストールマニュアル
64+|-- LICENSE # ライセンス
65+|-- MANIFEST.in x # distutilsを利用したパッケージングをするのに使用する。
66+|-- README # 英語語版インストールマニュアル
67+|-- README.ja # 日本語版インストールマニュアル
68+|-- doc # ドキュメントや設定ファイル関連置き場
69+| |-- epydoc.cfg # epydoc設定ファイル
70+| |-- log.conf.example # ログ設定ファイルのテンプレート
71+| |-- rc.d # 起動スクリプト
72+| | `-- init.d
73+| | |-- performerd # パフォーマーーデーモンの起動スクリプト
74+| | |-- schedulerd # スケジューラーデーモンの起動スクリプト
75+| | `-- silhouetted # 監視デーモンの起動スクリプト
76+| |-- redhat.spec # RPM用のspecファイル
77+| |-- silhouette.conf.example # Pysilhouette設定ファイルのテンプレート
78+| |-- sysconfig # システム設定ファイル
79+| | `-- silhouetted
80+| |-- whitelist.conf.example # ホワイトリスト設定ファイルのテンプレート
81+| `-- wwwpysilhouette # 簡易WEBインターフェース
82+| |-- config.py
83+| |-- deletejg.py
84+| |-- form.py
85+| |-- getjg.py
86+| |-- index.py
87+| |-- job_delete.py
88+| |-- job_get.py
89+| |-- job_post.py
90+| |-- job_put.py
91+| |-- postjg.py
92+| |-- putjg.py
93+| |-- statjg.py
94+| |-- style.css
95+| |-- util.py
96+| `-- validate.js
97+|-- example # サンプルプログラム関連
98+| |-- dummy.py
99+| |-- insert_dummy.py
100+| |-- sendmail.py
101+| |-- test_failure.py
102+| `-- test_success.py
103+|-- pysilhouette # プログラム本体
104+| |-- __init__.py
105+| |-- command.py
106+| |-- daemon.py # デーモン化で利用する関数群
107+| |-- db # Database関連
108+| | |-- __init__.py
109+| | |-- access.py # Databaseの操作
110+| | `-- model.py # Databaseのテーブルモデル
111+| |-- log.py
112+| |-- performer.py # パフォーマーデーモン(ジョブコマンドを実行する)
113+| |-- prep.py # 初期処理で利用する関数群
114+| |-- scheduler.py # スケジューラーデーモン(ジョブコマンドの実行のスケジューリング)
115+| |-- silhouette.py # 監視デーモン(パフォーマーデーモン、スケジューラーデーモンの監視)
116+| |-- tests # テスト関連
117+| | |-- __init__.py
118+| | |-- suite.py
119+| | |-- testprep.py
120+| | |-- testutil.py
121+| | `-- testworker.py
122+| |-- uniqkey.py # ユニークキー
123+| |-- util.py
124+| `-- worker.py
125+|-- setup.cfg # distutilsを利用したパッケージングをするのに使用する設定ファイル。
126+|-- setup.py # distutilsを利用したパッケージングをするのに使用する実行ファイル。
127+`-- tool # 開発時や運用時に利用するコマンドベースの実行ファイル
128+ |-- cleanupdb.py # Databaseを初期化する実行ファイル
129+ |-- epydoc.sh # Javadoc風なドキュメントを自動生成する実行ファイル
130+ `-- setdummy.py # 複数のダミージョブコマンドを登録する実行ファイル
131+
132+感謝
133+================================================================================
134+SQLAlchemy and webpy people.
135+HDE, Inc. and other related members.
136+All people in Python community.
--- /dev/null
+++ b/doc/epydoc.cfg
@@ -0,0 +1,32 @@
1+[epydoc]
2+
3+modules: pysilhouette
4+output: html
5+target: /var/www/html/pysilhouette-doc
6+verbosity: 0
7+debug: 0
8+simple-term: 0
9+docformat: epytext
10+parse: yes
11+introspect: yes
12+# It should be one of: 'grouped', 'listed', 'included'.
13+inheritance: listed
14+private: yes
15+imports: yes
16+sourcecode: yes
17+include-log: no
18+name: Karesansui Project
19+css: white
20+url: https://sourceforge.jp/projects/pysilhouette/
21+link: <a href="http://sourceforge.jp/projects/pysilhouette/">Pysilhouette Project</a>
22+# "trees.html", "indices.html", or "help.html"
23+#top: os.path
24+#help: my_helpfile.html
25+frames: yes
26+separate-classes: no
27+# "classtree", "callgraph", "umlclass", "all"
28+graph: all
29+dotpath: /usr/bin/dot
30+#pstat: profile.out
31+graph-font: Helvetica
32+graph-font-size: 10
--- /dev/null
+++ b/doc/log.conf.example
@@ -0,0 +1,48 @@
1+[loggers]
2+keys=root,pysilhouette,sqlalchemy
3+
4+[handlers]
5+keys=default,pysilhouette,sqlalchemy
6+
7+[formatters]
8+keys=default,common
9+
10+[formatter_default]
11+format=[%(asctime)s] [%(levelname)s] %(message)s
12+datefmt=%d/%b/%Y:%H:%M:%S
13+
14+[formatter_common]
15+class=logging.Formatter
16+format=[%(asctime)s] [%(levelname)s] [%(process)d] [%(name)s] [%(lineno)d] %(message)s
17+datefmt=%d/%b/%Y:%H:%M:%S (%Z)
18+
19+[handler_default]
20+class=StreamHandler
21+formatter=default
22+args=(sys.stdout,)
23+
24+[handler_pysilhouette]
25+class=handlers.RotatingFileHandler
26+formatter=common
27+args=('/var/log/pysilhouette/application.log', 'a', (5 *1024 *1024), 5)
28+
29+[handler_sqlalchemy]
30+class=handlers.RotatingFileHandler
31+formatter=common
32+args=('/var/log/pysilhouette/database.log', 'a', (5 *1024 *1024), 5)
33+
34+[logger_root]
35+level=INFO
36+handlers=default
37+
38+[logger_pysilhouette]
39+level=DEBUG
40+handlers=pysilhouette
41+propagate=0
42+qualname=pysilhouette
43+
44+[logger_sqlalchemy]
45+level=DEBUG
46+handlers=sqlalchemy
47+propagate=0
48+qualname=sqlalchemy
--- /dev/null
+++ b/doc/rc.d/init.d/performerd
@@ -0,0 +1,78 @@
1+#!/bin/bash
2+#
3+# performerd The stop script of the child process(performer.py) of the Pysilhouette system.
4+#
5+# processname: performerd
6+# pidfile: /var/run/performerd
7+# lockfile: /var/lock/subsys/performerd
8+
9+source /etc/rc.d/init.d/functions
10+
11+# Default value
12+prog="performer"
13+progd="performerd"
14+stop_code='2'
15+fifo='/tmp/pysilhouette.fifo'
16+
17+sysconfig="/etc/sysconfig/${progd}"
18+
19+if [ "x${PYTHON}" = "x" ]; then
20+ PYTHON=`which python`
21+fi
22+
23+# Process id file.
24+pidfile="/var/run/${progd}.pid"
25+
26+desc="${progd} (Daemon)"
27+
28+stop() {
29+ echo -n $"Shutting down $desc: "
30+ if [ ! -e ${pidfile} ]; then
31+ echo "not running..."
32+ return 1
33+ fi
34+ pid=`cat ${pidfile}`
35+ if [ "x${pid}" == "x" ]; then
36+ echo "not running... - not pid"
37+ rm -f ${pidfile}
38+ return 1
39+ fi
40+ [ -w "${fifo}" ] && echo -n $stop_code >> ${fifo}
41+ RETVAL=$?
42+ if [ $RETVAL -eq 0 ]; then
43+ success
44+ else
45+ failure
46+ fi
47+ echo
48+ return $RETVAL
49+}
50+
51+err_msg="Please execute \"/etc/rc.d/init.d/silhouetted $1\""
52+
53+case "$1" in
54+ start)
55+ echo ${err_msg}
56+ RETVAL=1
57+ ;;
58+ stop)
59+ stop
60+ ;;
61+ restart|reload)
62+ echo ${err_msg}
63+ RETVAL=1
64+ ;;
65+ condrestart)
66+ echo ${err_msg}
67+ RETVAL=1
68+ ;;
69+ status)
70+ status ${progd}
71+ RETVAL=$?
72+ ;;
73+ *)
74+ echo $"Usage: $0 {stop|status}"
75+ RETVAL=1
76+esac
77+
78+exit $RETVAL
--- /dev/null
+++ b/doc/rc.d/init.d/schedulerd
@@ -0,0 +1,77 @@
1+#!/bin/bash
2+#
3+# schedulerd The stop script of the child process(scheduler.py) of the Pysilhouette system.
4+#
5+# processname: schedulerd
6+# pidfile: /var/run/schedulerd.pid
7+# lockfile: /var/lock/subsys/schedulerd
8+
9+source /etc/rc.d/init.d/functions
10+
11+# Default value
12+prog="scheduler"
13+progd="schedulerd"
14+
15+sysconfig="/etc/sysconfig/${progd}"
16+
17+if [ "x${PYTHON}" = "x" ]; then
18+ PYTHON=`which python`
19+fi
20+
21+# Process id file.
22+pidfile="/var/run/${progd}.pid"
23+
24+desc="${progd} (Daemon)"
25+
26+stop() {
27+ echo -n $"Shutting down $desc: "
28+ if [ ! -e ${pidfile} ]; then
29+ echo "not running..."
30+ return 1
31+ fi
32+# pid=`cat ${pidfile}`
33+# if [ "x${pid}" == "x" ]; then
34+# echo "not running... - not pid"
35+# rm -f ${pidfile}
36+# return 1
37+# fi
38+ killproc -p ${pidfile} -15
39+# kill ${pid}
40+ RETVAL=$?
41+ if [ $RETVAL -eq 0 ]; then
42+ success
43+ else
44+ failure
45+ fi
46+ echo
47+ return $RETVAL
48+}
49+
50+err_msg="Please execute \"/etc/rc.d/init.d/silhouetted $1\""
51+
52+case "$1" in
53+ start)
54+ echo ${err_msg}
55+ RETVAL=1
56+ ;;
57+ stop)
58+ stop
59+ ;;
60+ restart|reload)
61+ echo ${err_msg}
62+ RETVAL=1
63+ ;;
64+ condrestart)
65+ echo ${err_msg}
66+ RETVAL=1
67+ ;;
68+ status)
69+ status ${progd}
70+ RETVAL=$?
71+ ;;
72+ *)
73+ echo $"Usage: $0 {stop|status}"
74+ RETVAL=1
75+esac
76+
77+exit $RETVAL
--- /dev/null
+++ b/doc/rc.d/init.d/silhouetted
@@ -0,0 +1,171 @@
1+#!/bin/bash
2+#
3+# silhouetted The startup script for the Pysilhouette system.
4+#
5+# chkconfig: 345 97 03
6+# description: Pysilhouette is an application running in the background system.
7+#
8+# processname: silhouetted
9+# config: /etc/sysconfig/silhouetted
10+# pidfile: /var/run/silhouetted.pid
11+# /var/run/schedulerd.pid
12+# /var/run/performerd.pid
13+# lockfile: /var/lock/subsys/silhouetted
14+# /var/lock/subsys/schedulerd
15+# /var/lock/subsys/performerd
16+
17+source /etc/rc.d/init.d/functions
18+source /etc/sysconfig/network
19+
20+# For SELinux we need to use 'runuser' not 'su'
21+if [ -x /sbin/runuser ]; then
22+ SU=runuser
23+else
24+ SU=su
25+fi
26+
27+# Check that networking is up.
28+[ ${NETWORKING} = "no" ] && exit 1
29+
30+#Default value
31+prog="silhouette"
32+progd="silhouetted"
33+app="pysilhouette"
34+sch_progd='schedulerd'
35+per_progd='performerd'
36+
37+sysconfig="/etc/sysconfig/${progd}"
38+
39+# Read configuration
40+[ -r "${sysconfig}" ] && source "${sysconfig}"
41+
42+if [ "x${PYTHON}" == "x" ]; then
43+ PYTHON=`which python`
44+fi
45+
46+# Config file.
47+conf="/etc/opt/${app}/${prog}.conf"
48+
49+# Process id file.
50+pidfile="/var/run/${progd}.pid"
51+lockfile="/var/lock/subsys/${progd}"
52+sch_pidfile="/var/run/${sch_progd}.pid"
53+sch_lockfile="/var/lock/subsys/${sch_progd}"
54+per_pidfile="/var/run/${per_progd}.pid"
55+per_lockfile="/var/lock/subsys/${per_progd}"
56+
57+# Daemon mode.
58+extra_args=""
59+if [ "x${DAEMON}" = "xyes" ]; then
60+ extra_args=${extra_args}" -d"
61+fi
62+
63+# Debug mode.
64+if [ "x${DEBUG}" = "xyes" ]; then
65+ extra_args=${extra_args}" -v"
66+fi
67+
68+desc="${progd} (Daemon)"
69+
70+# options
71+CMD_ARGS="-p ${pidfile} -c ${conf} ${extra_args}"
72+
73+
74+start() {
75+ echo -n $"Starting $desc: "
76+ if [ -e ${pidfile} ]; then
77+ echo "already running..."
78+ return 1
79+ fi
80+
81+ touch ${pidfile} ${sch_pidfile} ${per_pidfile}
82+ chown ${USER}:${GROUP} ${pidfile} ${sch_pidfile} ${per_pidfile}
83+ if [ "x${PYTHON_SEARCH_PATH}" != "x" ]; then
84+ env="PYTHONPATH=${PYTHON_SEARCH_PATH}:\$PYTHONPATH"
85+ fi
86+ ${SU} -l ${USER} -c "${env} ${PYTHON} ${PREFIX}/opt/pysilhouette/bin/${prog}.py ${CMD_ARGS}"
87+ RETVAL=$?
88+ [ ${RETVAL} -eq 0 ] && touch ${lockfile} ${sch_lockfile} ${per_lockfile}
89+ [ ${RETVAL} -eq 0 ] && success || failure
90+ echo ""
91+ return ${RETVAL}
92+}
93+
94+silhouetted_stop() {
95+ echo -n $"Shutting down $desc: "
96+ if [ ! -e ${pidfile} ]; then
97+ echo "not running..."
98+ return 1
99+ fi
100+ pid=`cat ${pidfile}`
101+ if [ "x${pid}" == "x" ]; then
102+ echo "not running... - not pid"
103+ rm -f ${pidfile}
104+ return 1
105+ fi
106+ killproc -p ${pidfile} -15
107+ echo
108+ RETVAL=$?
109+ return ${RETVAL}
110+}
111+
112+stop() {
113+ silhouetted_stop
114+ SIL_RETVAL=$?
115+ if [ ${SIL_RETVAL} -eq 0 ]; then
116+ rm -f ${lockfile}
117+ rm -f ${pidfile}
118+ fi
119+ eval "/etc/rc.d/init.d/${sch_progd} stop"
120+ SCH_RETVAL=$?
121+ if [ ${SCH_RETVAL} -eq 0 ]; then
122+ rm -f ${sch_lockfile}
123+ rm -f ${sch_pidfile}
124+ fi
125+ eval "/etc/rc.d/init.d/${per_progd} stop"
126+ PER_RETVAL=$?
127+ if [ ${PER_RETVAL} -eq 0 ]; then
128+ rm -f ${per_lockfile}
129+ rm -f ${per_pidfile}
130+ fi
131+ # The return code of the performer demon is the first digit.
132+ # The return code of the scheduler demon is the second digit.
133+ # The return code of the silhouetted demon is the third digit.
134+ # All stop functions return only the exit code of 0(Normal) or 1(Abnormal).
135+ RETVAL=`expr ${SIL_RETVAL} \* 100 + ${SCH_RETVAL} \* 10 + ${PER_RETVAL}`
136+ return ${RETVAL}
137+}
138+
139+restart() {
140+ stop
141+ sleep 1
142+ start
143+}
144+
145+
146+case "$1" in
147+ start)
148+ start
149+ ;;
150+ stop)
151+ stop
152+ ;;
153+ restart|reload)
154+ restart
155+ ;;
156+ condrestart)
157+ [ -e ${lockfile} ] && restart
158+ RETVAL=$?
159+ ;;
160+ status)
161+ status ${progd}
162+ eval "/etc/rc.d/init.d/${sch_progd} status"
163+ eval "/etc/rc.d/init.d/${per_progd} status"
164+ RETVAL=$?
165+ ;;
166+ *)
167+ echo $"Usage: $0 {start|stop|restart|condrestart|status}"
168+ RETVAL=1
169+esac
170+
171+exit $RETVAL
--- /dev/null
+++ b/doc/redhat.spec
@@ -0,0 +1,134 @@
1+%define name pysilhouette
2+%define version 0.6
3+%define release 1
4+%define date %(echo `LANG=C date +%%Y%%m%%d%%H%%M%%S`)
5+
6+%define _prefix /opt
7+
8+%define __python $(which python)
9+%define __app pysilhouette
10+%define __prog silhouette
11+%define __progd %{__prog}d
12+%define __sysconfdir %{_sysconfdir}/opt/%{__app}
13+%define __bindir %{_prefix}/%{__app}/bin
14+%define __datadir %{_var}/opt/%{__app}
15+%define _defaultdocdir %{_prefix}/%{__app}/share/doc
16+%define python_sitelib %{_prefix}/%{__app}/lib/python
17+
18+%define _user pysilhouette
19+%define _group pysilhouette
20+%define _uid_min 300
21+%define _uid_max 350
22+
23+Summary: Damon System is an application running in the background.
24+Summary(ja): オープンソースのジョブ実行管理アプリケーション
25+Name: %{name}
26+Version: %{version}
27+Release: %{release}.%{date}
28+Source0: %{name}-%{version}.tar.gz
29+License: MIT/X Consortium License
30+Group: System Environment/Daemons
31+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
32+Prefix: %{_prefix}
33+BuildArch: noarch
34+Vendor: HDE Package Maintainer <info@hde.co.jp>
35+Url: http://sourceforge.jp/projects/pysilhouette/
36+
37+%description
38+Pysilhouette is an application running in the background system.
39+A system executes the job command registered into the database.
40+100% Pure Python.
41+
42+%prep
43+%setup
44+
45+%build
46+python setup.py build
47+
48+%install
49+python setup.py install --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --home=%{_prefix}/%{__app}
50+
51+mkdir -p $RPM_BUILD_ROOT%{__sysconfdir}
52+mkdir -p $RPM_BUILD_ROOT%{__bindir}
53+mkdir -p $RPM_BUILD_ROOT%{__datadir}
54+mkdir -p $RPM_BUILD_ROOT/etc/rc.d/init.d
55+mkdir -p $RPM_BUILD_ROOT/etc/sysconfig
56+mkdir -p $RPM_BUILD_ROOT/var/log/%{__app}/
57+
58+install -c -m 644 doc/log.conf.default $RPM_BUILD_ROOT%{__sysconfdir}/log.conf.default
59+install -c -m 644 doc/log.conf.default $RPM_BUILD_ROOT%{__sysconfdir}/log.conf
60+
61+install -c -m 644 doc/%{__prog}.conf.default $RPM_BUILD_ROOT%{__sysconfdir}/%{__prog}.conf.default
62+install -c -m 644 doc/%{__prog}.conf.default $RPM_BUILD_ROOT%{__sysconfdir}/%{__prog}.conf
63+
64+install -c -m 644 doc/whitelist.conf.default $RPM_BUILD_ROOT%{__sysconfdir}/whitelist.conf.default
65+install -c -m 644 doc/whitelist.conf.default $RPM_BUILD_ROOT%{__sysconfdir}/whitelist.conf
66+
67+install -c -m 644 doc/rc.d/init.d/* $RPM_BUILD_ROOT%{_initrddir}/
68+install -c -m 644 doc/sysconfig/%{__progd} $RPM_BUILD_ROOT/etc/sysconfig/%{__progd}
69+
70+chmod +x $RPM_BUILD_ROOT%{python_sitelib}/%{__app}/%{__prog}.py
71+%{__ln_s} %{python_sitelib}/%{__app}/%{__prog}.py $RPM_BUILD_ROOT%{__bindir}
72+
73+%clean
74+rm -rf $RPM_BUILD_ROOT
75+
76+%pre
77+# Add group
78+getent group | %{__grep} "^%{_group}:" >/dev/null 2>&1
79+if [ $? -ne 0 ]; then
80+ __uid=%{_uid_min}
81+ while test ${__uid} -le %{_uid_max}
82+ do
83+ getent group | %{__grep} "^[^:]*:x:${__uid}:" >/dev/null 2>&1
84+ if [ $? -ne 0 ]; then
85+ _gid=${__uid}
86+ break
87+ fi
88+ __uid=`expr ${__uid} + 1`
89+ done
90+ /usr/sbin/groupadd -g ${_gid} -f %{_group}
91+fi
92+
93+# Add user
94+getent passwd | %{__grep} "^%{_user}:" >/dev/null 2>&1
95+if [ $? -ne 0 ]; then
96+ __uid=%{_uid_min}
97+ while test ${__uid} -le %{_uid_max}
98+ do
99+ getent passwd | %{__grep} "^[^:]*:x:${__uid}:" >/dev/null 2>&1
100+ if [ $? -ne 0 ]; then
101+ _uid=${__uid}
102+ break
103+ fi
104+ __uid=`expr ${__uid} + 1`
105+ done
106+ /usr/sbin/useradd -c "pysilhouette" -u ${_uid} -g %{_group} -s /bin/false -r %{_user} 2> /dev/null || :
107+fi
108+
109+%postun
110+if [ $1 = 0 ]; then
111+ /usr/sbin/userdel %{_user} 2> /dev/null || :
112+ /usr/sbin/groupdel %{_group} 2> /dev/null || :
113+fi
114+
115+
116+%files -f INSTALLED_FILES
117+%defattr(-,root,root)
118+%doc doc
119+%dir %attr(0755, root, root) %{__sysconfdir}
120+%attr(0755, root, root) %{_initrddir}/*
121+%attr(0644, root, root) %config(noreplace) %{__sysconfdir}/log.conf
122+%attr(0644, root, root) %{__sysconfdir}/log.conf.default
123+%attr(0644, root, root) %config(noreplace) %{__sysconfdir}/%{__prog}.conf
124+%attr(0644, root, root) %{__sysconfdir}/%{__prog}.conf.default
125+%attr(0644, root, root) %config(noreplace) %{__sysconfdir}/whitelist.conf
126+%attr(0644, root, root) %{__sysconfdir}/whitelist.conf.default
127+%attr(0644, root, root) %config(noreplace) /etc/sysconfig/%{__progd}
128+%{__bindir}/%{__prog}.py
129+%dir %{__datadir}
130+%dir /var/log/%{__app}
131+
132+%changelog
133+* Sat May 28 2009 HDE Package Maintainer <info@hde.co.jp> - 0.6-1
134+- new version.
--- /dev/null
+++ b/doc/silhouette.conf.example
@@ -0,0 +1,73 @@
1+##
2+# Environment variables
3+#
4+env.python=/usr/bin/python
5+env.sys.log.conf.path=/etc/opt/pysilhouette/log.conf
6+# To set a unique key, please.
7+# command : python uniqkey.py
8+env.uniqkey=aaaaaaaa-0000-0000-0000-aaaaaaaaaaaa
9+
10+##
11+# deamon
12+daemon.stdin=/dev/null
13+daemon.stdout=/var/log/pysilhouette/stdout.log
14+daemon.stderr=/var/log/pysilhouette/stderr.log
15+
16+##
17+# observer
18+observer.target.python=/usr/bin/python
19+observer.target.scheduler=/opt/pysilhouette/lib/python/pysilhouette/scheduler.py
20+observer.target.performer=/opt/pysilhouette/lib/python/pysilhouette/performer.py
21+observer.restart.count=5
22+# - Clear intervals. 0=Infinite
23+observer.restart.count.clear.time=300
24+# - Check interval
25+observer.check.interval=5
26+# - Output status information
27+observer.status.path=/var/opt/pysilhouette/status
28+# - mkfifo
29+observer.mkfifo.path=/tmp/pysilhouette.fifo
30+observer.mkfifo.start.code=0
31+observer.mkfifo.ignore.code=1
32+observer.mkfifo.stop.code=2
33+observer.mkfifo.user.name=pysilhouette
34+observer.mkfifo.group.name=pysilhouette
35+observer.mkfifo.perms=0666
36+
37+##
38+# scheduler
39+scheduler.interval=10
40+
41+##
42+# job
43+job.popen.env.lang=C
44+job.popen.timeout=3600
45+job.popen.waittime=10
46+# 1 or Other
47+job.whitelist.flag=1
48+job.whitelist.path=/etc/opt/pysilhouette/whitelist.conf
49+
50+##
51+# Database RFC-1738 style URLs.
52+# - driver://username:password@host:port/database
53+#
54+# postgresql :
55+# database.url=postgres://silhouette:<password>@localhost:5432/silhouette
56+#
57+# mysql :
58+# database.url=mysql://localhost/silhouette
59+# or
60+# database.url=mysql://silhouette:<password>@localhost/silhouette
61+#
62+# oracle
63+# database.url=oracle://scott:tiger@dsn - TNS
64+# database.url=oracle://scott:tiger@127.0.0.1:1521/sidname - host/port/SID
65+#
66+# sqlite
67+# database.url=sqlite:////absolute/path/to/silhouette.db - absolute path
68+# database.url=sqlite:///relative/path/to/silhouette.db - relative path
69+# database.url=sqlite:// - in memory
70+# database.url=sqlite://:memory: - in memory
71+#
72+#database.url=sqlite:///:memory:
73+database.url=sqlite:////var/opt/pysilhouette/pysilhouette.db
--- /dev/null
+++ b/doc/sysconfig/silhouetted
@@ -0,0 +1,9 @@
1+# pysilhouetted Counfigure.
2+PREFIX=""
3+USER="root"
4+GROUP="root"
5+PYTHON="/usr/bin/python"
6+DAEMON="yes"
7+DEBUG="no"
8+# PYTHON_SEARCH_PATH={BAR}:{FOO}
9+PYTHON_SEARCH_PATH="/opt/hde/lib/python:/opt/pysilhouette/lib/python"
--- /dev/null
+++ b/example/dummy.py
@@ -0,0 +1,19 @@
1+#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+
4+import sys
5+
6+from pysilhouette.command import Command
7+
8+class DummyCommand(Command):
9+
10+ def process(self):
11+ import time
12+ for x in xrange(0, 10):
13+ time.sleep(1)
14+ self.up_progress(10)
15+
16+ return True
17+if __name__ == '__main__':
18+ dc = DummyCommand()
19+ sys.exit(dc.run())
--- /dev/null
+++ b/example/insert_dummy.py
@@ -0,0 +1,28 @@
1+#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+
4+import os
5+import sys
6+
7+from pysilhouette.command import Command
8+from pysilhouette.db.model import JobGroup, Job
9+
10+class DummyCommand(Command):
11+
12+ def insert_data(self):
13+ self.db.get_metadata().drop_all()
14+ self.db.get_metadata().create_all()
15+
16+ jg_name = u'JobGroup-Dummy'
17+ jg_ukey = unicode(self.cf['env.uniqkey'], "utf-8")
18+ j_name = u'Job-Dummy'
19+ j_order = 0
20+ j_cmd = unicode( os.path.dirname(__file__) + "/dummy.py", "utf-8")
21+ jg = JobGroup(jg_name, jg_ukey)
22+ jg.jobs.append(Job(j_name, j_order, j_cmd))
23+ self.session.save(jg)
24+ self.session.commit()
25+
26+if __name__ == '__main__':
27+ dc = DummyCommand()
28+ sys.exit(dc.insert_data())
--- /dev/null
+++ b/example/sendmail.py
@@ -0,0 +1,116 @@
1+#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+#
4+# This file is part of Pysilhouette.
5+#
6+# Copyright (c) 2009 HDE, Inc.
7+#
8+# Permission is hereby granted, free of charge, to any person obtaining a copy
9+# of this software and associated documentation files (the "Software"), to deal
10+# in the Software without restriction, including without limitation the rights
11+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+# copies of the Software, and to permit persons to whom the Software is
13+# furnished to do so, subject to the following conditions:
14+#
15+# The above copyright notice and this permission notice shall be included in
16+# all copies or substantial portions of the Software.
17+#
18+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+# THE SOFTWARE.
25+#
26+
27+"""
28+@author: Kei Funagayama <kei@karesansui-project.info>
29+"""
30+
31+import sys
32+import os
33+import smtplib
34+from optparse import OptionParser, OptionValueError
35+from email.MIMEText import MIMEText
36+from email.Utils import formatdate
37+from email.Header import Header
38+
39+
40+usage = "%prog [options]"
41+version = '%prog 0.1'
42+
43+charset = 'us-ascii'
44+encode = 'utf-8'
45+
46+def getopts():
47+ optp = OptionParser(usage=usage, version=version)
48+ optp.add_option('-f', '--from', dest='ofrom', help='Mail From')
49+ optp.add_option('-t', '--to', dest='oto', help='RCPT TO')
50+ optp.add_option('-s', '--subject', dest='osubject', help='Subject')
51+ optp.add_option('-m', '--msg', dest='omsg', help='E-mail message (--bodyfile is the priority.)')
52+ optp.add_option('-b', '--bodyfile', dest='obodyfile', help='E-mail files')
53+
54+ optp.add_option('-n', '--hostname', dest='ohostname', help='The host name of the SMTP server')
55+ optp.add_option('-p', '--port', dest='oport', help='The port of the SMTP server')
56+ optp.add_option('-c', '--charset', dest='ocharset', help='Mail character set (Default: us-ascii)')
57+ optp.add_option('-e', '--encode', dest='oencode', help='Enter the character code (Default: utf-8)')
58+
59+ return optp.parse_args()
60+
61+def chkopts(opts):
62+ if not opts.ofrom:
63+ print >>sys.stderr, 'sendmail: --from is required.'
64+ return True
65+ if not opts.oto:
66+ print >>sys.stderr, 'sendmail: --to is required.'
67+ return True
68+ if not opts.osubject:
69+ print >>sys.stderr, 'sendmail: --subject is required.'
70+ return True
71+ if not opts.omsg and not opts.obodyfile:
72+ print >>sys.stderr, 'sendmail: --msg or --bodyfile are required.'
73+ return True
74+ if not opts.omsg and opts.obodyfile:
75+ if not os.path.exists(opts.obodyfile):
76+ print >>sys.stderr, 'sendmail: --bodyfile specified in the file does not exist.'
77+ return True
78+ if not opts.ohostname:
79+ print >>sys.stderr, 'sendmail: --hostname is required.'
80+ return True
81+ if not opts.oport:
82+ print >>sys.stderr, 'sendmail: --port is required.'
83+ return True
84+ if not opts.ocharset:
85+ opts.ocharset = charset
86+ if not opts.oencode:
87+ opts.oencode = encode
88+
89+ return False
90+
91+def sendmail(opts):
92+ if opts.obodyfile:
93+ fp = open(opts.obodyfile, 'r')
94+ body = unicode(fp.read(), opts.oencode).encode(opts.ocharset,'replace')
95+ else:
96+ body = unicode(opts.omsg, opts.oencode).encode(opts.ocharset,'replace')
97+
98+ msg = MIMEText(body, 'plain', opts.ocharset)
99+ msg['Subject'] = Header(unicode(opts.osubject, opts.oencode), opts.ocharset)
100+ msg['From'] = opts.ofrom
101+ msg['To'] = opts.oto
102+ msg['Date'] = formatdate()
103+
104+ s = smtplib.SMTP()
105+ s.connect(opts.ohostname, opts.oport)
106+ s.sendmail(opts.ofrom, [opts.oto], msg.as_string())
107+ s.close()
108+
109+def main():
110+ (opts, args) = getopts()
111+ if chkopts(opts):
112+ return 1
113+ sendmail(opts)
114+
115+if __name__ == '__main__':
116+ sys.exit(main())
--- /dev/null
+++ b/example/test_failure.py
@@ -0,0 +1,18 @@
1+#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+
4+import sys
5+import os
6+
7+fpath = '/tmp/pysilhouette_job_failure.txt'
8+
9+if __name__ == '__main__':
10+ fp= open(fpath, 'w')
11+ fp.write('Failure!!\n')
12+ fp.close()
13+ try:
14+ # os.unlink(fpath)
15+ raise Exception('Failure!!')
16+ except Exception, e:
17+ print >>sys.stderr, 'stderr : %s!!' % e.args
18+ sys.exit(1)
--- /dev/null
+++ b/example/test_success.py
@@ -0,0 +1,15 @@
1+#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+
4+import sys
5+import os
6+
7+fpath = '/tmp/pysilhouette_job_success.txt'
8+
9+if __name__ == '__main__':
10+ fp= open(fpath, 'w')
11+ fp.write('Success!!\n')
12+ fp.close()
13+ os.unlink(fpath)
14+ print >>sys.stdout, 'stdout : Success!!'
15+ sys.exit(0)
--- /dev/null
+++ b/pysilhouette/__init__.py
@@ -0,0 +1,36 @@
1+#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+#
4+# This file is part of Pysilhouette.
5+#
6+# Copyright (c) 2009 HDE, Inc.
7+#
8+# Permission is hereby granted, free of charge, to any person obtaining a copy
9+# of this software and associated documentation files (the "Software"), to deal
10+# in the Software without restriction, including without limitation the rights
11+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+# copies of the Software, and to permit persons to whom the Software is
13+# furnished to do so, subject to the following conditions:
14+#
15+# The above copyright notice and this permission notice shall be included in
16+# all copies or substantial portions of the Software.
17+#
18+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+# THE SOFTWARE.
25+#
26+
27+"""
28+@author: Kei Funagayama <kei@karesansui-project.info>
29+"""
30+
31+__version__ = '0.6'
32+__release__ = '1'
33+__app__ = 'pysilhouette'
34+
35+class SilhouetteException(StandardError):
36+ pass
--- /dev/null
+++ b/pysilhouette/command.py
@@ -0,0 +1,178 @@
1+#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+#
4+# This file is part of Pysilhouette.
5+#
6+# Copyright (c) 2009 HDE, Inc.
7+#
8+# Permission is hereby granted, free of charge, to any person obtaining a copy
9+# of this software and associated documentation files (the "Software"), to deal
10+# in the Software without restriction, including without limitation the rights
11+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+# copies of the Software, and to permit persons to whom the Software is
13+# furnished to do so, subject to the following conditions:
14+#
15+# The above copyright notice and this permission notice shall be included in
16+# all copies or substantial portions of the Software.
17+#
18+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+# THE SOFTWARE.
25+#
26+
27+"""
28+@author: Kei Funagayama <kei@karesansui-project.info>
29+"""
30+
31+import os
32+import logging
33+import sys
34+import traceback
35+
36+import pysilhouette.prep
37+import pysilhouette.log
38+from pysilhouette.util import is_empty
39+from pysilhouette.db import Database
40+from pysilhouette.db.model import reload_mappers
41+from pysilhouette.db.access import \
42+ get_progress as dba_get_progress, \
43+ up_progress as dba_up_progress
44+
45+# lib
46+def dict2command(cmd, options={}):
47+ """ Provides simple method to build up
48+ "/usr/bin/foo --key1=value1 --key2=value2"
49+ styled command line.
50+ @param cmd: command path to be executed.
51+ @type cmd: string
52+ @param options: dictionary which contains
53+ {key: value} to be transformed into
54+ "--key=value". Defaults to {}.
55+ If you like to pass a flag styled option(like --flag),
56+ just put None into value.
57+ @type options: dictionary
58+ @return: command line in single string.
59+ """
60+ if is_empty(cmd):
61+ raise CommandException("command not found. - cmd=%s" % cmd)
62+
63+ ret = ""
64+ for x in options.keys():
65+ if options[x] is None:
66+ ret += "--%s " % x
67+ else:
68+ ret += "--%s=%s " % (x, options[x])
69+ return "%s %s" % (cmd.strip(), ret.strip())
70+
71+# public
72+class CommandException(Exception):
73+ """Command execution error.
74+ """
75+ pass
76+
77+class Command:
78+ """Command is a class designed to assist implmentation of CLI programs.
79+ Expacted to be inherited.
80+ """
81+
82+ cf = None
83+ logger = None
84+
85+ def __init__(self):
86+ if os.environ.has_key('PYSILHOUETTE_CONF') is False:
87+ print >>sys.stderr, '[Error] "PYSILHOUETTE_CONF" did not exist in the environment.'
88+ sys.exit(1)
89+
90+ self.cf = pysilhouette.prep.readconf(os.environ['PYSILHOUETTE_CONF'])
91+ if self.logger is None:
92+ pysilhouette.log.reload_conf(self.cf["env.sys.log.conf.path"])
93+ self.logger = logging.getLogger('pysilhouette.command')
94+
95+ try:
96+ self.db = Database(self.cf['database.url'],
97+ encoding="utf-8",
98+ convert_unicode=True,
99+ assert_unicode=False, # product
100+ #assert_unicode='warn', # dev
101+ echo = True,
102+ echo_pool = True,
103+ )
104+
105+ reload_mappers(self.db.get_metadata())
106+ self.session = self.db.get_session()
107+ except Exception, e:
108+ print >>sys.stderr, '[Error] Initializing a database error - %s' % str(e.args)
109+ self.logger.error('Initializing a database error - %s' % str(e.args))
110+ t_logger = logging.getLogger('pysilhouette_traceback')
111+ t_logger.error(traceback.format_exc())
112+ sys.exit(1)
113+
114+ def _pre(self):
115+ if os.environ.has_key('JOB_ID') is False:
116+ self.logger.debug('"JOB_ID" did not exist in the environment.')
117+ self.job_id = None
118+ else:
119+ self.job_id = os.environ['JOB_ID']
120+ self.logger.debug('Command JOB_ID=%s' % self.job_id)
121+
122+ return True
123+
124+ def _post(self):
125+ return True
126+
127+ def run(self):
128+ try:
129+ try:
130+ try:
131+ if self._pre() is False:
132+ CommandException("Error running in _pre().")
133+ if self.process() is False:
134+ CommandException("Error running in process().")
135+ if self._post() is False:
136+ CommandException("Error running in _post().")
137+
138+ return 0
139+
140+ except CommandException, e:
141+ self.logger.error("Command execution error - %s" % str(e.args))
142+ print >>sys.stderr, _("Command execution error - %s") % str(e.args)
143+ raise
144+ except:
145+ self.session.rollback()
146+ return 1
147+ finally:
148+ self.session.commit()
149+
150+ def process(self):
151+ raise CommandException('Please use the override.')
152+
153+ def up_progress(self, r):
154+ """ Increments progress counter by r.
155+ If the counter reaches 100, then it will be 100 regardless of the value of r.
156+ @param r: Amount to increment.
157+ @type r: int
158+ """
159+ if self.job_id is None:
160+ self.logger.warn('up_progress called but no job ID is assigned with this object. Ignoring.')
161+ return None
162+ else:
163+ return dba_up_progress(self.session, self.job_id, r)
164+
165+ def get_progress(self):
166+ """ Returns the current progress counter.
167+ @return: Progress counter value on success (0-100). -1 on failure.
168+ """
169+ if self.job_id is None:
170+ self.logger.warn('get_progress called but no job ID is assigned with this object. Ignoring and returning -1.')
171+ return -1
172+ else:
173+ ret = dba_get_progress(self.session, self.job_id)
174+ self.session.commit()
175+ return ret
176+
177+if __name__ == '__main__':
178+ pass
--- /dev/null
+++ b/pysilhouette/daemon.py
@@ -0,0 +1,240 @@
1+#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+#
4+# This file is part of Pysilhouette.
5+#
6+# Copyright (c) 2009 HDE, Inc.
7+#
8+# Permission is hereby granted, free of charge, to any person obtaining a copy
9+# of this software and associated documentation files (the "Software"), to deal
10+# in the Software without restriction, including without limitation the rights
11+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+# copies of the Software, and to permit persons to whom the Software is
13+# furnished to do so, subject to the following conditions:
14+#
15+# The above copyright notice and this permission notice shall be included in
16+# all copies or substantial portions of the Software.
17+#
18+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+# THE SOFTWARE.
25+#
26+
27+"""
28+@author: Kei Funagayama <kei@karesansui-project.info>
29+"""
30+
31+import time
32+import os
33+import sys
34+import math
35+import subprocess
36+import signal
37+import logging
38+
39+import pysilhouette
40+import pysilhouette.log
41+from pysilhouette.util import astrftime
42+from pysilhouette.util import kill_proc
43+
44+def observer(opts, cf):
45+ """scheduler and performer manage and monitor.
46+ @param opts: command options
47+ @type opts: dict(OptionParser)
48+ @param cf: Configuration info
49+ @type cf: dict
50+ @rtype: int
51+ @return: exit code
52+ """
53+ def scheduler():
54+ cmd = [cf['observer.target.python'], cf['observer.target.scheduler']]
55+ if cmd_args:
56+ cmd.extend(cmd_args)
57+ if opts.daemon is True:
58+ cmd.extend(['-p', os.path.abspath(os.path.dirname(opts.pidfile)) + '/schedulerd.pid'])
59+
60+ logger.debug('scheduler:popen - cmd=%s' % cmd)
61+ return subprocess.Popen(args=cmd,
62+ close_fds=True,
63+ env=this_env,
64+ shell=False)
65+
66+ def performer():
67+ cmd = [cf['observer.target.python'], cf['observer.target.performer']]
68+ if cmd_args:
69+ cmd.extend(cmd_args)
70+ if opts.daemon is True:
71+ cmd.extend(['-p', os.path.abspath(os.path.dirname(opts.pidfile)) + '/performerd.pid'])
72+
73+ logger.debug('performer:popen - cmd=%s' % cmd)
74+ return subprocess.Popen(args=cmd,
75+ close_fds=True,
76+ env=this_env,
77+ shell=False)
78+
79+ def status(count, status, default, force=False):
80+ try:
81+ if (force is True) or (status != count):
82+ status = count
83+ fp = open(cf["observer.status.path"], "w")
84+ try:
85+ logger.debug("%d/%d" % (count, default))
86+ fp.write("%d/%d" % (count, default))
87+ except:
88+ fp.close()
89+ else:
90+ pass
91+
92+ except IOError, ioe:
93+ logger.error("Failed to write status. file=%s - %s" \
94+ % (cf["observer.status.path"], str(ioe.args)))
95+
96+ ##
97+ logger = logging.getLogger('pysilhouette.observer')
98+
99+ # environment
100+ this_env = os.environ
101+ cmd_args = ['-c', opts.config]
102+
103+ if opts.verbose is True:
104+ cmd_args.append('-v')
105+ if opts.daemon is True:
106+ cmd_args.append('-d')
107+
108+ spoint = time.time()
109+
110+ default_count = int(cf['observer.restart.count']) # default
111+ status_count = default_count # status
112+ count = default_count # now
113+
114+ sd = pf = None
115+
116+ pf = performer() # start!!
117+ logger.info('performer : [start] - pid=%s, count=%s/%s'
118+ % (pf.pid, count, cf['observer.restart.count']))
119+ sd = scheduler() # start!!
120+ logger.info('scheduler : [start] - pid=%s, count=%s/%s'
121+ % (sd.pid, count, cf['observer.restart.count']))
122+
123+ status(count, status_count, default_count, True)
124+
125+ try:
126+ while True:
127+ if not pf.poll() is None:
128+ logger.debug('return code=%d' % pf.returncode)
129+ logger.info('performer : [stop] - pid=%s, count=%s/%s'
130+ % (pf.pid, count, cf['observer.restart.count']))
131+ pf = performer() # restart
132+ count -= 1
133+ logger.info('performer : [start] - pid=%s, count=%s/%s'
134+ % (pf.pid, count, cf['observer.restart.count']))
135+ else:
136+ logger.info('performer [running] - pid=%s, count=%s/%s'
137+ % (pf.pid, count, cf['observer.restart.count']))
138+
139+ if not sd.poll() is None:
140+ logger.debug('return code=%d' % sd.returncode)
141+ logger.info('scheduler : [stop] - pid=%s, count=%s/%s'
142+ % (sd.pid, count, cf['observer.restart.count']))
143+ sd = scheduler() # restart
144+ count -= 1
145+ logger.info('scheduler : [start] - pid=%s, count=%s/%s'
146+ % (sd.pid, count, cf['observer.restart.count']))
147+ else:
148+ logger.info('scheduler [running] - pid=%s, count=%s/%s'
149+ % (sd.pid, count, cf['observer.restart.count']))
150+
151+ # status output
152+ status(count, status_count, default_count, False)
153+
154+ if ( 0 < int(cf['observer.restart.count.clear.time']) ) and (count <= 0):
155+ epoint = time.time()
156+ interval = int(math.ceil(epoint) - math.floor(spoint))
157+
158+ logger.error('observer restart count reached the value specified in config. Checking interval time. observer.restart.count=%d interval=%d/%s'
159+ % (cf['observer.restart.count'], interval, cf['observer.restart.count.clear.time']))
160+
161+ if interval < int(cf['observer.restart.count.clear.time']):
162+ # Failed 'observer.restart.count' times in 'observer.restart.count.clear.time' seconds.
163+ logger.error('observer restarted %s times in count.clear.time seconds interval. Recognizing as failure. Exiting.'
164+ % cf['observer.restart.count'])
165+ break
166+ else:
167+ # Failed 'observer.restart.count' times in an interval longer than
168+ # 'observer.restart.count.clear.time' seconds. Clearing counter.
169+ spoint = time.time()
170+ count = int(cf['observer.restart.count'])
171+ logger.info('observer restarted %s times, but in not short time. Clearing count. start time %s'
172+ % (cf['observer.restart.count'], astrftime(spoint)))
173+
174+ time.sleep(int(cf['observer.check.interval']))
175+
176+ # -- end while
177+
178+ finally:
179+ # destory
180+ if not sd is None:
181+ if kill_proc(sd) is True:
182+ logger.info('KILL %d: killing scheduler succeeded.' % sd.pid)
183+ else:
184+ logger.info('KILL %d: killing scheduler failed.' % sd.pid)
185+
186+ if not pf is None:
187+ if kill_proc(pf) is True:
188+ logger.info('KILL %d: killing performer succeeded.' % pf.pid)
189+ else:
190+ logger.info('KILL %d: killing performer failed.' % pf.pid)
191+
192+ return 1
193+
194+# -- deamon
195+def daemonize(stdin, stdout, stderr, pidfile):
196+ """The state is changed into daemon.
197+ """
198+ logger = logging.getLogger('pysilhouette.daemonize')
199+
200+ try:
201+ pid = os.fork()
202+ if pid > 0: sys.exit(0)
203+ except OSError, e:
204+ print >>sys.stderr, 'fork #1 failed: (%d) %s\n' % (e.errno, e.strerror)
205+ logger.error('fork #1 failed: (%d) %s\n' % (e.errno, e.strerror))
206+ sys.exit(1)
207+ os.chdir('/')
208+ os.umask(0)
209+ os.setsid()
210+ try:
211+ pid = os.fork()
212+ if pid > 0: sys.exit(0)
213+ except OSError, e:
214+ print >>sys.stderr, 'fork #2 failed: (%d) %s\n' % (e.errno, e.strerror)
215+ logger.error('fork #2 failed: (%d) %s\n' % (e.errno, e.strerror))
216+ sys.exit(1)
217+ # Write pid.
218+ pid=''
219+ try:
220+ f = file(pidfile, 'w')
221+ pid = os.getpid()
222+ f.write('%d' % pid)
223+ f.close()
224+ except IOError:
225+ print >>sys.stderr, 'file=%s - daemonize: failed to write pid to %s' % (pidfile , pid)
226+ logger.error('file=%s - daemonize: failed to write pid to %s' % (pidfile , pid))
227+ sys.exit(1)
228+
229+ for f in sys.stdout, sys.stderr: f.flush()
230+ sin = file(stdin, 'r')
231+ sout = file(stdout, 'a+')
232+ serr = file(stderr, 'a+')
233+ os.dup2(sin.fileno(), sys.stdin.fileno())
234+ os.dup2(sout.fileno(), sys.stdout.fileno())
235+ os.dup2(serr.fileno(), sys.stderr.fileno())
236+
237+ return pid
238+
239+if __name__ == '__main__':
240+ pass
--- /dev/null
+++ b/pysilhouette/db/__init__.py
@@ -0,0 +1,178 @@
1+#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+#
4+# This file is part of Pysilhouette.
5+#
6+# Copyright (c) 2009 HDE, Inc.
7+#
8+# Permission is hereby granted, free of charge, to any person obtaining a copy
9+# of this software and associated documentation files (the "Software"), to deal
10+# in the Software without restriction, including without limitation the rights
11+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+# copies of the Software, and to permit persons to whom the Software is
13+# furnished to do so, subject to the following conditions:
14+#
15+# The above copyright notice and this permission notice shall be included in
16+# all copies or substantial portions of the Software.
17+#
18+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+# THE SOFTWARE.
25+#
26+
27+"""
28+@author: Kei Funagayama <kei@karesansui-project.info>
29+"""
30+
31+import logging
32+
33+from sqlalchemy import create_engine, MetaData
34+from sqlalchemy.orm import sessionmaker, mapper, \
35+ clear_mappers, relation, scoped_session
36+from sqlalchemy.orm.exc import UnmappedInstanceError
37+
38+from pysilhouette.db.model import reload_mappers
39+from pysilhouette import SilhouetteException
40+
41+class SilhouetteDBException(SilhouetteException):
42+ """Database running error.
43+ """
44+ pass
45+
46+class Database:
47+ """TODO
48+ """
49+
50+ __engine = None
51+ __metadata = None
52+ __Session = None
53+
54+ def __init__(self, *args, **kwargs):
55+ self.get_engine(*args, **kwargs)
56+ self.create_metadata(self.__engine)
57+
58+ def get_engine(self, *args, **kwargs):
59+ if not self.__engine:
60+ self.__engine = create_engine(*args, **kwargs)
61+ return self.__engine
62+
63+ def create_metadata(self, bind=None, reflect=False):
64+ return MetaData(bind,reflect)
65+
66+ def get_metadata(self):
67+ if not self.__metadata:
68+ self.__metadata = self.create_metadata(self.__engine)
69+ return self.__metadata
70+
71+ def get_session(self):
72+ if self.__Session is None:
73+ self.__Session = sessionmaker(bind=self.__engine)
74+ return self.__Session()
75+
76+
77+def dbsave(func):
78+ """TODO
79+ """
80+
81+ def wrapper(*args, **kwargs):
82+ logger = logging.getLogger('pysilhouette.db')
83+ session = args[0]
84+ model = args[1]
85+ model_name = repr(model).split("<")[0]
86+ model_id = model.id
87+ try:
88+ func(*args, **kwargs)
89+ except UnmappedInstanceError, ui:
90+ logger.error(('Data to insert is failed, '
91+ 'Invalid value was inputed. '
92+ '- %s=%s, error=%s') % (model_name, model_id, ''.join(ui)))
93+ raise SilhouetteDBException(('Data to insert is failed, '
94+ 'Invalid value was inputed. '
95+ '- %s=%s, error=%s') % (model_name, model_id, ''.join(ui)))
96+
97+ num = len(session.new)
98+ if not num:
99+ logger.warn('Data has not been changed. - %s=%s' % (model_name, model_id))
100+ return num # The retrun value assume zero
101+
102+ logger.debug('Data to insert is succeeded. - %s=%s' % (model_name, model_id))
103+ return num
104+
105+ wrapper.__name__ = func.__name__
106+ wrapper.__dict__ = func.__dict__
107+ wrapper.__doc__ = func.__doc__
108+ return wrapper
109+
110+def dbupdate(func):
111+ """TODO
112+ """
113+
114+ def wrapper(*args, **kwargs):
115+ logger = logging.getLogger('pysilhouette.db')
116+ session = args[0]
117+ model = args[1]
118+ model_name = repr(model).split("<")[0]
119+ model_id = model.id
120+ try:
121+ func(*args, **kwargs)
122+ except UnmappedInstanceError, ui:
123+ logger.error(('Data to update is failed, '
124+ 'Invalid value was inputed '
125+ '- %s=%s, error=%s') % (model_name, model_id, ''.join(ui)))
126+ raise SilhouetteDBException(('Data to update is failed, '
127+ 'Invalid value was inputed. '
128+ '- %s=%s, error=%s') % (model_name, model_id, ''.join(ui)))
129+
130+ num = len(session.dirty)
131+ if not num:
132+ logger.warn('Data has not been changed. - %s=%s' % (model_name, model_id))
133+ return num # The retrun value assume zero
134+
135+ logger.debug('Data to update is succeeded. - %s=%s' % (model_name, model_id))
136+ return num
137+
138+ wrapper.__name__ = func.__name__
139+ wrapper.__dict__ = func.__dict__
140+ wrapper.__doc__ = func.__doc__
141+ return wrapper
142+
143+def dbdelete(func):
144+ """TODO
145+ """
146+
147+ def wrapper(*args, **kwargs):
148+ logger = logging.getLogger('pysilhouette.db')
149+ session = args[0]
150+ model = args[1]
151+ model_name = repr(model).split("<")[0]
152+ model_id = model.id
153+ try:
154+ func(*args, **kwargs)
155+ except UnmappedInstanceError, ui:
156+ logger.error(('Data to delete is failed, '
157+ 'Invalid value was inputed '
158+ '- %s=%s, error=%s') % (model_name, model_id, ''.join(ui)))
159+ raise SilhouetteDBException(('Data to delete is failed, '
160+ 'Invalid value was inputed. '
161+ '- %s=%s, error=%s') % (model_name, model_id, ''.join(ui)))
162+
163+ num = len(session.deleted)
164+ if not num:
165+ logger.warn('Data has not been changed. - %s=%s' % (model_name, model_id))
166+ return num # The retrun value assume zero
167+
168+ logger.debug('Data to delete is succeeded. - %s=%s' % (model_name, model_id))
169+ return num
170+
171+ wrapper.__name__ = func.__name__
172+ wrapper.__dict__ = func.__dict__
173+ wrapper.__doc__ = func.__doc__
174+ return wrapper
175+
176+
177+if __name__ == '__main__':
178+ pass
--- /dev/null
+++ b/pysilhouette/db/access.py
@@ -0,0 +1,146 @@
1+#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+#
4+# This file is part of Pysilhouette.
5+#
6+# Copyright (c) 2009 HDE, Inc.
7+#
8+# Permission is hereby granted, free of charge, to any person obtaining a copy
9+# of this software and associated documentation files (the "Software"), to deal
10+# in the Software without restriction, including without limitation the rights
11+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+# copies of the Software, and to permit persons to whom the Software is
13+# furnished to do so, subject to the following conditions:
14+#
15+# The above copyright notice and this permission notice shall be included in
16+# all copies or substantial portions of the Software.
17+#
18+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+# THE SOFTWARE.
25+#
26+
27+"""
28+@author: Kei Funagayama <kei@karesansui-project.info>
29+"""
30+
31+import sqlalchemy
32+import sqlalchemy.orm
33+from pysilhouette.db import dbsave, dbupdate, dbdelete
34+from pysilhouette.db.model import JobGroup, Job, JOBGROUP_STATUS
35+
36+# JobGroup Table
37+def jobgroup_findbyall(session, desc=False):
38+ if desc is True:
39+ return session.query(JobGroup).order_by(JobGroup.id.desc()).all()
40+ else:
41+ return session.query(JobGroup).order_by(JobGroup.id.asc()).all()
42+
43+def jobgroup_findbyall_limit(session, limit, desc=False):
44+ if desc is True:
45+ return session.query(JobGroup).order_by(JobGroup.modified.desc()).all()[:limit]
46+ else:
47+ return session.query(JobGroup).order_by(JobGroup.modified.asc()).all()[:limit]
48+
49+def jobgroup_findbystatus(session, status=JOBGROUP_STATUS['PEND']):
50+ return session.query(JobGroup).filter(
51+ JobGroup.status == status).order_by(JobGroup.id.asc()).all()
52+
53+def jobgroup_findbyuniqkey(session, uniq_key):
54+ if uniq_key:
55+ return session.query(JobGroup).filter(
56+ JobGroup.uniq_key == uniq_key).all()
57+ else:
58+ return None
59+
60+def jobgroup_findbyid(session, jgid, uniq_key):
61+ try:
62+ return session.query(JobGroup).filter(
63+ JobGroup.id == jgid).filter(JobGroup.uniq_key == uniq_key).one()
64+ except sqlalchemy.orm.exc.NoResultFound, nrf:
65+ return None
66+
67+
68+def jobgroup_update(session, m_jg, status, autocommit=True):
69+ m_jg.status = status
70+ ret = update(session, m_jg)
71+ if autocommit is True:
72+ session.commit()
73+ return ret
74+
75+# Edit
76+@dbsave
77+def save(session, model):
78+ return session.save(model)
79+
80+@dbupdate
81+def update(session, model):
82+ return session.update(model)
83+
84+@dbdelete
85+def delete(session, model):
86+ return session.delete(model)
87+
88+
89+# Job Table
90+def job_findbyjobgroup_id(session, jgid, desc=False):
91+ _q = session.query(Job).filter(Job.jobgroup_id == jgid)
92+ if desc:
93+ _r = _q.order_by(Job.order.desc()).all()
94+ else:
95+ _r = _q.order_by(Job.order.asc()).all()
96+ return _r
97+
98+def job_update(session, m_job, status=None, autocommit=True):
99+ if not status is None:
100+ m_job.status = status
101+
102+ ret = update(session, m_job)
103+
104+ if autocommit is True:
105+ session.commit()
106+ return ret
107+
108+def job_result_action(session, job, info, autocommit=True):
109+ job.action_exit_code = info['r_code']
110+ job.action_stdout = info['stdout']
111+ job.action_stderr = info['stderr']
112+
113+ ret = job_update(session, job)
114+
115+ if autocommit is True:
116+ session.commit()
117+
118+ return ret
119+
120+def job_result_rollback(session, job, info, autocommit=True):
121+ job.rollback_exit_code = info['r_code']
122+ job.rollback_stdout = info['stdout']
123+ job.rollback_stderr = info['stderr']
124+
125+ ret = job_update(session, job)
126+
127+ if autocommit is True:
128+ session.commit()
129+
130+ return ret
131+
132+# progress
133+def get_progress(session, job_id):
134+ job = session.query(Job).filter(Job.id == job_id).one()
135+ return job.progress
136+
137+def up_progress(session, job_id, up):
138+ job = session.query(Job).filter(Job.id == job_id).one()
139+ job.progress += up
140+ if 100 < job.progress:
141+ job.progress = 100
142+
143+ job_update(session, job)
144+
145+if __name__ == '__main__':
146+ pass
--- /dev/null
+++ b/pysilhouette/db/model.py
@@ -0,0 +1,253 @@
1+#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+#
4+# This file is part of Pysilhouette.
5+#
6+# Copyright (c) 2009 HDE, Inc.
7+#
8+# Permission is hereby granted, free of charge, to any person obtaining a copy
9+# of this software and associated documentation files (the "Software"), to deal
10+# in the Software without restriction, including without limitation the rights
11+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+# copies of the Software, and to permit persons to whom the Software is
13+# furnished to do so, subject to the following conditions:
14+#
15+# The above copyright notice and this permission notice shall be included in
16+# all copies or substantial portions of the Software.
17+#
18+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+# THE SOFTWARE.
25+#
26+
27+"""
28+@author: Kei Funagayama <kei@karesansui-project.info>
29+"""
30+
31+import sqlalchemy
32+import sqlalchemy.exc
33+from sqlalchemy.orm import mapper, relation, clear_mappers
34+
35+from pysilhouette.util import is_empty
36+
37+# Job Constant
38+_RES_CREATING = u'100' #: Creating
39+_RES_PENDING = u'101' #: Pending
40+_RES_RUNNING = u'102' #: Running
41+_RES_ROLLBACK = u'103' #: Rollback running
42+_RES_NORMAL_END = u'200' #: Normal end
43+_RES_ROLLBACK_SUCCESSFUL_COMPLETION = u'201' #: Rollback successful completion
44+_RES_ABNORMAL_TERMINATION = u'500' #: Abnormal termination
45+_RES_ROLLBACK_ABEND = u'501' #: Rollback abend
46+_RES_APP_ERROR = u'502' #: Application error
47+_RES_WHITELIST_ERROR = u'503' #: Whitelist error
48+
49+#: Action command status.
50+ACTION_STATUS = {
51+ 'PEND' : _RES_PENDING,
52+ 'RUN' : _RES_RUNNING,
53+ 'OK' : _RES_NORMAL_END,
54+ 'NG' : _RES_ABNORMAL_TERMINATION,
55+ 'WHITELIST' : _RES_WHITELIST_ERROR,
56+ }
57+#: Rollback command status.
58+ROLLBACK_STATUS = {
59+ 'RUN' : _RES_ROLLBACK,
60+ 'OK' : _RES_ROLLBACK_SUCCESSFUL_COMPLETION,
61+ 'NG' : _RES_ROLLBACK_ABEND,
62+ 'WHITELIST' : _RES_WHITELIST_ERROR,
63+ }
64+
65+#: Jobgroup status
66+JOBGROUP_STATUS = {
67+ 'PEND' : _RES_PENDING,
68+ 'RUN' : _RES_RUNNING,
69+ 'OK' : _RES_NORMAL_END,
70+ 'NG' : _RES_ABNORMAL_TERMINATION,
71+ 'APPERR' : _RES_APP_ERROR,
72+ }
73+
74+#: Jobgroup Table instance.
75+def get_jobgroup_table(metadata=sqlalchemy.MetaData()):
76+ return sqlalchemy.Table('jobgroup', metadata,
77+ sqlalchemy.Column('id', sqlalchemy.Integer, primary_key=True,
78+ autoincrement=True),
79+ sqlalchemy.Column('name', sqlalchemy.String(32), nullable=False),
80+ sqlalchemy.Column('uniq_key', sqlalchemy.Unicode(36), nullable=False),
81+ sqlalchemy.Column('finish_command', sqlalchemy.String(1024)),
82+ sqlalchemy.Column('status', sqlalchemy.Unicode(3), nullable=False,
83+ default=JOBGROUP_STATUS['PEND']),
84+ sqlalchemy.Column('register', sqlalchemy.String(32), nullable=True),
85+ sqlalchemy.Column('created', sqlalchemy.DateTime,
86+ default=sqlalchemy.func.now()),
87+ sqlalchemy.Column('modified', sqlalchemy.DateTime,
88+ default=sqlalchemy.func.now(),
89+ onupdate=sqlalchemy.func.current_timestamp()),
90+ )
91+
92+#: Job Table instance.
93+def get_job_table(metadata=sqlalchemy.MetaData()):
94+ return sqlalchemy.Table('job', metadata,
95+ sqlalchemy.Column('id', sqlalchemy.Integer, primary_key=True,
96+ autoincrement=True),
97+ sqlalchemy.Column('jobgroup_id', sqlalchemy.Integer,
98+ sqlalchemy.ForeignKey('jobgroup.id'),
99+ index=True, nullable=False),
100+ sqlalchemy.Column('name', sqlalchemy.String(32), nullable=False),
101+ sqlalchemy.Column('order', sqlalchemy.Integer, nullable=False),
102+ sqlalchemy.Column('action_command', sqlalchemy.String(1024), nullable=False),
103+ sqlalchemy.Column('rollback_command', sqlalchemy.String(1024)),
104+ sqlalchemy.Column('status', sqlalchemy.Unicode(3), nullable=False,
105+ default=ACTION_STATUS['PEND']),
106+ sqlalchemy.Column('action_exit_code', sqlalchemy.Integer),
107+ sqlalchemy.Column('action_stdout', sqlalchemy.TEXT),
108+ sqlalchemy.Column('action_stderr', sqlalchemy.TEXT),
109+ sqlalchemy.Column('rollback_exit_code', sqlalchemy.Integer),
110+ sqlalchemy.Column('rollback_stdout', sqlalchemy.TEXT),
111+ sqlalchemy.Column('rollback_stderr', sqlalchemy.TEXT),
112+ sqlalchemy.Column('progress', sqlalchemy.Integer, nullable=False,
113+ default=0),
114+ sqlalchemy.Column('created', sqlalchemy.DateTime,
115+ default=sqlalchemy.func.now()),
116+ sqlalchemy.Column('modified', sqlalchemy.DateTime,
117+ default=sqlalchemy.func.now(),
118+ onupdate=sqlalchemy.func.current_timestamp()),
119+ )
120+
121+def reload_mappers(metadata):
122+ """all model mapper reload.
123+ @param metadata: reload MetaData
124+ @type metadata: sqlalchemy.schema.MetaData
125+ """
126+ t_jobgroup = get_jobgroup_table(metadata)
127+ t_job = get_job_table(metadata)
128+ try:
129+ mapper(JobGroup, t_jobgroup, properties={'jobs': relation(Job)})
130+ #mapper(JobGroup, t_jobgroup, properties={'jobs': relation(Job, backref='job_group')})
131+ mapper(Job, t_job)
132+ except sqlalchemy.exc.ArgumentError, ae:
133+ clear_mappers()
134+ mapper(JobGroup, t_jobgroup, properties={'jobs': relation(Job)})
135+ #mapper(JobGroup, t_jobgroup, properties={'jobs': relation(Job, backref='job_group')})
136+ mapper(Job, t_job)
137+
138+class Model(object):
139+ """Model base class of all.
140+ """
141+ def utf8(self, column):
142+ if hasattr(self, column):
143+ ret = getattr(self, column)
144+ if isinstance(ret, unicode):
145+ return ret.encode('utf-8')
146+ elif isinstance(ret, str):
147+ return ret
148+ else:
149+ return str(ret)
150+ else:
151+ return 'not found.' # TODO: raise
152+
153+class JobGroup(Model):
154+ """JobGroup Table class.
155+ """
156+
157+ def __init__(self, name, uniq_key):
158+ self.name = name
159+ self.uniq_key = uniq_key
160+
161+ def __repr__(self):
162+ return "JobGroup<'%s','%s'>" % (self.name, self.uniq_key)
163+
164+class Job(Model):
165+ """Job Table class.
166+ """
167+
168+ #: Maximum number of characters to stdout.
169+ STD_OUTPUT_LIMIT = 4096
170+
171+ def __init__(self, name, order, action_command):
172+ self.name = name
173+ self.order = order
174+ self.action_command = action_command
175+
176+ def __repr__(self):
177+ return "Job<'%s','%s','%s'>" % \
178+ (self.name, self.order, self.action_command)
179+
180+ def is_rollback(self):
181+ return not is_empty(self.rollback_command)
182+
183+if __name__ == '__main__':
184+ """Testing
185+ """
186+ import sqlalchemy.orm
187+ bind_name = 'sqlite:///:memory:'
188+
189+ engine = sqlalchemy.create_engine(bind_name, encoding="utf8", convert_unicode=True)
190+ engine.echo = True
191+ metadata = sqlalchemy.MetaData(bind=engine)
192+
193+ t_jg = get_jobgroup_table(metadata)
194+ t_job = get_job_table(metadata)
195+
196+ sqlalchemy.orm.mapper(JobGroup, t_jg,
197+ properties={'jobs': sqlalchemy.orm.relation(Job)})
198+ sqlalchemy.orm.mapper(Job, t_job)
199+
200+ metadata.drop_all()
201+ metadata.create_all()
202+
203+ Session = sqlalchemy.orm.sessionmaker(bind=engine)
204+ session = Session()
205+
206+ # INSERT
207+ jg = JobGroup(u'All Update', '192.168.0.100')
208+ jg.jobs.append(Job(
209+ u'Yum Update MySQL',
210+ '0',
211+ '/usr/bin/yum update mysql'))
212+ jg.jobs.append(Job(
213+ u'Yum Update PostgreSQL',
214+ '1',
215+ '/usr/bin/yum update postgresql'))
216+ jg.jobs.append(Job(
217+ u'Yum Update httpd',
218+ '2',
219+ '/usr/bin/yum update httpd'))
220+ session.save(jg)
221+ jg1 = JobGroup(u'get date', '172.16.0.123')
222+ jg1.jobs.append(Job(u'get date','0', '/bin/date'))
223+ jg2 = JobGroup(u'get route', '172.16.0.123')
224+ jg2.jobs.append(Job(u'get route','0', '/sbin/route'))
225+ jg3 = JobGroup(u'get ping', '172.16.0.123')
226+ jg3.jobs.append(Job(u'get ping','0', '/bin/ping 172.16.0.1'))
227+ session.add_all([jg1,jg2,jg3])
228+ session.commit()
229+
230+ # SELECT One
231+ jg = session.query(JobGroup).filter(JobGroup.name == u'All Update').one()
232+ print jg.__repr__()
233+ for jg in session.query(JobGroup).all():
234+ print jg.__repr__()
235+
236+ # UPDATE
237+ jg = session.query(JobGroup).filter(JobGroup.name == u'All Update').one()
238+ jg.name = 'All Update - edit'
239+ for j in jg.jobs:
240+ j.name = 'All Update - edit'
241+ session.update(jg)
242+ session.commit()
243+
244+ # DELETE + Manual CASCADE
245+ jgs = session.query(JobGroup).\
246+ filter(JobGroup.name.in_([u'get date', \
247+ u'get route', \
248+ u'get ping'])).all()
249+ for jg in jgs:
250+ for j in jg.jobs:
251+ session.delete(j)
252+ session.delete(jg)
253+ session.commit()
--- /dev/null
+++ b/pysilhouette/log.py
@@ -0,0 +1,64 @@
1+#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+#
4+# This file is part of Pysilhouette.
5+#
6+# Copyright (c) 2009 HDE, Inc.
7+#
8+# Permission is hereby granted, free of charge, to any person obtaining a copy
9+# of this software and associated documentation files (the "Software"), to deal
10+# in the Software without restriction, including without limitation the rights
11+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+# copies of the Software, and to permit persons to whom the Software is
13+# furnished to do so, subject to the following conditions:
14+#
15+# The above copyright notice and this permission notice shall be included in
16+# all copies or substantial portions of the Software.
17+#
18+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+# THE SOFTWARE.
25+#
26+
27+"""
28+@author: Kei Funagayama <kei@karesansui-project.info>
29+"""
30+
31+import sys
32+import logging
33+import logging.config
34+
35+#: logging ready
36+ready = False
37+
38+def reload_conf(log_conf='/etc/opt/pysilhouette/log.conf'):
39+ """Re-logging configuration
40+ @param log_conf: configuration file path
41+ @type log_conf: str
42+ @rtype: bool
43+ @return: ready
44+ """
45+ global ready
46+ try:
47+ logging.config.fileConfig(log_conf)
48+ ready = True
49+ except:
50+ ready = False
51+ return ready
52+
53+def is_ready():
54+ return ready
55+
56+if __name__ == '__main__':
57+ """Testing
58+ """
59+ reload_conf('/etc/opt/pysilhouette/log.conf')
60+ if is_ready():
61+ _logger = logging.getLogger('pysilhouette.log')
62+ _logger.debug('test')
63+ else:
64+ print >>sys.stderr('Loading configuration files still do not log.')
--- /dev/null
+++ b/pysilhouette/performer.py
@@ -0,0 +1,185 @@
1+#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+#
4+# This file is part of Pysilhouette.
5+#
6+# Copyright (c) 2009 HDE, Inc.
7+#
8+# Permission is hereby granted, free of charge, to any person obtaining a copy
9+# of this software and associated documentation files (the "Software"), to deal
10+# in the Software without restriction, including without limitation the rights
11+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+# copies of the Software, and to permit persons to whom the Software is
13+# furnished to do so, subject to the following conditions:
14+#
15+# The above copyright notice and this permission notice shall be included in
16+# all copies or substantial portions of the Software.
17+#
18+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+# THE SOFTWARE.
25+#
26+
27+"""
28+@author: Kei Funagayama <kei@karesansui-project.info>
29+"""
30+
31+import sys
32+import signal
33+import os
34+import traceback
35+import logging
36+
37+from pysilhouette.log import reload_conf
38+from pysilhouette.prep import readconf, getopts, chkopts
39+from pysilhouette.db import Database
40+from pysilhouette.db.model import reload_mappers, JOBGROUP_STATUS
41+from pysilhouette.db.access import jobgroup_findbystatus, jobgroup_update
42+from pysilhouette.worker import SimpleWorker
43+
44+from pysilhouette.util import kill_proc, write_pidfile, create_fifo
45+
46+def sigterm_handler(signum, frame):
47+ logger = logging.getLogger('pysilhouette.performer.signal')
48+ logger.info('Stop the performerd with signal - pid=%s, signal=%s' % (os.getpid(), signum))
49+
50+def performer(opts, cf):
51+ logger = logging.getLogger('pysilhouette.performer')
52+
53+ # Initialization
54+ if os.access(cf["observer.mkfifo.path"], os.F_OK|os.R_OK|os.W_OK) is False:
55+ try:
56+ os.unlink(cf["observer.mkfifo.path"])
57+ logger.info('Deleted filo file. - file=%s' % cf["observer.mkfifo.path"])
58+ except:
59+ pass # Not anything
60+
61+ create_fifo(cf["observer.mkfifo.path"],
62+ cf["observer.mkfifo.user.name"],
63+ cf["observer.mkfifo.group.name"],
64+ cf["observer.mkfifo.perms"],
65+ )
66+
67+ logger.info('The fifo file was created. - file=%s' % cf["observer.mkfifo.path"])
68+
69+
70+ if opts.daemon is True:
71+ pid = os.getpid()
72+ if write_pidfile(opts.pidfile, pid):
73+ logger.info('The process file was created. - file=%s' % opts.pidfile)
74+ else:
75+ logger.error('Could not create process file. - file=%s' % opts.pidfile)
76+ return 1
77+
78+ logger.info('performer : [started]')
79+
80+ try:
81+ db = Database(cf['database.url'],
82+ encoding="utf-8",
83+ convert_unicode=True,
84+ assert_unicode=False, # product
85+ #assert_unicode='warn', # dev
86+ #echo = opts.verbose,
87+ #echo_pool = opts.verbose,
88+ echo=True,
89+ echo_pool=True
90+ )
91+
92+ reload_mappers(db.get_metadata())
93+
94+ except Exception, e:
95+ logger.error('Initializing a database error - %s' % ''.join(e.args))
96+ t_logger = logging.getLogger('pysilhouette_traceback')
97+ t_logger.error(traceback.format_exc())
98+ return 1
99+
100+ while True:
101+ fp = open(cf["observer.mkfifo.path"], 'r')
102+ try:
103+ code = fp.read()
104+ finally:
105+ fp.close()
106+
107+ logger.info('Received code from the FIFO file. - code=%s' % code)
108+ session = db.get_session()
109+ m_jgs = jobgroup_findbystatus(session)
110+ session.close()
111+
112+ logger.info('Queued the Job Group from the database. - Number of JobGroup=%d' % len(m_jgs))
113+
114+ if code == cf["observer.mkfifo.start.code"]:
115+ if 0 < len(m_jgs):
116+ for m_jg in m_jgs:
117+ try:
118+ w = SimpleWorker(cf, db, m_jg.id)
119+ w.run()
120+ except Exception, e:
121+ logger.info('Failed to perform the job group. Exceptions are not expected. - jobgroup_id=%d : %s'
122+ % (m_jg.id, ','.join(e.args)))
123+ print >>sys.stderr, traceback.format_exc()
124+ t_logger = logging.getLogger('pysilhouette_traceback')
125+ t_logger.error(traceback.format_exc())
126+
127+ try:
128+ session = db.get_session()
129+ jobgroup_update(session, m_jg, JOBGROUP_STATUS['APPERR'])
130+ session.close()
131+ except:
132+ logger.error('Failed to change the status of the job group. - jobgroup_id=%d : %s'
133+ % (m_jg.id, ','.join(e.args)))
134+ t_logger = logging.getLogger('pysilhouette_traceback')
135+ t_logger.error(traceback.format_exc())
136+
137+ else:
138+ logger.info('No Job Group.')
139+ elif code == cf["observer.mkfifo.stop.code"]:
140+ logger.info('Received stop code from the FIFO file. - code=%s' % code)
141+ break
142+ else:
143+ logger.info('Received illegal code from the FIFO file. - code=%s' % code)
144+
145+def main():
146+ (opts, args) = getopts()
147+ if chkopts(opts) is True:
148+ return 1
149+
150+ cf = readconf(opts.config)
151+ if cf is None:
152+ print >>sys.stderr, 'Failed to load the config file "%s". (%s)' % (opts.config, sys.argv[0])
153+ return 1
154+
155+ # set env=PYSILHOUETTE_CONF
156+ os.environ['PYSILHOUETTE_CONF'] = opts.config
157+
158+ if reload_conf(cf["env.sys.log.conf.path"]):
159+ logger = logging.getLogger('pysilhouette.performer')
160+ else:
161+ print >>sys.stderr, 'Failed to load the log file. (%s)' % sys.argv[0]
162+ return 1
163+
164+ try:
165+ try:
166+ signal.signal(signal.SIGTERM, sigterm_handler)
167+ ret = performer(opts, cf) # start!!
168+ return ret
169+ except KeyboardInterrupt, k:
170+ logger.critical('Keyboard interrupt occurred. - %s' % ''.join(k.args))
171+ print >>sys.stderr, 'Keyboard interrupt occurred. - %s' % ''.join(k.args)
172+ except Exception, e:
173+ logger.critical('System error has occurred. - %s' % ''.join(e.args))
174+ print >>sys.stderr, 'System error has occurred. - %s' % ''.join(e.args)
175+ print >>sys.stderr, traceback.format_exc()
176+ t_logger = logging.getLogger('pysilhouette_traceback')
177+ t_logger.critical(traceback.format_exc())
178+
179+ finally:
180+ if opts.daemon is True and os.path.isfile(opts.pidfile):
181+ os.unlink(opts.pidfile)
182+ logger.info('Process file has been deleted.. - pidfile=%s' % opts.pidfile)
183+
184+if __name__ == '__main__':
185+ sys.exit(main())
--- /dev/null
+++ b/pysilhouette/prep.py
@@ -0,0 +1,279 @@
1+#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+#
4+# This file is part of Pysilhouette.
5+#
6+# Copyright (c) 2009 HDE, Inc.
7+#
8+# Permission is hereby granted, free of charge, to any person obtaining a copy
9+# of this software and associated documentation files (the "Software"), to deal
10+# in the Software without restriction, including without limitation the rights
11+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+# copies of the Software, and to permit persons to whom the Software is
13+# furnished to do so, subject to the following conditions:
14+#
15+# The above copyright notice and this permission notice shall be included in
16+# all copies or substantial portions of the Software.
17+#
18+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+# THE SOFTWARE.
25+#
26+
27+"""
28+@author: Kei Funagayama <kei@karesansui-project.info>
29+"""
30+
31+import re
32+import sys
33+import os
34+import pwd
35+import grp
36+from optparse import OptionParser
37+
38+try:
39+ from cStringIO import StringIO
40+except ImportError:
41+ from StringIO import StringIO
42+
43+from pysilhouette import __version__
44+
45+usage = '%prog [options]'
46+
47+def getopts():
48+ optp = OptionParser(usage=usage, version=__version__)
49+ optp.add_option('-c', '--config', dest='config', help='configuration file')
50+ optp.add_option('-d', '--daemon', dest='daemon', action="store_true", help='Daemon startup')
51+ optp.add_option('-v', '--verbose', dest='verbose', action="store_true", help='Has not been used.')
52+ optp.add_option('-p', '--pidfile', dest='pidfile', action="store", type='string', help='process file path')
53+ optp.add_option('-k', '--uniqkey', dest='uniqkey', action="store_true", help='show unique key')
54+ return optp.parse_args()
55+
56+def chkopts(opts):
57+ if not opts.config:
58+ print >>sys.stderr, '-c or --config option is required.'
59+ return True
60+
61+ if os.path.isfile(opts.config) is False:
62+ print >>sys.stderr, '-c or --config file is specified in the option does not exist.'
63+ return True
64+
65+ if opts.uniqkey:
66+ return False
67+
68+ if opts.daemon is True and not opts.pidfile:
69+ print >>sys.stderr, '-p or --pidfile option is required.'
70+ return True
71+
72+ if not opts.daemon and opts.pidfile:
73+ print >>sys.stderr, '-p doesn work without -d. Please add the option to -d or --daemon.'
74+ return True
75+
76+ return False
77+
78+def chk_conf(cf):
79+ def is_key(key):
80+ if cf.has_key(key) is True and 0 < len(cf[key]):
81+ return True
82+ else:
83+ return False
84+
85+ # env
86+ err_key = ""
87+ if len(err_key) <= 0 and is_key("env.python") is False:
88+ err_key = "env.python"
89+ if len(err_key) <= 0 and is_key("env.sys.log.conf.path") is False:
90+ err_key = "env.sys.log.conf.path"
91+ if len(err_key) <= 0 and is_key("env.uniqkey") is False:
92+ err_key = "env.uniqkey"
93+ if len(err_key) <= 0 and is_key("daemon.stdin") is False:
94+ err_key = "daemon.stdin"
95+ if len(err_key) <= 0 and is_key("daemon.stdout") is False:
96+ err_key = "daemon.stdout"
97+ if len(err_key) <= 0 and is_key("daemon.stderr") is False:
98+ err_key = "daemon.stderr"
99+ if len(err_key) <= 0 and is_key("observer.target.python") is False:
100+ err_key = "observer.target.python"
101+ if len(err_key) <= 0 and is_key("observer.target.scheduler") is False:
102+ err_key = "observer.target.scheduler"
103+ if len(err_key) <= 0 and is_key("observer.target.performer") is False:
104+ err_key = "observer.target.performer"
105+ if len(err_key) <= 0 and is_key("observer.restart.count") is False:
106+ err_key = "observer.restart.count"
107+ if len(err_key) <= 0 and is_key("observer.restart.count.clear.time") is False:
108+ err_key = "observer.restart.count.clear.time"
109+ if len(err_key) <= 0 and is_key("observer.check.interval") is False:
110+ err_key = "observer.check.interval"
111+ if len(err_key) <= 0 and is_key("observer.status.path") is False:
112+ err_key = "observer.status.path"
113+ if len(err_key) <= 0 and is_key("observer.mkfifo.path") is False:
114+ err_key = "observer.mkfifo.path"
115+ if len(err_key) <= 0 and is_key("observer.mkfifo.start.code") is False:
116+ err_key = "observer.mkfifo.start.code"
117+ if len(err_key) <= 0 and is_key("observer.mkfifo.ignore.code") is False:
118+ err_key = "observer.mkfifo.ignore.code"
119+ if len(err_key) <= 0 and is_key("observer.mkfifo.stop.code") is False:
120+ err_key = "observer.mkfifo.stop.code"
121+ if len(err_key) <= 0 and is_key("observer.mkfifo.user.name") is False:
122+ err_key = "observer.mkfifo.user.name"
123+ if len(err_key) <= 0 and is_key("observer.mkfifo.group.name") is False:
124+ err_key = "observer.mkfifo.group.name"
125+ if len(err_key) <= 0 and is_key("observer.mkfifo.perms") is False:
126+ err_key = "observer.mkfifo.perms"
127+ if len(err_key) <= 0 and is_key("scheduler.interval") is False:
128+ err_key = "scheduler.interval"
129+ if len(err_key) <= 0 and is_key("job.popen.env.lang") is False:
130+ err_key = "job.popen.env.lang"
131+ if len(err_key) <= 0 and is_key("job.popen.timeout") is False:
132+ err_key = "job.popen.timeout"
133+ if len(err_key) <= 0 and is_key("job.popen.waittime") is False:
134+ err_key = "job.popen.waittime"
135+ if len(err_key) <= 0 and is_key("database.url") is False:
136+ err_key = "database.url"
137+
138+ if 0 < len(err_key):
139+ print >>sys.stderr, 'Configuration files are missing. - %s' % (err_key)
140+ return False
141+
142+ if os.access(cf["env.python"], os.R_OK | os.X_OK) is False:
143+ print >>sys.stderr, 'Incorrect file permissions. - env.python=%s' % (cf["env.python"])
144+ return False
145+
146+ if os.access(cf["observer.target.python"], os.R_OK | os.X_OK) is False:
147+ print >>sys.stderr, 'Incorrect file permissions. - observer.target.python=%s' % (cf["observer.target.python"])
148+ return False
149+
150+ if os.access(cf["observer.target.scheduler"], os.R_OK) is False:
151+ print >>sys.stderr, 'Incorrect file permissions. - observer.target.scheduler=%s' % (cf["observer.target.scheduler"])
152+ return False
153+
154+ if os.access(cf["observer.target.performer"], os.R_OK) is False:
155+ print >>sys.stderr, 'Incorrect file permissions. - observer.target.performer=%s' % (cf["observer.target.performer"])
156+ return False
157+
158+ from pysilhouette.util import is_int
159+ if is_int(cf["observer.restart.count"]) is False:
160+ print >>sys.stderr, 'Must be a number. - observer.restart.count=%s' % (cf["observer.restart.count"])
161+ return False
162+
163+ if is_int(cf["observer.restart.count.clear.time"]) is False:
164+ print >>sys.stderr, 'Must be a number. - observer.restart.count.clear.time=%s' % (cf["observer.restart.count.clear.time"])
165+ return False
166+
167+ if is_int(cf["observer.check.interval"]) is False:
168+ print >>sys.stderr, 'Must be a number. - observer.check.interval=%s' % (cf["observer.check.interval"])
169+ return False
170+
171+ if is_int(cf["observer.mkfifo.start.code"]) is False:
172+ print >>sys.stderr, 'Must be a number. - observer.mkfifo.start.code=%s' % (cf["observer.mkfifo.start.code"])
173+ return False
174+
175+ if is_int(cf["observer.mkfifo.ignore.code"]) is False:
176+ print >>sys.stderr, 'Must be a number. - observer.mkfifo.ignore.code=%s' % (cf["observer.mkfifo.ignore.code"])
177+ return False
178+
179+ if is_int(cf["observer.mkfifo.stop.code"]) is False:
180+ print >>sys.stderr, 'Must be a number. - observer.mkfifo.stop.code=%s' % (cf["observer.mkfifo.stop.code"])
181+ return False
182+
183+ if is_int(cf["scheduler.interval"]) is False:
184+ print >>sys.stderr, 'Must be a number. - scheduler.interval=%s' % (cf["scheduler.interval"])
185+ return False
186+
187+ if is_int(cf["job.popen.timeout"]) is False:
188+ print >>sys.stderr, 'Must be a number. - job.popen.timeout=%s' % (cf["job.popen.timeout"])
189+ return False
190+
191+ if is_int(cf["job.popen.waittime"]) is False:
192+ print >>sys.stderr, 'Must be a number. - job.popen.waittime=%s' % (cf["job.popen.waittime"])
193+ return False
194+
195+ s_mkfifo = set([cf["observer.mkfifo.start.code"],
196+ cf["observer.mkfifo.ignore.code"],
197+ cf["observer.mkfifo.stop.code"]],
198+ )
199+ if len(s_mkfifo) != 3:
200+ print >>sys.stderr, 'Is not unique. - observer.mkfifo.[start,ignore,stop]=%s,%s,%s' \
201+ % (cf["observer.mkfifo.start.code"],
202+ cf["observer.mkfifo.ignore.code"],
203+ cf["observer.mkfifo.stop.code"],
204+ )
205+ return False
206+
207+ try:
208+ pwd.getpwnam(cf["observer.mkfifo.user.name"])
209+ except:
210+ print >>sys.stderr, 'Can not get information of the user (nonexistent?). - observer.mkfifo.user.name=%s' % (cf["observer.mkfifo.user.name"])
211+
212+ try:
213+ grp.getgrnam(cf["observer.mkfifo.group.name"])
214+ except:
215+ print >>sys.stderr, 'Can not get information of the group (nonexistent?). - observer.mkfifo.group.name=%s' % (cf["observer.mkfifo.group.name"])
216+
217+ try:
218+ int(cf["observer.mkfifo.perms"], 8)
219+ except:
220+ print >>sys.stderr, 'Incorrect file permissions. - observer.mkfifo.perms=%s' % (cf["observer.mkfifo.perms"])
221+ return False
222+
223+ if cf.has_key("job.whitelist.flag") is True \
224+ and cf["job.whitelist.flag"] == "1" \
225+ and cf.has_key("job.whitelist.path") is True \
226+ and 0 < len(cf["job.whitelist.path"]):
227+ if os.path.isfile(cf["job.whitelist.path"]) is False:
228+ print >>sys.stderr, 'File not found. - job.whitelist.path=%s' % (cf["job.whitelist.path"])
229+ return False
230+
231+ return True
232+
233+def readconf(path):
234+ if not os.path.isfile(path):
235+ print >>sys.stderr, 'file=%s - file specified in the option does not exist.' % path
236+ return None
237+
238+ fp = open(path, 'r')
239+ try:
240+ try:
241+ _r = {}
242+ for line in fp:
243+ line = re.sub(r'[ \t]', '', line).strip()
244+ if len(line) <= 0 or line[0] == '#':
245+ continue
246+ key, value = line.split('=', 1)
247+ try:
248+ value = value[:value.rindex('#')]
249+ except ValueError,ve:
250+ pass
251+ _r[key] = value
252+ return _r
253+ except Exception, e:
254+ print >>sys.stderr, 'file=%s - Failed to load configuration files. : except=%s' \
255+ % (path, e.args)
256+ return None
257+ finally:
258+ fp.close()
259+
260+def sysappend(pkg):
261+ lines = []
262+ if type(pkg) is list:
263+ lines = pkg
264+ else:
265+ print >>sys.stderr, '%s should be list type.' % (pkg,)
266+ return False
267+
268+ for line in lines:
269+ f = False
270+ for path in sys.path:
271+ if line == path:
272+ f = True
273+ break
274+ if f is False:
275+ sys.path.insert(1, line)
276+ return True
277+
278+if __name__ == "__main__":
279+ pass
--- /dev/null
+++ b/pysilhouette/scheduler.py
@@ -0,0 +1,137 @@
1+#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+#
4+# This file is part of Pysilhouette.
5+#
6+# Copyright (c) 2009 HDE, Inc.
7+#
8+# Permission is hereby granted, free of charge, to any person obtaining a copy
9+# of this software and associated documentation files (the "Software"), to deal
10+# in the Software without restriction, including without limitation the rights
11+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+# copies of the Software, and to permit persons to whom the Software is
13+# furnished to do so, subject to the following conditions:
14+#
15+# The above copyright notice and this permission notice shall be included in
16+# all copies or substantial portions of the Software.
17+#
18+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+# THE SOFTWARE.
25+#
26+
27+"""
28+@author: Kei Funagayama <kei@karesansui-project.info>
29+"""
30+
31+import os
32+import sys
33+import time
34+import signal
35+import traceback
36+import logging
37+
38+from pysilhouette.log import reload_conf
39+from pysilhouette.prep import readconf, getopts, chkopts
40+from pysilhouette.util import write_pidfile, create_fifo
41+
42+def sigterm_handler(signum, frame):
43+ global logger
44+ logger.info('Stop the schedulerd with signal- pid=%s, signal=%s' % (os.getpid(), signum))
45+
46+def scheduler():
47+ global opts
48+ global cf
49+ global logger
50+
51+ if os.access(cf["observer.mkfifo.path"], os.F_OK|os.R_OK|os.W_OK) is False:
52+ try:
53+ os.unlink(cf["observer.mkfifo.path"])
54+ logger.info('Deleted filo file. - file=%s' % cf["observer.mkfifo.path"])
55+ except:
56+ pass # Not anything
57+ create_fifo(cf["observer.mkfifo.path"],
58+ cf["observer.mkfifo.user.name"],
59+ cf["observer.mkfifo.group.name"],
60+ cf["observer.mkfifo.perms"],
61+ )
62+
63+ logger.info('The fifo file was created. - file=%s' % cf["observer.mkfifo.path"])
64+
65+ if opts.daemon is True:
66+ pid = os.getpid()
67+ if write_pidfile(opts.pidfile, pid):
68+ logger.info('The process file was created. - file=%s' % opts.pidfile)
69+ else:
70+ logger.info('Could not create process file. - file=%s' % opts.pidfile)
71+ return 1
72+
73+ logger.info('schedulerd started!!')
74+
75+ while True:
76+ try:
77+ fp = open(cf["observer.mkfifo.path"], 'w')
78+ try:
79+ fp.write(cf['observer.mkfifo.start.code'])
80+ logger.info('Start code was written. - file=%s : code=%s'
81+ % (cf["observer.mkfifo.path"], cf['observer.mkfifo.start.code']))
82+ finally:
83+ fp.close()
84+
85+ logger.debug('interval start, interval=%s' % (cf['scheduler.interval']))
86+ time.sleep(int(cf['scheduler.interval']))
87+ except IOError, i:
88+ if i.errno == 4:
89+ return 0 # When ending with the signal
90+
91+ # beyond expectation
92+ logger.error('file=%s - 2 error write FIFO, code=%s'
93+ % (self.fifo, cf['observer.mkfifo.start.code']))
94+ return 1
95+
96+def main():
97+ global opts
98+ global cf
99+ global logger
100+
101+ (opts, args) = getopts()
102+ if chkopts(opts) is True:
103+ return 1
104+
105+ cf = readconf(opts.config)
106+ if cf is None:
107+ print >>sys.stderr, 'Failed to load the config file "%s". (%s)' % (opts.config, sys.argv[0])
108+ return 1
109+
110+ if reload_conf(cf["env.sys.log.conf.path"]):
111+ logger = logging.getLogger('pysilhouette.scheduler')
112+ else:
113+ print >>sys.stderr, 'Failed to load the log file. (%s)' % sys.argv[0]
114+ return 1
115+
116+ try:
117+ try:
118+ signal.signal(signal.SIGTERM, sigterm_handler)
119+ ret = scheduler() # start!!
120+ return ret
121+ except KeyboardInterrupt, k:
122+ logger.critical('Keyboard interrupt occurred. - %s' % ''.join(k.args))
123+ print >>sys.stderr, 'Keyboard interrupt occurred. - %s' % ''.join(k.args)
124+ except Exception, e:
125+ logger.critical('A system error has occurred. - %s' % ''.join(e.args))
126+ print >>sys.stderr, 'A system error has occurred. - %s' % ''.join(e.args)
127+ print >>sys.stderr, traceback.format_exc()
128+ t_logger = logging.getLogger('pysilhouette_traceback')
129+ t_logger.critical(traceback.format_exc())
130+
131+ finally:
132+ if opts.daemon is True and os.path.isfile(opts.pidfile):
133+ os.unlink(opts.pidfile)
134+ logger.info('Process file has been deleted.. - pidfile=%s' % opts.pidfile)
135+
136+if __name__ == '__main__':
137+ sys.exit(main())
--- /dev/null
+++ b/pysilhouette/silhouette.py
@@ -0,0 +1,116 @@
1+#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+#
4+# This file is part of Pysilhouette.
5+#
6+# Copyright (c) 2009 HDE, Inc.
7+#
8+# Permission is hereby granted, free of charge, to any person obtaining a copy
9+# of this software and associated documentation files (the "Software"), to deal
10+# in the Software without restriction, including without limitation the rights
11+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+# copies of the Software, and to permit persons to whom the Software is
13+# furnished to do so, subject to the following conditions:
14+#
15+# The above copyright notice and this permission notice shall be included in
16+# all copies or substantial portions of the Software.
17+#
18+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+# THE SOFTWARE.
25+#
26+
27+"""
28+@author: Kei Funagayama <kei@karesansui-project.info>
29+"""
30+
31+import os
32+import sys
33+import signal
34+import logging
35+import traceback
36+
37+try:
38+ import sqlalchemy
39+except ImportError, e:
40+ print >>sys.stderr, '[Error] There are not enough libraries. - %s' % ''.join(e.args)
41+ #traceback.format_exc()
42+ sys.exit(1)
43+
44+from pysilhouette.prep import readconf, getopts, chkopts, chk_conf
45+from pysilhouette.daemon import daemonize, observer
46+from pysilhouette.log import reload_conf
47+
48+opt = None #: command options
49+
50+def sigterm_handler(signum, frame):
51+ global opt
52+ logger = logging.getLogger('pysilhouette.silhouette.signal')
53+ logger.info('Stop the schedulerd with signal- pid=%s, signal=%s' % (os.getpid(), signum))
54+ if opts.daemon is True and os.path.isfile(opts.pidfile):
55+ os.unlink(opts.pidfile)
56+ logger.info('Process file has been deleted.. - pidfile=%s' % opts.pidfile)
57+
58+def main():
59+ global opts
60+
61+ (opts, args) = getopts()
62+ if chkopts(opts) is True:
63+ return 1
64+
65+ ####
66+ try:
67+ opts.config = os.path.abspath(opts.config)
68+ except AttributeError, e:
69+ print >>sys.stderr, 'No configuration file path.'
70+ return 1
71+
72+ cf = readconf(opts.config)
73+ if cf is None:
74+ print >>sys.stderr, 'Failed to load the config file "%s". (%s)' % (opts.config, sys.argv[0])
75+ return 1
76+
77+ # conf check
78+ if chk_conf(cf) is False:
79+ return 1
80+
81+ if reload_conf(cf["env.sys.log.conf.path"]):
82+ logger = logging.getLogger('pysilhouette.silhouette')
83+ else:
84+ print >>sys.stderr, 'Failed to load the log file. (%s)' % sys.argv[0]
85+ return 1
86+
87+ if opts.uniqkey:
88+ print >>sys.stdout, cf["env.uniqkey"]
89+ return 0
90+
91+ if opts.daemon is True:
92+ logger.debug('Daemon stdin=%s' % cf['daemon.stdin'])
93+ logger.debug('Daemon stdout=%s' % cf['daemon.stdout'])
94+ logger.debug('Daemon stderr=%s' % cf['daemon.stderr'])
95+ pid = daemonize(stdin=cf['daemon.stdin'],
96+ stdout=cf['daemon.stdout'],
97+ stderr=cf['daemon.stderr'],
98+ pidfile=opts.pidfile)
99+ logger.info('Daemon Running!! pid=%s' % pid)
100+
101+ try:
102+ signal.signal(signal.SIGTERM, sigterm_handler)
103+ ret = observer(opts=opts, cf=cf) # start!!
104+ return ret
105+ except KeyboardInterrupt, k:
106+ logger.critical('Keyboard interrupt occurred. - %s' % ''.join(k.args))
107+ print >>sys.stderr, 'Keyboard interrupt occurred. - %s' % ''.join(k.args)
108+ except Exception, e:
109+ logger.critical('System error has occurred. - %s' % ''.join(e.args))
110+ print >>sys.stderr, 'System error has occurred. - %s' % ''.join(e.args)
111+ t_logger = logging.getLogger('pysilhouette_traceback')
112+ t_logger.critical(traceback.format_exc())
113+ print >>sys.stderr, traceback.format_exc()
114+
115+if __name__ == '__main__':
116+ sys.exit(main())
--- /dev/null
+++ b/pysilhouette/tests/__init__.py
@@ -0,0 +1,29 @@
1+#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+#
4+# This file is part of Pysilhouette.
5+#
6+# Copyright (c) 2009 HDE, Inc.
7+#
8+# Permission is hereby granted, free of charge, to any person obtaining a copy
9+# of this software and associated documentation files (the "Software"), to deal
10+# in the Software without restriction, including without limitation the rights
11+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+# copies of the Software, and to permit persons to whom the Software is
13+# furnished to do so, subject to the following conditions:
14+#
15+# The above copyright notice and this permission notice shall be included in
16+# all copies or substantial portions of the Software.
17+#
18+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+# THE SOFTWARE.
25+#
26+
27+"""
28+@author: Kei Funagayama <kei@karesansui-project.info>
29+"""
--- /dev/null
+++ b/pysilhouette/tests/suite.py
@@ -0,0 +1,39 @@
1+#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+#
4+# This file is part of Pysilhouette.
5+#
6+# Copyright (c) 2009 HDE, Inc.
7+#
8+# Permission is hereby granted, free of charge, to any person obtaining a copy
9+# of this software and associated documentation files (the "Software"), to deal
10+# in the Software without restriction, including without limitation the rights
11+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+# copies of the Software, and to permit persons to whom the Software is
13+# furnished to do so, subject to the following conditions:
14+#
15+# The above copyright notice and this permission notice shall be included in
16+# all copies or substantial portions of the Software.
17+#
18+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+# THE SOFTWARE.
25+#
26+
27+"""
28+@author: Kei Funagayama <kei@karesansui-project.info>
29+"""
30+
31+import unittest
32+
33+from pysilhouette.tests.testprep import all_suite_prep
34+from pysilhouette.tests.testworker import all_suite_worker
35+
36+ts = unittest.TestSuite()
37+ts.addTest(all_suite_prep())
38+ts.addTest(all_suite_worker())
39+unittest.TextTestRunner(verbosity=2).run(ts)
--- /dev/null
+++ b/pysilhouette/tests/testprep.py
@@ -0,0 +1,119 @@
1+#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+#
4+# This file is part of Pysilhouette.
5+#
6+# Copyright (c) 2009 HDE, Inc.
7+#
8+# Permission is hereby granted, free of charge, to any person obtaining a copy
9+# of this software and associated documentation files (the "Software"), to deal
10+# in the Software without restriction, including without limitation the rights
11+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+# copies of the Software, and to permit persons to whom the Software is
13+# furnished to do so, subject to the following conditions:
14+#
15+# The above copyright notice and this permission notice shall be included in
16+# all copies or substantial portions of the Software.
17+#
18+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+# THE SOFTWARE.
25+#
26+
27+"""
28+@author: Kei Funagayama <kei@karesansui-project.info>
29+"""
30+
31+import sys
32+import os
33+import unittest
34+import pysilhouette.prep
35+
36+class TestPrep(unittest.TestCase):
37+
38+ _sysappend0 = ['hoge', 'foo']
39+ _sysappend1 = []
40+ _sysappend2 = ''
41+ _sysappend3 = None
42+
43+ _tmpfile = None
44+
45+ def setUp(self):
46+ # readconf
47+ import tempfile
48+ self._tmpfile = tempfile.mkstemp()
49+ fp = open(self._tmpfile[1], 'w')
50+ fp.write('key0=value0\n')
51+ fp.write('key1 =value1\n')
52+ fp.write('key2= value2\n')
53+ fp.write('key3 = value3\n')
54+ fp.write('#key4=value4\n')
55+ fp.write('k#ey5=value5\n')
56+ fp.close()
57+ pass
58+
59+ def tearDown(self):
60+ os.unlink(self._tmpfile[1])
61+ pass
62+
63+ def test_sysappend_0(self):
64+ ret = pysilhouette.prep.sysappend(self._sysappend0)
65+ self.assertEqual(ret, True)
66+ for target in self._sysappend0:
67+ val = False
68+ for line in sys.path:
69+ if target == line:
70+ val = True
71+ if val is False:
72+ self.fail('sys.path insert error!!')
73+
74+ def test_sysappend_1(self):
75+ ret = pysilhouette.prep.sysappend(self._sysappend1)
76+ self.assertEqual(ret, True)
77+
78+ def test_sysappend_2(self):
79+ ret = pysilhouette.prep.sysappend(self._sysappend2)
80+ self.assertEqual(ret, False)
81+
82+ def test_sysappend_3(self):
83+ ret = pysilhouette.prep.sysappend(self._sysappend3)
84+ self.assertEqual(ret, False)
85+
86+ def test_readconf_0(self):
87+ ret = pysilhouette.prep.readconf(self._tmpfile[1])
88+ if ret['key0'] != 'value0':
89+ self.fail()
90+ if ret['key1'] != 'value1':
91+ self.fail()
92+ if ret['key2'] != 'value2':
93+ self.fail()
94+ if ret['key3'] != 'value3':
95+ self.fail()
96+ try:
97+ if ret['#key4'] != 'value4':
98+ self.fail()
99+ except KeyError:
100+ pass
101+ if ret['k#ey5'] != 'value5':
102+ self.fail()
103+
104+class SuiteSysappend(unittest.TestSuite):
105+ def __init__(self):
106+ tests = ['test_sysappend_0', 'test_sysappend_1',
107+ 'test_sysappend_2', 'test_sysappend_3']
108+ unittest.TestSuite.__init__(self,map(TestPrep, tests))
109+
110+class SuiteReadconf(unittest.TestSuite):
111+ def __init__(self):
112+ tests = ['test_readconf_0']
113+ unittest.TestSuite.__init__(self,map(TestPrep, tests))
114+
115+def all_suite_prep():
116+ return unittest.TestSuite([SuiteSysappend(), SuiteReadconf()])
117+
118+if __name__ == '__main__':
119+ unittest.TextTestRunner(verbosity=2).run(all_suite_prep())
--- /dev/null
+++ b/pysilhouette/tests/testutil.py
@@ -0,0 +1,193 @@
1+#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+#
4+# This file is part of Pysilhouette.
5+#
6+# Copyright (c) 2009 HDE, Inc.
7+#
8+# Permission is hereby granted, free of charge, to any person obtaining a copy
9+# of this software and associated documentation files (the "Software"), to deal
10+# in the Software without restriction, including without limitation the rights
11+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+# copies of the Software, and to permit persons to whom the Software is
13+# furnished to do so, subject to the following conditions:
14+#
15+# The above copyright notice and this permission notice shall be included in
16+# all copies or substantial portions of the Software.
17+#
18+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+# THE SOFTWARE.
25+#
26+
27+"""
28+@author: Kei Funagayama <kei@karesansui-project.info>
29+"""
30+
31+import os
32+import unittest
33+import pysilhouette.util as target
34+
35+class TestUtil(unittest.TestCase):
36+
37+ fname = '/tmp/testutil.fifo'
38+ pname = '/tmp/testutil.pid'
39+
40+ def setUp(self):
41+ pass
42+
43+ def tearDown(self):
44+ pass
45+
46+ def unlink(self, name):
47+ try:
48+ os.unlink(name)
49+ except:
50+ pass
51+
52+ def test_popen_0(self):
53+ cmd = target.split_shell_command('date')
54+ (proc, proc_info) = target.popen(cmd)
55+ self.assertTrue(proc_info['r_code'] == 0)
56+
57+ def test_popen_1(self):
58+ cmd = target.split_shell_command('cat *')
59+ (proc, proc_info) = target.popen(cmd)
60+ self.assertTrue(proc_info['r_code'] == 1)
61+
62+ def test_split_shell_command_0(self):
63+ ret = target.split_shell_command('date')
64+ self.assertTrue(type(ret) is list)
65+ self.assertTrue(len(ret) == 1)
66+ self.assertTrue(ret[0] == 'date')
67+
68+ ret = target.split_shell_command('date -a')
69+ self.assertTrue(type(ret) is list)
70+ self.assertTrue(len(ret) == 2)
71+ self.assertTrue((ret[0] == 'date' and ret[1] == '-a'))
72+
73+
74+ ret = target.split_shell_command(' date -a ')
75+ self.assertTrue(type(ret) is list)
76+ self.assertTrue(len(ret) == 2)
77+ self.assertTrue((ret[0] == 'date' and ret[1] == '-a'))
78+
79+ ret = target.split_shell_command(' date -a ')
80+ self.assertTrue(type(ret) is list)
81+ self.assertTrue(len(ret) == 2)
82+ self.assertTrue((ret[0] == 'date' and ret[1] == '-a'))
83+
84+ def test_split_shell_command_1(self):
85+ self.assertFalse(target.split_shell_command(None))
86+
87+
88+ def test_is_empty_0(self):
89+ self.assertFalse(target.is_empty('cmd'))
90+ self.assertFalse(target.is_empty(' cmd '))
91+ self.assertFalse(target.is_empty(' cmd'))
92+ self.assertFalse(target.is_empty('cmd -a '))
93+ self.assertFalse(target.is_empty('cmd -a '))
94+ self.assertFalse(target.is_empty('cmd -a /hoge'))
95+ self.assertFalse(target.is_empty('cmd -a -as'))
96+ self.assertFalse(target.is_empty('cmd a -as'))
97+ self.assertTrue(target.is_empty(''))
98+ self.assertTrue(target.is_empty(' '))
99+ self.assertTrue(target.is_empty(None))
100+
101+ def test_create_fifo_0(self):
102+ self.unlink(self.fname)
103+ ret = target.create_fifo(self.fname,'satori','pysilhouette','0641')
104+ self.assertTrue(ret)
105+ self.unlink(self.fname)
106+
107+ def test_create_fifo_1(self):
108+ target.create_fifo(self.fname,'root','root','0641')
109+ ret = target.create_fifo(self.fname,'root','root','0641')
110+ self.assertFalse(ret)
111+ self.unlink(self.fname)
112+
113+ def test_write_pidfile_0(self):
114+ self.unlink(self.pname)
115+ ret = target.write_pidfile(self.pname, 12345)
116+ self.assertTrue(ret)
117+ self.unlink(self.pname)
118+
119+ def test_write_pidfile_1(self):
120+ self.unlink(self.pname)
121+ ret = target.write_pidfile(self.pname, 10)
122+ ret = target.write_pidfile(self.pname, 20)
123+ fp = open(self.pname, 'r')
124+ ret = fp.read()
125+ self.assertEquals('20', ret)
126+ self.unlink(self.pname)
127+
128+ def test_read_pidfile_0(self):
129+ self.unlink(self.pname)
130+ target.write_pidfile(self.pname, 30)
131+
132+ ret = target.read_pidfile(self.pname)
133+ self.assertEquals('30', ret)
134+ self.unlink(self.pname)
135+
136+ def test_read_pidfile_1(self):
137+ self.unlink(self.pname)
138+
139+ ret = target.read_pidfile(self.pname)
140+ self.assertEquals('', ret)
141+
142+class SuiteIsSplitShellCommand(unittest.TestSuite):
143+ def __init__(self):
144+ tests = ['test_split_shell_command_0',
145+ 'test_split_shell_command_1',
146+ ]
147+ unittest.TestSuite.__init__(self,map(TestUtil, tests))
148+
149+class SuiteIsEmpty(unittest.TestSuite):
150+ def __init__(self):
151+ tests = ['test_is_empty_0',
152+ ]
153+ unittest.TestSuite.__init__(self,map(TestUtil, tests))
154+
155+class SuiteCreateFifo(unittest.TestSuite):
156+ def __init__(self):
157+ tests = ['test_create_fifo_0',
158+ 'test_create_fifo_1',
159+ ]
160+ unittest.TestSuite.__init__(self,map(TestUtil, tests))
161+
162+class SuitePopen(unittest.TestSuite):
163+ def __init__(self):
164+ tests = ['test_popen_0',
165+ 'test_popen_1',
166+ ]
167+ unittest.TestSuite.__init__(self,map(TestUtil, tests))
168+
169+class SuiteWritePidfile(unittest.TestSuite):
170+ def __init__(self):
171+ tests = ['test_write_pidfile_0',
172+ 'test_write_pidfile_1',
173+ ]
174+ unittest.TestSuite.__init__(self,map(TestUtil, tests))
175+
176+class SuiteReadPidfile(unittest.TestSuite):
177+ def __init__(self):
178+ tests = ['test_read_pidfile_0',
179+ 'test_read_pidfile_1',
180+ ]
181+ unittest.TestSuite.__init__(self,map(TestUtil, tests))
182+
183+def all_suite_util():
184+ return unittest.TestSuite([SuiteIsSplitShellCommand(),
185+ SuitePopen(),
186+ SuiteIsEmpty(),
187+ SuiteCreateFifo(),
188+ SuiteWritePidfile(),
189+ SuiteReadPidfile(),
190+ ])
191+
192+if __name__ == '__main__':
193+ unittest.TextTestRunner(verbosity=2).run(all_suite_util())
--- /dev/null
+++ b/pysilhouette/tests/testworker.py
@@ -0,0 +1,886 @@
1+#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+#
4+# This file is part of Pysilhouette.
5+#
6+# Copyright (c) 2009 HDE, Inc.
7+#
8+# Permission is hereby granted, free of charge, to any person obtaining a copy
9+# of this software and associated documentation files (the "Software"), to deal
10+# in the Software without restriction, including without limitation the rights
11+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+# copies of the Software, and to permit persons to whom the Software is
13+# furnished to do so, subject to the following conditions:
14+#
15+# The above copyright notice and this permission notice shall be included in
16+# all copies or substantial portions of the Software.
17+#
18+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+# THE SOFTWARE.
25+#
26+
27+"""
28+@author: Kei Funagayama <kei@karesansui-project.info>
29+"""
30+
31+import sys
32+import os
33+import unittest
34+import logging
35+
36+import sqlalchemy.orm
37+
38+from pysilhouette.prep import readconf
39+from pysilhouette.db import Database
40+from pysilhouette.db.model import RES_PENDING, RES_RUNNING,RES_NORMAL_END, \
41+ RES_ABNORMAL_TERMINATION, RES_ROLLBACK,RES_ROLLBACK_ABEND, \
42+ RES_ROLLBACK_SUCCESSFUL_COMPLETION, reload_mappers
43+from pysilhouette.worker import Worker
44+from pysilhouette.db.access import *
45+
46+class TestWorker(unittest.TestCase):
47+ """
48+ """
49+ _db = None
50+
51+ def setUp(self):
52+ # O/R mapping
53+ self._db = Database(cf['database.url'], encoding="utf-8", convert_unicode=True, echo=True)
54+ reload_mappers(self._db.get_metadata())
55+
56+ # database init
57+ self._db.get_metadata().drop_all()
58+ self._db.get_metadata().create_all() # create table
59+ pass
60+
61+ def tearDown(self):
62+ sqlalchemy.orm.clear_mappers()
63+ pass
64+
65+ def set_job(self, session, jg_name, uniqkey,
66+ job=(True, True, True), rollback=None, mail=None):
67+ jg1 = JobGroup(jg_name.decode('utf-8'), uniqkey)
68+
69+ if job[0] is True:
70+ j1 = Job(u'file create','0','/bin/touch /tmp/test_case1.txt')
71+ else:
72+ j1 = Job(u'file create','0','/bin/touch_dummy /tmp/test_case1.txt')
73+
74+ if job[1] is True:
75+ j2 = Job(u'file copy', '1', '/bin/cp /tmp/test_case1.txt /tmp/test_case1_rename.txt')
76+ else:
77+ j2 = Job(u'file copy', '1', '/bin/cp_dummy /tmp/test_case1.txt /tmp/test_case1_rename.txt')
78+
79+ if job[2] is True:
80+ j3 = Job(u'file delete','2','/bin/rm /tmp/test_case1.txt')
81+ else:
82+ j3 = Job(u'file delete','2','/bin/rm_dummy /tmp/test_case1.txt')
83+
84+ jg1.jobs.append(j1)
85+ jg1.jobs.append(j2)
86+ jg1.jobs.append(j3)
87+
88+ if rollback is True:
89+ j1.rollback_command = u'/bin/echo JOB1 rollback'
90+ j2.rollback_command = u'/bin/echo JOB2 rollback'
91+ j3.rollback_command = u'/bin/echo JOB3 rollback'
92+ elif rollback is False:
93+ j1.rollback_command = u'/bin/echo_dummy JOB1 rollback'
94+ j2.rollback_command = u'/bin/echo_dummy JOB2 rollback'
95+ j3.rollback_command = u'/bin/echo_dummy JOB3 rollback'
96+ elif rollback is None:
97+ pass
98+
99+ if mail is True:
100+ jg1.finish_command = ok_f_cmd % (jg1.name, jg1.name)
101+ elif mail is False:
102+ jg1.finish_command = ng_f_cmd % (jg1.name, jg1.name)
103+ elif mail is None:
104+ pass
105+
106+ session.add_all([jg1])
107+ session.commit()
108+
109+ def run_job(self, sess):
110+ _m_jgs = jobgroup_findbystatus(sess)
111+ for _m_jg in _m_jgs:
112+ _w = Worker(self._db, _m_jg.id)
113+ _w.run()
114+ worker_debug(self._db, _m_jg.id)
115+ sess.close()
116+
117+ def check_job(self, sess, jg=RES_NORMAL_END,
118+ job=(RES_NORMAL_END, RES_NORMAL_END, RES_NORMAL_END)):
119+ sess = self._db.get_session()
120+ _m_jg1 = jobgroup_findbyid(sess, 1)
121+ self.assertEqual(int(_m_jg1.status), int(jg))
122+
123+ _m_jobs = job_findbyjobgroup_id(sess, 1, False)
124+ self.assertEqual(int(_m_jobs[0].status), int(job[0]))
125+ self.assertEqual(int(_m_jobs[1].status), int(job[1]))
126+ self.assertEqual(int(_m_jobs[2].status), int(job[2]))
127+
128+ sess.close()
129+
130+
131+ def test_case1(self):
132+ """Test Case 1
133+ - Job Group : Normal end
134+ - Job : All Normal end
135+ - Rollback: Normal end
136+ - Send Mail : Normal end
137+ """
138+ sess = self._db.get_session()
139+ self.set_job(sess, 'Test Case 1', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
140+ job=(True, True, True), rollback=True, mail=True)
141+
142+ self.run_job(sess)
143+
144+ self.check_job(sess, jg=RES_NORMAL_END,
145+ job=(RES_NORMAL_END, RES_NORMAL_END, RES_NORMAL_END))
146+
147+
148+ def test_case2(self):
149+ """Test Case 2
150+ - Job Group : Normal end
151+ - Job : All Normal end
152+ - Rollback: Abnormal termination
153+ - Send Mail : Normal end
154+ """
155+ sess = self._db.get_session()
156+ self.set_job(sess, 'Test Case 2', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
157+ job=(True, True, True), rollback=False, mail=True)
158+
159+ self.run_job(sess)
160+
161+ self.check_job(sess, jg=RES_NORMAL_END,
162+ job=(RES_NORMAL_END, RES_NORMAL_END, RES_NORMAL_END))
163+
164+ sess.close()
165+
166+
167+ def test_case3(self):
168+ """Test Case 3
169+ - Job Group : Normal end
170+ - Job : All Normal end
171+ - Rollback: None
172+ - Send Mail : Normal end
173+ """
174+ sess = self._db.get_session()
175+ self.set_job(sess, 'Test Case 3', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
176+ job=(True, True, True), rollback=None, mail=True)
177+
178+ self.run_job(sess)
179+
180+ self.check_job(sess, jg=RES_NORMAL_END,
181+ job=(RES_NORMAL_END, RES_NORMAL_END, RES_NORMAL_END))
182+
183+ sess.close()
184+
185+ def test_case4(self):
186+ """Test Case 4
187+ - Job Group : Normal end
188+ - Job : All Normal end
189+ - Rollback: Normal end
190+ - Send Mail : Abnormal termination
191+ """
192+ sess = self._db.get_session()
193+ self.set_job(sess, 'Test Case 4', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
194+ job=(True, True, True), rollback=None, mail=False)
195+
196+ self.run_job(sess)
197+
198+ self.check_job(sess, jg=RES_NORMAL_END,
199+ job=(RES_NORMAL_END, RES_NORMAL_END, RES_NORMAL_END))
200+
201+ sess.close()
202+
203+
204+ def test_case5(self):
205+ """Test Case 5
206+ - Job Group : Normal end
207+ - Job : All Normal end
208+ - Rollback: None
209+ - Send Mail : None
210+ """
211+ sess = self._db.get_session()
212+ self.set_job(sess, 'Test Case 5', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
213+ job=(True, True, True), rollback=None, mail=None)
214+
215+ self.run_job(sess)
216+
217+ self.check_job(sess, jg=RES_NORMAL_END,
218+ job=(RES_NORMAL_END, RES_NORMAL_END, RES_NORMAL_END))
219+
220+ sess.close()
221+
222+ def test_case6(self):
223+ """Test Case 6
224+ - Job Group : Abnormal termination
225+ - Job : First Job Abnormal termination
226+ - Rollback: Normal end
227+ - Send Mail : Normal end
228+ """
229+ sess = self._db.get_session()
230+ self.set_job(sess, 'Test Case 6', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
231+ job=(False, True, True), rollback=True, mail=True)
232+
233+ self.run_job(sess)
234+
235+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
236+ job=(RES_ROLLBACK_SUCCESSFUL_COMPLETION,
237+ RES_PENDING,
238+ RES_PENDING))
239+ sess.close()
240+
241+ def test_case7(self):
242+ """Test Case 7
243+ - Job Group : Abnormal termination
244+ - Job : First Job Abnormal termination
245+ - Rollback: Abnormal termination
246+ - Send Mail : Normal end
247+ """
248+ sess = self._db.get_session()
249+ self.set_job(sess, 'Test Case 7', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
250+ job=(False, True, True), rollback=False, mail=True)
251+
252+ self.run_job(sess)
253+
254+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
255+ job=(RES_ROLLBACK_ABEND, RES_PENDING, RES_PENDING))
256+
257+ sess.close()
258+
259+
260+ def test_case8(self):
261+ """Test Case 8
262+ - Job Group : Abnormal termination
263+ - Job : First Job Abnormal termination
264+ - Rollback: None
265+ - Send Mail : Normal end
266+ """
267+ sess = self._db.get_session()
268+ self.set_job(sess, 'Test Case 8', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
269+ job=(False, True, True), rollback=None, mail=True)
270+
271+ self.run_job(sess)
272+
273+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
274+ job=(RES_ABNORMAL_TERMINATION, RES_PENDING, RES_PENDING))
275+
276+ sess.close()
277+
278+ def test_case9(self):
279+ """Test Case 9
280+ - Job Group : Abnormal termination
281+ - Job : First Job Abnormal termination
282+ - Rollback: Normal end
283+ - Send Mail : Abnormal termination
284+ """
285+ sess = self._db.get_session()
286+ self.set_job(sess, 'Test Case 9', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
287+ job=(False, True, True), rollback=True, mail=False)
288+
289+ self.run_job(sess)
290+
291+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
292+ job=(RES_ROLLBACK_SUCCESSFUL_COMPLETION,
293+ RES_PENDING,
294+ RES_PENDING))
295+
296+ sess.close()
297+
298+ def test_case10(self):
299+ """Test Case 10
300+ - Job Group : Abnormal termination
301+ - Job : First Job Abnormal termination
302+ - Rollback: Abnormal termination
303+ - Send Mail : Abnormal termination
304+ """
305+ sess = self._db.get_session()
306+ self.set_job(sess, 'Test Case 10', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
307+ job=(False, True, True), rollback=False, mail=False)
308+
309+ self.run_job(sess)
310+
311+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
312+ job=(RES_ROLLBACK_ABEND, RES_PENDING, RES_PENDING))
313+
314+ sess.close()
315+
316+
317+ def test_case11(self):
318+ """Test Case 11
319+ - Job Group : Abnormal termination
320+ - Job : First Job Abnormal termination
321+ - Rollback : None
322+ - Send Mail : Abnormal termination
323+ """
324+ sess = self._db.get_session()
325+ self.set_job(sess, 'Test Case 11', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
326+ job=(False, True, True), rollback=None, mail=False)
327+
328+ self.run_job(sess)
329+
330+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
331+ job=(RES_ABNORMAL_TERMINATION, RES_PENDING, RES_PENDING))
332+
333+ sess.close()
334+
335+
336+ def test_case12(self):
337+ """Test Case 12
338+ - Job Group : Abnormal termination
339+ - Job : First Job Abnormal termination
340+ - Rollback: Normal end
341+ - Send Mail : None
342+ """
343+ sess = self._db.get_session()
344+ self.set_job(sess, 'Test Case 12', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
345+ job=(False, True, True), rollback=True, mail=None)
346+
347+ self.run_job(sess)
348+
349+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
350+ job=(RES_ROLLBACK_SUCCESSFUL_COMPLETION,
351+ RES_PENDING,
352+ RES_PENDING))
353+
354+ sess.close()
355+
356+ def test_case13(self):
357+ """Test Case 13
358+ - Job Group : Abnormal termination
359+ - Job : First Job Abnormal termination
360+ - Rollback: Abnormal termination
361+ - Send Mail : None
362+ </comment-en>
363+ """
364+ sess = self._db.get_session()
365+ self.set_job(sess, 'Test Case 13', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
366+ job=(False, True, True), rollback=False, mail=None)
367+
368+ self.run_job(sess)
369+
370+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
371+ job=(RES_ROLLBACK_ABEND, RES_PENDING, RES_PENDING))
372+
373+ sess.close()
374+
375+ def test_case14(self):
376+ """Test Case 14
377+ - Job Group : Abnormal termination
378+ - Job : First Job Abnormal termination
379+ - Rollback: None
380+ - Send Mail : None
381+ """
382+ sess = self._db.get_session()
383+ self.set_job(sess, 'Test Case 14', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
384+ job=(False, True, True), rollback=None, mail=None)
385+
386+ self.run_job(sess)
387+
388+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
389+ job=(RES_ABNORMAL_TERMINATION, RES_PENDING, RES_PENDING))
390+
391+ sess.close()
392+
393+ def test_case15(self):
394+ """Test Case 15
395+ - Job Group : Abnormal termination
396+ - Job : Second Job Abnormal termination
397+ - Rollback: Normal end
398+ - Send Mail : Normal end
399+ """
400+ sess = self._db.get_session()
401+ self.set_job(sess, 'Test Case 15', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
402+ job=(True, False, True), rollback=True, mail=True)
403+
404+ self.run_job(sess)
405+
406+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
407+ job=(RES_ROLLBACK_SUCCESSFUL_COMPLETION,
408+ RES_ROLLBACK_SUCCESSFUL_COMPLETION,
409+ RES_PENDING))
410+ sess.close()
411+
412+ def test_case16(self):
413+ """Test Case 16
414+ - Job Group : Abnormal termination
415+ - Job : Second Job Abnormal termination
416+ - Rollback: Abnormal termination
417+ - Send Mail : Normal end
418+ """
419+ sess = self._db.get_session()
420+ self.set_job(sess, 'Test Case 16', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
421+ job=(True, False, True), rollback=False, mail=True)
422+
423+ self.run_job(sess)
424+
425+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
426+ job=(RES_ROLLBACK_ABEND, RES_ROLLBACK_ABEND, RES_PENDING))
427+
428+ sess.close()
429+
430+
431+ def test_case17(self):
432+ """Test Case 17
433+ - Job Group : Abnormal termination
434+ - Job : Second Job Abnormal termination
435+ - Rollback: None
436+ - Send Mail : Normal end
437+ """
438+ sess = self._db.get_session()
439+ self.set_job(sess, 'Test Case 17', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
440+ job=(True, False, True), rollback=None, mail=True)
441+
442+ self.run_job(sess)
443+
444+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
445+ job=(RES_NORMAL_END, RES_ABNORMAL_TERMINATION, RES_PENDING))
446+
447+ sess.close()
448+
449+ def test_case18(self):
450+ """Test Case 9
451+ - Job Group : Abnormal termination
452+ - Job : Second Job Abnormal termination
453+ - Rollback: Normal end
454+ - Send Mail : Abnormal termination
455+ """
456+ sess = self._db.get_session()
457+ self.set_job(sess, 'Test Case 18', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
458+ job=(True, False, True), rollback=True, mail=False)
459+
460+ self.run_job(sess)
461+
462+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
463+ job=(RES_ROLLBACK_SUCCESSFUL_COMPLETION,
464+ RES_ROLLBACK_SUCCESSFUL_COMPLETION,
465+ RES_PENDING))
466+
467+ sess.close()
468+
469+ def test_case19(self):
470+ """Test Case 19
471+ - Job Group : Abnormal termination
472+ - Job : Second Job Abnormal termination
473+ - Rollback: Abnormal termination
474+ - Send Mail : Abnormal termination
475+ """
476+ sess = self._db.get_session()
477+ self.set_job(sess, 'Test Case 19', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
478+ job=(True, False, True), rollback=False, mail=False)
479+
480+ self.run_job(sess)
481+
482+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
483+ job=(RES_ROLLBACK_ABEND, RES_ROLLBACK_ABEND, RES_PENDING))
484+
485+ sess.close()
486+
487+
488+ def test_case20(self):
489+ """Test Case 11
490+ - Job Group : Abnormal termination
491+ - Job : Second Job Abnormal termination
492+ - Rollback : None
493+ - Send Mail : Abnormal termination
494+ """
495+ sess = self._db.get_session()
496+ self.set_job(sess, 'Test Case 20', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
497+ job=(True, False, True), rollback=None, mail=False)
498+
499+ self.run_job(sess)
500+
501+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
502+ job=(RES_NORMAL_END,
503+ RES_ABNORMAL_TERMINATION,
504+ RES_PENDING))
505+
506+ sess.close()
507+
508+
509+ def test_case21(self):
510+ """Test Case 21
511+ - Job Group : Abnormal termination
512+ - Job : Second Job Abnormal termination
513+ - Rollback: Normal end
514+ - Send Mail : None
515+ """
516+ sess = self._db.get_session()
517+ self.set_job(sess, 'Test Case 21', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
518+ job=(True, False, True), rollback=True, mail=None)
519+
520+ self.run_job(sess)
521+
522+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
523+ job=(RES_ROLLBACK_SUCCESSFUL_COMPLETION,
524+ RES_ROLLBACK_SUCCESSFUL_COMPLETION,
525+ RES_PENDING))
526+
527+ sess.close()
528+
529+ def test_case22(self):
530+ """Test Case 22
531+ - Job Group : Abnormal termination
532+ - Job : Second Job Abnormal termination
533+ - Rollback: Abnormal termination
534+ - Send Mail : None
535+ """
536+ sess = self._db.get_session()
537+ self.set_job(sess, 'Test Case ス22', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
538+ job=(True, False, True), rollback=False, mail=None)
539+
540+ self.run_job(sess)
541+
542+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
543+ job=(RES_ROLLBACK_ABEND, RES_ROLLBACK_ABEND, RES_PENDING))
544+
545+ sess.close()
546+
547+ def test_case23(self):
548+ """Test Case 23
549+ - Job Group : Abnormal termination
550+ - Job : Second Job Abnormal termination
551+ - Rollback: None
552+ - Send Mail : None
553+ """
554+ sess = self._db.get_session()
555+ self.set_job(sess, 'テストケース23', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
556+ job=(True, False, True), rollback=None, mail=None)
557+
558+ self.run_job(sess)
559+
560+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
561+ job=(RES_NORMAL_END,
562+ RES_ABNORMAL_TERMINATION,
563+ RES_PENDING))
564+
565+ sess.close()
566+
567+ def test_case24(self):
568+ """Test Case 24
569+ - Job Group : Abnormal termination
570+ - Job : Third Job Abnormal termination
571+ - Rollback: Normal end
572+ - Send Mail : Normal end
573+ """
574+ sess = self._db.get_session()
575+ self.set_job(sess, 'Test Case 24', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
576+ job=(True, True, False), rollback=True, mail=True)
577+
578+ self.run_job(sess)
579+
580+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
581+ job=(RES_ROLLBACK_SUCCESSFUL_COMPLETION,
582+ RES_ROLLBACK_SUCCESSFUL_COMPLETION,
583+ RES_ROLLBACK_SUCCESSFUL_COMPLETION))
584+ sess.close()
585+
586+ def test_case25(self):
587+ """Test Case 25
588+ - Job Group : Abnormal termination
589+ - Job : Third Job Abnormal termination
590+ - Rollback: Abnormal termination
591+ - Send Mail : Normal end
592+ """
593+ sess = self._db.get_session()
594+ self.set_job(sess, 'Test Case 25', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
595+ job=(True, True, False), rollback=False, mail=True)
596+
597+ self.run_job(sess)
598+
599+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
600+ job=(RES_ROLLBACK_ABEND,
601+ RES_ROLLBACK_ABEND,
602+ RES_ROLLBACK_ABEND))
603+
604+ sess.close()
605+
606+
607+ def test_case26(self):
608+ """Test Case 26
609+ - Job Group : Abnormal termination
610+ - Job : Second Job Abnormal termination
611+ - Rollback: None
612+ - Send Mail : Normal end
613+ """
614+ sess = self._db.get_session()
615+ self.set_job(sess, 'Test Case 26', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
616+ job=(True, True, False), rollback=None, mail=True)
617+
618+ self.run_job(sess)
619+
620+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
621+ job=(RES_NORMAL_END, RES_NORMAL_END, RES_ABNORMAL_TERMINATION))
622+
623+ sess.close()
624+
625+ def test_case27(self):
626+ """Test Case 27
627+ - Job Group : Abnormal termination
628+ - Job : Third Job Abnormal termination
629+ - Rollback: Normal end
630+ - Send Mail : Abnormal termination
631+ """
632+ sess = self._db.get_session()
633+ self.set_job(sess, 'Test Case 27', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
634+ job=(True, True, False), rollback=True, mail=False)
635+
636+ self.run_job(sess)
637+
638+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
639+ job=(RES_ROLLBACK_SUCCESSFUL_COMPLETION,
640+ RES_ROLLBACK_SUCCESSFUL_COMPLETION,
641+ RES_ROLLBACK_SUCCESSFUL_COMPLETION))
642+
643+ sess.close()
644+
645+ def test_case28(self):
646+ """Test Case 28
647+ - Job Group : Abnormal termination
648+ - Job : Third Job Abnormal termination
649+ - Rollback: Abnormal termination
650+ - Send Mail : Abnormal termination
651+ """
652+ sess = self._db.get_session()
653+ self.set_job(sess, 'Test Case 28', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
654+ job=(True, True, False), rollback=False, mail=False)
655+
656+ self.run_job(sess)
657+
658+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
659+ job=(RES_ROLLBACK_ABEND, RES_ROLLBACK_ABEND, RES_ROLLBACK_ABEND))
660+
661+ sess.close()
662+
663+
664+ def test_case29(self):
665+ """Test Case 29
666+ - Job Group : Abnormal termination
667+ - Job : Second Job Abnormal termination
668+ - Rollback : None
669+ - Send Mail : Abnormal termination
670+ """
671+ sess = self._db.get_session()
672+ self.set_job(sess, 'Test Case 29', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
673+ job=(True, True, False), rollback=None, mail=False)
674+
675+ self.run_job(sess)
676+
677+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
678+ job=(RES_NORMAL_END,
679+ RES_NORMAL_END,
680+ RES_ABNORMAL_TERMINATION))
681+
682+ sess.close()
683+
684+
685+ def test_case30(self):
686+ """Test Case 30
687+ - Job Group : Abnormal termination
688+ - Job : Third Job Abnormal termination
689+ - Rollback: Normal end
690+ - Send Mail : None
691+ """
692+ sess = self._db.get_session()
693+ self.set_job(sess, 'Test Case 30', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
694+ job=(True, True, False), rollback=True, mail=None)
695+
696+ self.run_job(sess)
697+
698+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
699+ job=(RES_ROLLBACK_SUCCESSFUL_COMPLETION,
700+ RES_ROLLBACK_SUCCESSFUL_COMPLETION,
701+ RES_ROLLBACK_SUCCESSFUL_COMPLETION))
702+
703+ sess.close()
704+
705+ def test_case31(self):
706+ """Test Case 31
707+ - Job Group : Abnormal termination
708+ - Job : Third Job Abnormal termination
709+ - Rollback: Abnormal termination
710+ - Send Mail : None
711+ """
712+ sess = self._db.get_session()
713+ self.set_job(sess, 'Test Case 31', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
714+ job=(True, True, False), rollback=False, mail=None)
715+
716+ self.run_job(sess)
717+
718+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
719+ job=(RES_ROLLBACK_ABEND,
720+ RES_ROLLBACK_ABEND,
721+ RES_ROLLBACK_ABEND))
722+
723+ sess.close()
724+
725+ def test_case32(self):
726+ """Test Case 32
727+ - Job Group : Abnormal termination
728+ - Job : Third Job Abnormal termination
729+ - Rollback: None
730+ - Send Mail : None
731+ """
732+ sess = self._db.get_session()
733+ self.set_job(sess, 'Test Case 32', 'b942f21c-4039-e6e9-09dc-9685985a1b84',
734+ job=(True, True, False), rollback=None, mail=None)
735+
736+ self.run_job(sess)
737+
738+ self.check_job(sess, jg=RES_ABNORMAL_TERMINATION,
739+ job=(RES_NORMAL_END,
740+ RES_NORMAL_END,
741+ RES_ABNORMAL_TERMINATION))
742+
743+ sess.close()
744+
745+def worker_debug(db, jobgroup_id):
746+ logger = logging.getLogger('pysilhouette.performer.worker')
747+ session = db.get_session()
748+ jg = jobgroup_findbyid(session, jobgroup_id)
749+
750+ def po(msg):
751+ logger.debug(str(msg))
752+
753+ def poc(msg, code):
754+ import pysilhouette.db.model
755+ if pysilhouette.db.model.RES_PENDING == str(code):
756+ po(msg+' : Pending')
757+ if pysilhouette.db.model.RES_RUNNING == str(code):
758+ po(msg+' : Running')
759+ if pysilhouette.db.model.RES_NORMAL_END == str(code):
760+ po(msg+' : Normal end')
761+ if pysilhouette.db.model.RES_ABNORMAL_TERMINATION == str(code):
762+ po(msg+' : Abnormal termination')
763+ if pysilhouette.db.model.RES_ROLLBACK == str(code):
764+ po(msg+' : Rollback running')
765+ if pysilhouette.db.model.RES_ROLLBACK_ABEND == str(code):
766+ po(msg+' : Rollback abend')
767+ if pysilhouette.db.model.RES_ROLLBACK_SUCCESSFUL_COMPLETION == str(code):
768+ po(msg+' : Rollback successful completion')
769+ def pc(uni):
770+ if not uni:
771+ return str(uni)
772+ else:
773+ return str(uni.encode('utf-8'))
774+
775+ # debug print
776+ po('------------------------------')
777+ po("Job Group ID=%s" % jg.id)
778+ po("Target host ip address=%s" % str(jg.uniq_key))
779+ po("Job Group name=%s" % pc(jg.name))
780+ poc("Job Group status now=%s" % str(jg.status), jg.status)
781+ po("Finish Command='%s'" % pc(jg.finish_command))
782+ po('------------------------------')
783+
784+ jobs = job_findbyjobgroup_id(session, jg.id, False)
785+ for j in jobs:
786+ po("Job ID=%s" % str(j.id))
787+ po("Job Name=%s" % pc(j.name))
788+ po("Job Order=%s" % str(j.order))
789+ po("Job Progress=%s" % str(j.progress))
790+ po("Job Action Commaind='%s'" % pc(j.action_command))
791+ po("Job Rollbakc Command='%s'"% pc(j.rollback_command))
792+ poc("Job Status=%s" % str(j.status), str(j.status))
793+ po("Job Action Exit Code='%s'" % str(j.action_exit_code))
794+ po("Job Action Stdout='%s'" % pc(j.action_stdout))
795+ po("Job Action Stderr='%s'" % pc(j.action_stderr))
796+ po("Job Rollback Exit Code='%s'" % str(j.rollback_exit_code))
797+ po("Job Rollback Stdout='%s'" % pc(j.rollback_stdout))
798+ po("Job Rollback Stderr='%s'" % pc(j.rollback_stderr))
799+ po('------------------------------')
800+
801+ session.close()
802+
803+def test_setup(cf):
804+ # --
805+ db = Database(cf['database.url'], encoding='utf-8', convert_unicode=True)
806+ reload_mappers(db.get_metadata())
807+
808+ db.get_metadata().drop_all()
809+ db.get_metadata().create_all()
810+
811+ f_cmd = (u'python /root/repository/pysilhouette_svn/pysilhouette/job/sendmail.py'
812+ u' --from="root@localhost"'
813+ u' --to="root@localhost"'
814+ u' --subject="test"'
815+ u' --hostname="localhost"'
816+ u' --port="25"'
817+ u' --msg="%s"'
818+ u' --charset="utf-8"')
819+
820+ session = db.get_session()
821+ jg1 = JobGroup(u'get date', '172.16.0.123')
822+ jg1.finish_command = f_cmd % jg1.name
823+ jb1 = Job(u'get date','0',u'/bin/date error')
824+ jb1.rollback_command = u'/bin/date'
825+ jg1.jobs.append(jb1)
826+ jg2 = JobGroup(u'get route', '172.16.0.123')
827+ jg2.finish_command = f_cmd % jg2.name
828+ jg2.jobs.append(Job(u'get route','1', u'/sbin/route'))
829+ jg3 = JobGroup(u'print string', '172.16.0.123')
830+ jg3.finish_command = f_cmd % jg3.name
831+ jg3.jobs.append(Job(u'print string','2', u'/bin/echo test'))
832+ session.add_all([jg1,jg2,jg3])
833+ #session.add(jg1)
834+ session.commit()
835+
836+ from pysilhouette.db.access import jobgroup_findbystatus
837+ _m_jgs = jobgroup_findbystatus(session)
838+ return db, session, _m_jgs
839+
840+
841+class SuiteWorker(unittest.TestSuite):
842+ def __init__(self):
843+ #tests = ['test_case1']
844+ tests = ['test_case1', 'test_case2', 'test_case3',
845+ 'test_case4', 'test_case5', 'test_case6',
846+ 'test_case7', 'test_case8', 'test_case9',
847+ 'test_case10', 'test_case11', 'test_case12',
848+ 'test_case13', 'test_case14', 'test_case15',
849+ 'test_case16', 'test_case17', 'test_case18',
850+ 'test_case19', 'test_case20', 'test_case21',
851+ 'test_case22', 'test_case23', 'test_case24',
852+ 'test_case25', 'test_case26', 'test_case27',
853+ 'test_case28', 'test_case29', 'test_case30',
854+ 'test_case31', 'test_case32']
855+
856+ unittest.TestSuite.__init__(self,map(TestWorker, tests))
857+
858+def all_suite_worker():
859+ return unittest.TestSuite([SuiteWorker()])
860+
861+if __name__ == '__main__':
862+ job_path = '/root/repository/pysilhouette_svn/job/'
863+
864+ os.environ['PYSILHOUETTE_CONF'] = '/etc/opt/pysilhouette/silhouette.conf'
865+ cf = readconf(os.environ['PYSILHOUETTE_CONF'])
866+ pysilhouette.log.reload_conf(cf["env.sys.log.conf.path"])
867+
868+ ok_f_cmd = (u'python /root/pysilhouette/pysilhouette/job/sendmail.py'
869+ u' --from="root@localhost"'
870+ u' --to="root@localhost"'
871+ u' --subject="Results of Worker(%s)"'
872+ u' --hostname="localhost"'
873+ u' --port="25"'
874+ u' --msg="%s"'
875+ u' --charset="utf-8"')
876+
877+ ng_f_cmd = (u'python /root/pysilhouette/pysilhouette/job/sendmail_dummy.py'
878+ u' --from="root@localhost"'
879+ u' --to="root@localhost"'
880+ u' --subject="Results of Worker(%s)"'
881+ u' --hostname="localhost"'
882+ u' --port="25"'
883+ u' --msg="%s"'
884+ u' --charset="utf-8"')
885+
886+ unittest.TextTestRunner(verbosity=2).run(all_suite_worker())
--- /dev/null
+++ b/pysilhouette/uniqkey.py
@@ -0,0 +1,45 @@
1+#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+#
4+# This file is part of Pysilhouette.
5+#
6+# Copyright (c) 2009 HDE, Inc.
7+#
8+# Permission is hereby granted, free of charge, to any person obtaining a copy
9+# of this software and associated documentation files (the "Software"), to deal
10+# in the Software without restriction, including without limitation the rights
11+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+# copies of the Software, and to permit persons to whom the Software is
13+# furnished to do so, subject to the following conditions:
14+#
15+# The above copyright notice and this permission notice shall be included in
16+# all copies or substantial portions of the Software.
17+#
18+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+# THE SOFTWARE.
25+#
26+
27+"""
28+@author: Kei Funagayama <kei@karesansui-project.info>
29+"""
30+
31+import sys
32+import random
33+
34+UNIQ_TPL = '%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x'
35+
36+def getuniqkey():
37+ _r = []
38+ for _i in xrange(0,16):
39+ _r.append(random.randint(0, 255))
40+ return UNIQ_TPL % tuple(_r)
41+
42+if __name__ == '__main__':
43+ print >>sys.stdout, getuniqkey()
44+
45+
--- /dev/null
+++ b/pysilhouette/util.py
@@ -0,0 +1,202 @@
1+#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+#
4+# This file is part of Pysilhouette.
5+#
6+# Copyright (c) 2009 HDE, Inc.
7+#
8+# Permission is hereby granted, free of charge, to any person obtaining a copy
9+# of this software and associated documentation files (the "Software"), to deal
10+# in the Software without restriction, including without limitation the rights
11+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+# copies of the Software, and to permit persons to whom the Software is
13+# furnished to do so, subject to the following conditions:
14+#
15+# The above copyright notice and this permission notice shall be included in
16+# all copies or substantial portions of the Software.
17+#
18+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+# THE SOFTWARE.
25+#
26+
27+"""
28+@author: Kei Funagayama <kei@karesansui-project.info>
29+"""
30+
31+import os
32+import pwd
33+import grp
34+import subprocess
35+import time
36+import logging
37+
38+def is_empty(s):
39+ """is empty
40+ @param s: string
41+ @type s: str
42+ """
43+ if s and 0 < len(s.strip()):
44+ return False
45+ else:
46+ return True
47+
48+def astrftime(tm):
49+ return time.strftime("%m/%d/%Y %H:%M:%S", time.localtime(tm))
50+
51+def split_shell_command(cmd):
52+ ret = []
53+ if is_empty(cmd) is False:
54+ vs =cmd.split(' ')
55+ for v in vs:
56+ v = v.strip()
57+ if is_empty(v): continue
58+ ret.append(v)
59+ return ret
60+
61+def write_pidfile(fname, pid):
62+ try:
63+ fp = open(fname, 'w')
64+ try:
65+ fp.write('%d' % pid)
66+ return True
67+ finally:
68+ fp.close()
69+
70+ except:
71+ return False
72+
73+def read_pidfile(fname):
74+ try:
75+ fp = open(fname, 'r')
76+ try:
77+ return fp.read()
78+ finally:
79+ fp.close()
80+ except:
81+ return ''
82+
83+
84+def create_fifo(fname, user, group, perm):
85+ """create fifo file.
86+ @param fname: file name
87+ @type fname: str
88+ @param: user: username
89+ @type: user: str
90+ @param: group: groupname
91+ @type: group: str
92+ @param: perm: Permission - example) '0666'
93+ @type: perm: str(4)
94+ """
95+ try:
96+ perm8 = int(perm, 8)
97+ os.mkfifo(fname, perm8)
98+ os.chown(fname, pwd.getpwnam(user)[2], grp.getgrnam(group)[2])
99+ os.chmod(fname, perm8)
100+ return True
101+ except OSError ,o:
102+ return False
103+
104+def kill_proc(proc):
105+ if proc and hasattr(os, 'kill'):
106+ import signal
107+ try:
108+ os.kill(proc.pid, signal.SIGTERM)
109+ return True
110+ except:
111+ try:
112+ os.kill(proc.pid, signal.SIGKILL)
113+ except:
114+ return False
115+
116+def popen(cmd, timeout, waittime, lang, limit=4096, job_id=None):
117+
118+ proc_info = {}
119+
120+ timeout = int(timeout)
121+ waittime = int(waittime)
122+ env = os.environ.copy()
123+ env['LANG'] = lang
124+ if not (job_id is None):
125+ env['JOB_ID'] = str(job_id)
126+
127+ proc = subprocess.Popen(cmd,
128+ stdout=subprocess.PIPE,
129+ stderr=subprocess.PIPE,
130+ #env=os.environ,
131+ env=env,
132+ shell=False,
133+ )
134+
135+ # parent process wait.
136+ start_time = time.time()
137+ while True:
138+ r = proc.poll()
139+ if r is None:
140+ interval = int(time.time() - start_time)
141+ if 0 < timeout and timeout < interval:
142+ try:
143+ kill_proc(proc)
144+ finally:
145+ break
146+ time.sleep(waittime)
147+ else:
148+ break
149+
150+ stdout = stderr = ''
151+ for x in proc.stdout:
152+ stdout += x
153+ for x in proc.stderr:
154+ stderr += x
155+
156+ if stdout and limit < len(stdout):
157+ proc_info['stdout'] = stdout[:limit]
158+ else:
159+ proc_info['stdout'] = stdout
160+ if stderr and limit < len(stderr):
161+ proc_info['stderr'] = stderr[:limit]
162+ else:
163+ proc_info['stderr'] = stderr
164+
165+ proc_info['pid'] = proc.pid
166+ proc_info['r_code'] = r
167+
168+ return proc, proc_info
169+
170+def debug_popen(proc, proc_info):
171+ logger = logging.getLogger('pysilhouette.popen')
172+
173+ #logger.debug("Command : %s" % cmd)
174+ logger.debug("Sub process id. id=%s" % proc.pid)
175+ logger.debug("stdout : %s" % proc_info['stdout'])
176+ logger.debug("stderr : %s" % proc_info['stderr'])
177+
178+ if os.WIFSTOPPED(proc_info['r_code']) is True:
179+ logger.debug('The process stopped. code=%s' % proc_info['r_code'])
180+ if os.WIFSIGNALED(proc_info['r_code']) is True:
181+ logger.debug('The process stopped by the signal. code=%s' % \
182+ proc_info['r_code'])
183+ if os.WIFEXITED(proc_info['r_code']) is True:
184+ logger.debug('The process stopped by the system call. code=%s' % \
185+ proc_info['r_code'])
186+ logger.debug('Integer parameter passed to system call. parameter=%s' % \
187+ os.WEXITSTATUS(proc_info['r_code']))
188+ logger.debug('The process stopped by the signal no. no=%s' % \
189+ os.WSTOPSIG(proc_info['r_code']))
190+ logger.debug('The process finished by the signal no. no=%s' % \
191+ os.WTERMSIG(proc_info['r_code']))
192+
193+def is_int(val):
194+ try:
195+ int(val)
196+ return True
197+ except:
198+ return False
199+
200+if __name__ == '__main__':
201+ #print popen(cmd='efdsfdsafdsafdsafdsafdsa', timeout=3, waittime=1, lang='C')
202+ print popen(cmd='date', timeout=3, waittime=1, lang='C')
--- /dev/null
+++ b/pysilhouette/worker.py
@@ -0,0 +1,330 @@
1+#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+#
4+# This file is part of Pysilhouette.
5+#
6+# Copyright (c) 2009 HDE, Inc.
7+#
8+# Permission is hereby granted, free of charge, to any person obtaining a copy
9+# of this software and associated documentation files (the "Software"), to deal
10+# in the Software without restriction, including without limitation the rights
11+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+# copies of the Software, and to permit persons to whom the Software is
13+# furnished to do so, subject to the following conditions:
14+#
15+# The above copyright notice and this permission notice shall be included in
16+# all copies or substantial portions of the Software.
17+#
18+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+# THE SOFTWARE.
25+#
26+
27+"""
28+@author: Kei Funagayama <kei@karesansui-project.info>
29+"""
30+
31+import subprocess
32+import os
33+import traceback
34+import logging
35+
36+import pysilhouette
37+from pysilhouette.db import *
38+from pysilhouette.db.model import *
39+from pysilhouette.db.access import jobgroup_findbyid, \
40+ job_findbyjobgroup_id, jobgroup_update, job_update, \
41+ job_result_action, job_result_rollback
42+
43+from pysilhouette.util import popen, kill_proc, is_empty, split_shell_command
44+
45+class SilhouetteWorkerException(pysilhouette.SilhouetteException):
46+ """Worker execution error.
47+ """
48+ pass
49+
50+class Worker:
51+ """Worker Base class
52+ """
53+
54+ def __init__(self, cf, db, jobgroup_id):
55+ self._cf = cf
56+ self._db = db
57+ self._jobgroup_id = jobgroup_id
58+ self.logger = logging.getLogger('pysilhouette.performer.worker')
59+
60+ def run(self):
61+ try:
62+ session = self._db.get_session()
63+ self.logger.debug('Session was obtained from the database. - session=%s' % session)
64+ self._m_jg = jobgroup_findbyid(session,
65+ self._jobgroup_id,
66+ self._cf['env.uniqkey'])
67+
68+ if self._m_jg is None: return False
69+
70+ jobgroup_update(session, self._m_jg, JOBGROUP_STATUS['RUN']) # JobGroup UPDATE
71+ _m_jobs = job_findbyjobgroup_id(session, self._jobgroup_id, False) # order asc
72+
73+ # action
74+ ret = False
75+ err = False
76+ try:
77+ ret = self._action(session, _m_jobs)
78+ except Exception, e:
79+ self.logger.info('Failed to perform the job action. Exceptions are not expected. - jobgroup_id=%d : %s'
80+ % (self._jobgroup_id, ','.join(e.args)))
81+
82+ jobgroup_update(session, self._m_jg, JOBGROUP_STATUS['APPERR'])
83+ t_logger = logging.getLogger('pysilhouette_traceback')
84+ t_logger.info(traceback.format_exc())
85+ err = True
86+
87+ try:
88+ if err is False:
89+ if ret is True:
90+ # normal
91+ jobgroup_update(session, self._m_jg, JOBGROUP_STATUS['OK'])
92+ else:
93+ # rollback
94+ jobgroup_update(session, self._m_jg, JOBGROUP_STATUS['NG']) # JobGroup UPDATE
95+ try:
96+ self._rollback(session, _m_jobs)
97+ except Exception, e:
98+ self.logger.info('Failed to perform a rollback. Exceptions are not expected. - jobgroup_id=%d : %s'
99+ % (self._jobgroup_id, ','.join(e.args)))
100+ t_logger = logging.getLogger('pysilhouette_traceback')
101+ t_logger.info(traceback.format_exc())
102+
103+ finally:
104+ # finish
105+ try:
106+ self._finish()
107+ except Exception, e:
108+ self.logger.info('Failed to perform the finish action. Exceptions are not expected. - jobgroup_id=%d : %s'
109+ % (self._jobgroup_id, ','.join(e.args)))
110+ t_logger = logging.getLogger('pysilhouette_traceback')
111+ t_logger.info(traceback.format_exc())
112+ finally:
113+ self.logger.debug('close database session, session=%s' % session)
114+ session.close()
115+
116+ def _action(self, session, m_jobs):
117+ raise SilhouetteWorkerException('Please override this method.')
118+
119+ def _rollback(self, session, m_jobs):
120+ raise SilhouetteWorkerException('Please override this method.')
121+
122+ def _finish(self):
123+ proc = None
124+ proc_info = []
125+ cmd = self._m_jg.finish_command
126+
127+ if is_empty(cmd):
128+ self.logger.debug('finish command not running!!- jobgroup_id=%d' % (self._m_jg.id))
129+ return False # No finish Command
130+ else:
131+ try:
132+ self.logger.info('finish command running!! - jobgroup_id=%d : cmd=%s'
133+ % (self._m_jg.id, cmd))
134+
135+ lcmd = split_shell_command(cmd)
136+
137+ if self.chk_whitelist(lcmd[0]):
138+ try:
139+ (proc, proc_info) = popen(lcmd,
140+ self._cf['job.popen.timeout'],
141+ self._cf['job.popen.waittime'],
142+ self._cf['job.popen.env.lang'],
143+ )
144+ self.logger.debug('Of commands executed stdout=%s' % proc_info['stdout'])
145+ self.logger.debug('Of commands executed stderr=%s' % proc_info['stderr'])
146+
147+ except OSError, oe:
148+ self.logger.info('finish command system failed!! job_id=%d : cmd=%s'
149+ % (m_job.id, cmd))
150+ raise oe
151+
152+ if proc_info['r_code'] == 0:
153+ self.logger.info('finish command successful!! - jobgroup_id=%d : cmd=%s'
154+ % (self._m_jg.id, cmd))
155+ else:
156+ self.logger.info('finish command failed!! - jobgroup_id=%d : cmd=%s'
157+ % (self._m_jg.id, cmd))
158+ return True
159+
160+ else:
161+ # whitelist
162+ self.logger.info('Tried to run the rollback command that is not registered in the whitelist. - jobgroup_id=%d : cmd=%s'
163+ % (self._m_jg.id, cmd))
164+
165+ finally:
166+ kill_proc(proc)
167+
168+
169+
170+ def chk_whitelist(self, cmd):
171+ flag = self._cf['job.whitelist.flag'].strip()
172+
173+ if is_empty(flag) is True:
174+ self.logger.debug("Whitelist feature [OFF] - empty")
175+ return True # Unconditional
176+
177+ if flag != "1":
178+ self.logger.debug("Whitelist feature [OFF]")
179+ return True # Unconditional
180+
181+ self.logger.debug("Whitelist feature [ON]")
182+ fp = open(self._cf['job.whitelist.path'], 'r')
183+ try:
184+ for line in fp.readlines():
185+ if cmd.strip() == line.strip():
186+ return True
187+ finally:
188+ fp.close()
189+
190+ return False
191+
192+class SimpleWorker(Worker):
193+ """Sequential Worker Class
194+ """
195+
196+ def _action(self, session, m_jobs):
197+ ret = True
198+ for m_job in m_jobs: # job(N) execute
199+ job_update(session, m_job, ACTION_STATUS['RUN']) # Job UPDATE
200+ proc = None
201+ proc_info = []
202+ try:
203+ cmd = m_job.action_command
204+ self.logger.info('action command running!!- jobgroup_id=%d : cmd=%s'
205+ % (m_job.id, cmd))
206+
207+ lcmd = split_shell_command(cmd)
208+ if self.chk_whitelist(lcmd[0]):
209+ try:
210+ (proc, proc_info) = popen(lcmd,
211+ self._cf['job.popen.timeout'],
212+ self._cf['job.popen.waittime'],
213+ self._cf['job.popen.env.lang'],
214+ m_job.STD_OUTPUT_LIMIT,
215+ m_job.id,
216+ )
217+
218+ self.logger.debug('Of commands executed stdout=%s' % proc_info['stdout'])
219+ self.logger.debug('Of commands executed stderr=%s' % proc_info['stderr'])
220+
221+ except OSError, oe:
222+ self.logger.info('action command system failed!! job_id=%d : cmd=%s'
223+ % (m_job.id, cmd))
224+ raise oe
225+
226+ job_result_action(session, m_job, proc_info) # Job result UPDATE
227+ if proc_info['r_code'] == 0: # Normal end
228+ self.logger.info('action command was successful!! job_id=%d : cmd=%s'
229+ % (m_job.id, cmd))
230+ job_update(session, m_job, ACTION_STATUS['OK']) # Job UPDATE
231+ else: # Abnormal termination
232+ self.logger.info('action command failed!! job_id=%d : cmd=%s'
233+ % (m_job.id, cmd))
234+ job_update(session, m_job, ACTION_STATUS['NG']) # Job UPDATE
235+ ret = False
236+ break
237+ else:
238+ # whitelist error
239+ self.logger.info('Tried to run the action command that is not registered in the whitelist. job_id=%d : cmd=%s'
240+ % (m_job.id, cmd))
241+ m_job.action_stderr = "Command is not registered to run the whitelist."
242+ job_update(session, m_job, ACTION_STATUS['WHITELIST']) # Job UPDATE
243+ ret = False
244+ break
245+
246+ finally:
247+ kill_proc(proc)
248+
249+ return ret
250+
251+ def _rollback(self, session, m_jobs):
252+ for m_job in m_jobs:
253+
254+ if m_job.is_rollback() and m_job.status in (ACTION_STATUS['RUN'],
255+ ACTION_STATUS['OK'],
256+ ACTION_STATUS['NG'],
257+ ACTION_STATUS['WHITELIST']):
258+ # rollback exec
259+ proc = None
260+ proc_info = []
261+ try:
262+ #cmd = m_job.action_command
263+ cmd = m_job.rollback_command
264+ self.logger.info('rollback command running!!- jobgroup_id=%d : cmd=%s'
265+ % (m_job.id, cmd))
266+
267+ lcmd = split_shell_command(cmd)
268+
269+ if self.chk_whitelist(lcmd[0]):
270+ try:
271+ (proc, proc_info) = popen(lcmd,
272+ self._cf['job.popen.timeout'],
273+ self._cf['job.popen.waittime'],
274+ self._cf['job.popen.env.lang'],
275+ m_job.STD_OUTPUT_LIMIT,
276+ )
277+
278+ self.logger.debug('Of commands executed stdout=%s' % proc_info['stdout'])
279+ self.logger.debug('Of commands executed stderr=%s' % proc_info['stderr'])
280+
281+ except OSError, oe:
282+ self.logger.info('rollback command system failed!! job_id=%d : cmd=%s'
283+ % (m_job.id, cmd))
284+ raise oe
285+
286+ job_result_rollback(session, m_job, proc_info) # Job result UPDATE
287+ if proc_info['r_code'] == 0: # Normal end
288+ self.logger.info('rollback command was successful!! job_id=%d : cmd=%s'
289+ % (m_job.id, cmd))
290+ job_update(session, m_job, ROLLBACK_STATUS['OK']) # Job UPDATE
291+ else: # Abnormal termination
292+ self.logger.info('rollback command failed!! job_id=%d : cmd=%s'
293+ % (m_job.id, cmd))
294+ job_update(session, m_job, ROLLBACK_STATUS['NG']) # Job UPDATE
295+
296+ else:
297+ # whitelist error
298+ self.logger.info('Tried to run the rollback command that is not registered in the whitelist. job_id=%d : cmd=%s'
299+ % (m_job.id, cmd))
300+ m_job.rollback_stderr = "Command is not registered to run the whitelist."
301+ job_update(session, m_job, ROLLBACK_STATUS['WHITELIST']) # Job UPDATE
302+
303+ finally:
304+ kill_proc(proc)
305+ else:
306+ self.logger.debug('Does not rollback the process. - job_id=%d : status=%s'
307+ % (m_job.id, m_job.status))
308+
309+if __name__ == '__main__':
310+ """Testing
311+ """
312+ import pysilhouette.tests.testworker
313+ # dev start
314+ _env = os.environ
315+ _env['PYSILHOUETTE_CONF'] = '/etc/opt/pysilhouette/silhouette.conf'
316+ # dev end
317+
318+ # init
319+ from pysilhouette.prep import readconf
320+ cf = readconf(os.environ['PYSILHOUETTE_CONF'])
321+ pysilhouette.cf = pysilhouette.prep.readconf(os.environ['PYSILHOUETTE_CONF'])
322+ pysilhouette.log.reload_conf(pysilhouette.cf["env.sys.log.conf.path"])
323+
324+ (db, session, _m_jgs) = pysilhouette.tests.testworker.test_setup(cf)
325+ # run
326+ for _m_jg in _m_jgs:
327+ _w = SimpleWorker(db, _m_jg.id)
328+ _w.run()
329+ pysilhouette.tests.testworker.worker_debug(db, _m_jg.id)
330+ session.close()
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,7 @@
1+[global]
2+verbose=1
3+[install]
4+optimize = 1
5+[bdist_rpm]
6+release=1
7+doc_files=doc tool example
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,66 @@
1+#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+#
4+# This file is part of Pysilhouette.
5+#
6+# Copyright (c) 2009 HDE, Inc.
7+#
8+# Permission is hereby granted, free of charge, to any person obtaining a copy
9+# of this software and associated documentation files (the "Software"), to deal
10+# in the Software without restriction, including without limitation the rights
11+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+# copies of the Software, and to permit persons to whom the Software is
13+# furnished to do so, subject to the following conditions:
14+#
15+# The above copyright notice and this permission notice shall be included in
16+# all copies or substantial portions of the Software.
17+#
18+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+# THE SOFTWARE.
25+#
26+
27+"""
28+@author: Kei Funagayama <kei.funagayama@hde.co.jp>
29+"""
30+
31+from distutils.core import setup
32+import glob
33+import os
34+import sys
35+# DISTUTILS_DEBUG=1
36+from pysilhouette import __app__, __version__, __release__
37+
38+__prog__ = 'silhouette'
39+__progd__='%sd' % __prog__
40+
41+setup(name=__app__,
42+ version= "%s.%s" % (__version__, __release__),
43+ description='Pysilhouette is an application running in the background system.',
44+ long_description="""Pysilhouette is an application running in the background system.
45+ A system executes the job command registered into the database.
46+ 100% Pure Python.""",
47+ maintainer='HDE Package Maintainer',
48+ maintainer_email='info@hde.co.jp',
49+ url='http://sourceforge.jp/projects/pysilhouette/',
50+ download_url='',
51+ packages=['pysilhouette',
52+ 'pysilhouette.db',
53+ 'pysilhouette.tests',
54+ ],
55+ py_modules=[],
56+ scripts=[],
57+ license='The MIT License',
58+ keywords='',
59+ platforms='Linux',
60+ classifiers=['Environment :: No Input/Output (Daemon)',
61+ 'License :: OSI Approved :: MIT License',
62+ 'Natural Language :: Japanese',
63+ 'Programming Language :: Python :: 2.4',
64+ ],
65+)
66+
--- /dev/null
+++ b/tool/cleanupdb.py
@@ -0,0 +1,84 @@
1+#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+#
4+# This file is part of Pysilhouette.
5+#
6+# Copyright (c) 2009 HDE, Inc.
7+#
8+# Permission is hereby granted, free of charge, to any person obtaining a copy
9+# of this software and associated documentation files (the "Software"), to deal
10+# in the Software without restriction, including without limitation the rights
11+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+# copies of the Software, and to permit persons to whom the Software is
13+# furnished to do so, subject to the following conditions:
14+#
15+# The above copyright notice and this permission notice shall be included in
16+# all copies or substantial portions of the Software.
17+#
18+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+# THE SOFTWARE.
25+#
26+
27+"""
28+@author: Kei Funagayama <kei.funagayama@hde.co.jp>
29+"""
30+
31+import sys
32+import os
33+import logging
34+
35+from pysilhouette.prep import getopts, readconf, chkopts
36+from pysilhouette.db import Database, reload_mappers
37+
38+def main():
39+ (opts, args) = getopts()
40+ if chkopts(opts) is True:
41+ return 1
42+
43+ try:
44+ opts.config = os.path.abspath(opts.config)
45+ except AttributeError, e:
46+ print >>sys.stderr, 'No configuration file path.'
47+ return 1
48+
49+ cf = readconf(opts.config)
50+ if cf is None:
51+ print >>sys.stderr, 'Failed to load the config file.'
52+ return 1
53+
54+ try:
55+ db = Database(cf['database.url'],
56+ encoding="utf-8",
57+ convert_unicode=True,
58+ assert_unicode=False, # product
59+ #assert_unicode='warn', # dev
60+ #echo = opts.verbose,
61+ #echo_pool = opts.verbose,
62+ echo=True,
63+ echo_pool=True
64+ )
65+
66+ reload_mappers(db.get_metadata())
67+
68+ except Exception, e:
69+ print >>sys.stderr, 'Initializing a database error'
70+ raise
71+
72+ try:
73+ db.get_metadata().drop_all()
74+ db.get_metadata().create_all()
75+ print >>sys.stdout, 'Cleanup Database [OK]'
76+ except Exception,e:
77+ print >>sys.stderr, 'database drop and create error.'
78+ raise
79+
80+
81+ return 0
82+
83+if __name__ == '__main__':
84+ sys.exit(main())
--- /dev/null
+++ b/tool/epydoc.sh
@@ -0,0 +1,22 @@
1+#!/bin/bash
2+
3+name=pysilhouette
4+export PYTHONPATH=${PYTHONPATH}:/opt/pysilhouette/lib/python
5+
6+script_dir=`dirname $0`
7+pushd $script_dir >/dev/null 2>&1
8+# shell directory.
9+script_dir=`pwd`
10+popd >/dev/null 2>&1
11+
12+epydoc_config=${script_dir}/../doc/epydoc.cfg
13+
14+
15+target=/var/www/html/${name}-doc
16+
17+if [ -e ${target} ]; then
18+ rm -fr ${target}
19+fi
20+mkdir -p ${target}
21+
22+epydoc --config ${epydoc_config}
--- /dev/null
+++ b/tool/setdummy.py
@@ -0,0 +1,99 @@
1+]#!/usr/bin/env python
2+# -*- coding: utf-8 -*-
3+#
4+# This file is part of Pysilhouette.
5+#
6+# Copyright (c) 2009 HDE, Inc.
7+#
8+# Permission is hereby granted, free of charge, to any person obtaining a copy
9+# of this software and associated documentation files (the "Software"), to deal
10+# in the Software without restriction, including without limitation the rights
11+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+# copies of the Software, and to permit persons to whom the Software is
13+# furnished to do so, subject to the following conditions:
14+#
15+# The above copyright notice and this permission notice shall be included in
16+# all copies or substantial portions of the Software.
17+#
18+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+# THE SOFTWARE.
25+#
26+
27+"""
28+@author: Kei Funagayama <kei.funagayama@hde.co.jp>
29+"""
30+
31+import sys
32+import os
33+import logging
34+
35+from pysilhouette.prep import getopts, readconf, chkopts
36+from pysilhouette.db import Database, reload_mappers
37+from pysilhouette.db.model import JobGroup, Job
38+
39+NUM = 10
40+
41+def main():
42+ (opts, args) = getopts()
43+ if chkopts(opts) is True:
44+ return 1
45+
46+ try:
47+ opts.config = os.path.abspath(opts.config)
48+ except AttributeError, e:
49+ print >>sys.stderr, 'No configuration file path.'
50+ return 1
51+
52+ cf = readconf(opts.config)
53+ if cf is None:
54+ print >>sys.stderr, 'Failed to load the config file.'
55+ return 1
56+
57+ try:
58+ db = Database(cf['database.url'],
59+ encoding="utf-8",
60+ convert_unicode=True,
61+ assert_unicode=False, # product
62+ #assert_unicode='warn', # dev
63+ #echo = opts.verbose,
64+ #echo_pool = opts.verbose,
65+ echo=True,
66+ echo_pool=True
67+ )
68+
69+ reload_mappers(db.get_metadata())
70+ session = db.get_session()
71+
72+ except Exception, e:
73+ print >>sys.stderr, 'Initializing a database error'
74+ raise
75+
76+ try:
77+ jgs = []
78+ for i in range(NUM):
79+ jg_name = u'JobGroup-%d' % i
80+ jg_ukey = unicode(cf['env.uniqkey'], "utf-8")
81+ j_name = u'Job-%d' % i
82+ j_order = i
83+ j_cmd = u'/bin/echo num=%d' % i
84+ jg = JobGroup(jg_name, jg_ukey)
85+ jg.jobs.append(
86+ Job(j_name, j_order, j_cmd))
87+ jgs.append(jg)
88+
89+ session.add_all(jgs)
90+ session.commit()
91+ session.close()
92+ print >>sys.stdout, 'Insert JobGroup and Job. num=%d [OK]' % NUM
93+ except:
94+ print >>sys.stderr, 'Failed to add JobGroup and Job.'
95+ raise
96+
97+if __name__ == '__main__':
98+ sys.exit(main())
99+
Show on old repository browser