nand 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "nand"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ require "pry"
11
+ Pry.start
12
+
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'nand'
4
+ require 'nand/cli'
5
+
6
+ def split_options( argv )
7
+ if argv.include? "--"
8
+ idx = argv.index("--")
9
+ [argv[0...idx], argv[idx+1..-1] ]
10
+ else
11
+ [argv, []]
12
+ end
13
+ end
14
+
15
+ wrap_exception{
16
+ argv, additional = split_options(ARGV)
17
+ Nand.additional_argv = additional
18
+ Cli.start(argv)
19
+ }
@@ -0,0 +1,47 @@
1
+ require "nand/version"
2
+
3
+ module Nand
4
+ def wrap_exception
5
+ yield
6
+ rescue => e
7
+ STDERR.puts e.message
8
+ #STDERR.puts "\n\t" + e.backtrace.join("\n\t")
9
+ exit -1
10
+ end
11
+ def self.additional_argv=(argv)
12
+ @@additional_argv ||= argv.freeze
13
+ end
14
+ def self.additional_argv
15
+ @@additional_argv
16
+ end
17
+ def string_to_stdio( str )
18
+ if str.to_s.upcase =~/^STD(OUT|ERR|IN)$/
19
+ eval(str)
20
+ else
21
+ str
22
+ end
23
+ end
24
+ def restore_options( options )
25
+ opts = options.dup
26
+ unless opts[:debug]
27
+ opts[:out] &&= string_to_stdio(opts[:out])
28
+ opts[:err] &&= string_to_stdio(opts[:err])
29
+ opts[:in] &&= string_to_stdio(opts[:in] )
30
+
31
+ opts[:daemon_log] &&= string_to_stdio(opts[:daemon_log])
32
+ opts[:daemon_out] &&= string_to_stdio(opts[:daemon_out])
33
+ opts[:daemon_err] &&= string_to_stdio(opts[:daemon_err])
34
+
35
+ else
36
+ opts[:out] = STDOUT
37
+ opts[:err] = STDERR
38
+
39
+ opts[:daemon_out] = STDOUT
40
+ opts[:daemon_err] = STDERR
41
+ opts[:daemon_log] = STDOUT
42
+ end
43
+ opts
44
+ end
45
+ end
46
+
47
+ include Nand
@@ -0,0 +1,126 @@
1
+ # -*-mode: ruby; coding: utf-8 -*-
2
+
3
+ require 'pathname'
4
+ require 'thor'
5
+
6
+ require 'nand/daemon'
7
+ require 'nand/logger'
8
+ require 'nand/launcher'
9
+
10
+ module Nand
11
+ class Cli < Thor
12
+ include Logging
13
+
14
+ class_option :run_dir, :desc => "Specify running dir, Default is a Current Directory"
15
+ class_option :debug, :type => :boolean, :default => false, :hide => true, :desc => "This is debug flag, Daemon process stdout/stderr pipes to this terminal"
16
+
17
+ desc "start EXEC_TARGET [OPTIONS] [-- EXEC OPTIONS]", "Start Daemon for EXEC with options"
18
+ option :out, :aliases => '-o', :default => "/dev/null", :desc => "Daemon process STDOUT pipes to output file"
19
+ option :err, :aliases => '-e', :default => "/dev/null", :desc => "Daemon process STDERR pipes to output error file"
20
+ option :in, :aliases => '-i', :default => "/dev/null", :desc => "Daemon process STDIN pipes to input file"
21
+ option :name, :aliases => '-n', :desc => "Spcify Alias Name of Daemon process, default is first argument(EXEC_TARGET)"
22
+ option :plugin, :aliases => '-p', :desc => "Nand Plugin File Name or Gem Package Name"
23
+ option :sec, :aliases => '-s', :type => :numeric, :desc => "Running time limit [seconds] of Daemon process"
24
+ option :demon_out, :default => "/dev/null", :hide => true, :desc => "Daemon Process STDOUT pipes DEAMON_OUT"
25
+ option :demon_err, :default => "/dev/null", :hide => true, :desc => "Daemon Process STDERR pipes DEAMON_ERR"
26
+ option :demon_log, :default => "/dev/null", :desc => "Output Daemon Process Log file"
27
+ option :recovery, :aliases => '-r', :type => :boolean, :desc => "Auto Recovery"
28
+ option :recovery_sec, :type => :numeric, :desc => "Auto Recovery time span[seconds]"
29
+
30
+ def start(target, *argv)
31
+ opts = restore_options(options)
32
+ log_debug! if opts[:debug]
33
+ dir = run_dir(opts)
34
+ dir.mkpath unless dir.exist?
35
+ launcher = find_launcher(target, opts, *argv)
36
+ opts[:launcher] = launcher
37
+ daemon = Daemon.new(dir, launcher.execname, opts)
38
+ print "#{daemon.execname} is Start "
39
+ begin
40
+ daemon.run
41
+ sleep 0
42
+ if daemon.running?
43
+ puts "Success [#{daemon.pid}]"
44
+ end
45
+ rescue => e
46
+ puts "Failed [#{e.message}]"
47
+ log.debug "\t" + e.backtrace.join("\n\t")
48
+ exit -1
49
+ end
50
+ end
51
+
52
+ desc "status [EXEC_TARGET] [OPTIONS]", "Show Running Daemon Process Status w/wo EXEC_TARGET"
53
+ option :all, :aliases => '-a', :type => :boolean, :desc => "All running Daemons Status in this Server"
54
+ def status(target = nil)
55
+ ds = if options[:all]
56
+ Daemon.all_runnings
57
+ else
58
+ Daemon.running_in(run_dir(options), target)
59
+ end
60
+ ds.each do |d|
61
+ puts status_message(d.run_dir, d.execname, d.running?, d.pid, d.user)
62
+ end
63
+ puts status_message(run_dir(options), target, false) if ds.empty? and !target.nil?
64
+ end
65
+
66
+ desc "stop [EXEC_TARGET] [OPTIONS]", "Stop Daemon Process w/wo EXEC_TARGET"
67
+ option :all, :aliases => '-a', :type => :boolean, :desc => "Show All Running Daemon Status in this Server"
68
+ def stop(target = nil)
69
+ dir = run_dir(options)
70
+ log_debug! if options[:debug]
71
+ ds = if options[:all]
72
+ Daemon.all_runnings
73
+ else
74
+ Daemon.running_in(dir, target)
75
+ end
76
+ ds.each do |d|
77
+ pid = d.pid
78
+ if d.running?
79
+ begin
80
+ print "#{d.execname} is "
81
+ d.stop and puts "Stopped [#{pid}]"
82
+ rescue => e
83
+ puts "Failed [#{e.message}]"
84
+ log.debug "\t" + e.backtrace.join("\n\t")
85
+ end
86
+ else
87
+ puts "#{d.execname} is Not Running"
88
+ end
89
+ end
90
+ puts status_message(dir, target, false) if !target.nil? and ds.empty?
91
+ end
92
+
93
+ desc "version", ""
94
+ def version
95
+ puts File.basename($PROGRAM_NAME) + " " + VERSION
96
+ end
97
+
98
+ private
99
+ def find_launcher(target, opts, *argv, &block)
100
+ launcher = Nand::Launcher.find( target, opts, *(argv + Nand.additional_argv) )
101
+ if block_given?
102
+ block.call(launcher)
103
+ else
104
+ launcher
105
+ end
106
+ end
107
+ def run_dir(opts)
108
+ Pathname.new(opts[:run_dir] || Dir.pwd)
109
+ end
110
+ def status_message(dir, target, running, pid = nil, user = nil)
111
+ "#{target} is " + (running ? "Running [#{pid}] by #{user}" : "Not Running") + " in #{dir}"
112
+ end
113
+ no_tasks do
114
+ # デフォルトメソッドを上書きして -h をヘルプ
115
+ def invoke_task(task, *args)
116
+ if options[:help]
117
+ help(task.name)
118
+ elsif options[:version] or args.flatten.first == "-v"
119
+ version
120
+ else
121
+ super
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,127 @@
1
+ # -*-mode: ruby; coding: utf-8 -*-
2
+
3
+ require 'pathname'
4
+ require 'nand/proc_operation'
5
+ require 'nand/logger'
6
+
7
+ module Nand
8
+ class Daemon
9
+ extend ProcOperation
10
+ include Logging
11
+ def logger_output_params ; [@daemon_log] end
12
+ def logger_progname ; @execname end
13
+ def logger_formatter ; TimeFormatter.new end
14
+
15
+ attr_reader :execname, :run_dir
16
+ def initialize(run_dir, execname, opts = {} )
17
+ @run_dir = Pathname.new(run_dir)
18
+ @execname = execname.to_s
19
+ @launcher = opts[:launcher]
20
+ @arg_uid = opts[:uid]
21
+ @pid_file = @run_dir.join(self.class.pid_filename(@execname))
22
+ @daemon_log = opts[:daemon_log] || "/dev/null"
23
+ @daemon_out = opts[:daemon_out] || "/dev/null"
24
+ @daemon_err = opts[:daemon_err] || "/dev/null"
25
+ @recovery = opts[:recovery] || false
26
+ @recovery_sec = opts[:recovery_sec] || 1
27
+ @limit = opts[:sec]
28
+
29
+ log.level = LOG_DEBUG if opts[:debug]
30
+ end
31
+ def user
32
+ Etc.getpwuid(uid).name
33
+ rescue => e
34
+ nil
35
+ end
36
+ def uid; @arg_uid || Process.uid end
37
+ def running?; @pid_file.exist? and self.class.running_with?(pid, @execname) end
38
+
39
+ def run
40
+ raise "Launcher is Not Specified for #{@execname}" if @launcher.nil?
41
+ raise "PID file exist #{@pid_file}" if @pid_file.exist?
42
+ daemonize
43
+ end
44
+ def stop; stop_with_signal(:TERM) end
45
+ def kill; stop_with_signal(:KILL) end
46
+ def pid; @pid ||=self.class.pid_from_pidfile(@pid_file) end
47
+
48
+ private
49
+ def daemonize( &block )
50
+ if child = Process.fork
51
+ Process.waitpid(child) # wait until exit child process
52
+ return child
53
+ end
54
+ Process.setsid
55
+ exit(0) if Process.fork # double fork and exit child process
56
+ Process.setsid
57
+ Dir.chdir(@run_dir)
58
+ File.umask(0)
59
+ STDIN.reopen("/dev/null")
60
+ STDOUT.reopen(@daemon_out)
61
+ STDERR.reopen(@daemon_err)
62
+
63
+ begin
64
+ File.open(@pid_file, "w"){|fs| fs.puts Process.pid }
65
+
66
+ log.info "Daemonize [#{Process.pid}]"
67
+
68
+ Signal.trap(:INT) {Thread.new{log.warn("RECEVIED Signal INT") ; send_signal_and_exit(:INT)}}
69
+ Signal.trap(:TERM) {Thread.new{log.warn("RECEVIED Signal TERM") ; send_signal_and_exit(:TERM)}}
70
+ begin
71
+ sleep 0.1
72
+
73
+ @child = @launcher.launch
74
+ raise "Failed Launch for #{@execname}" if @child.nil?
75
+ log.info "Launched Child Process [#{@child}]"
76
+
77
+ wait_untill_limit if !@limit.nil? and 0 < @limit
78
+
79
+ Process.waitpid2(@child) unless @child.nil?
80
+ log.warn "PID #{@child} down"
81
+ sleep @recovery_sec if @recovery
82
+ end while @recovery
83
+ rescue => e
84
+ log.fatal e.message
85
+ log.debug "\n\t" + e.backtrace.join("\n\t")
86
+ ensure
87
+ terminate
88
+ end
89
+ end
90
+ def terminate( code = 0 )
91
+ unless @child.nil?
92
+ log.info "terminate for #{@child} with exit code #{code}"
93
+ @child = nil
94
+ end
95
+ @pid_file.delete if @pid_file.exist?
96
+ exit code
97
+ end
98
+ def send_signal_and_exit(type)
99
+ Process.kill(type, -@child) unless @child.nil?
100
+ log.warn "Sent Signal #{type} to #{@child}"
101
+ terminate
102
+ rescue => e
103
+ log.fatal "Failed Send Signal to -#{@child}, since #{e.message}"
104
+ terminate -1
105
+ end
106
+ private
107
+ def stop_with_signal(signal)
108
+ raise "Can Not Send #{signal.to_s.capitalize} Signal to #{@execname}" unless uid == Process.uid or uid == 0
109
+ unless running?
110
+ @pid_file.delete if @pid_file.exist?
111
+ raise "#{@execname} is Not Running"
112
+ end
113
+ Process.kill(signal, pid)
114
+ end
115
+ def wait_untill_limit
116
+ Signal.trap(:SIGCHLD) {terminate}
117
+ pid, status = Process.waitpid2(@child, Process::WNOHANG)
118
+ if pid.nil?
119
+ log.info "Child[#{@child}] will be Stopped after #{@limit} sec"
120
+ sleep @limit
121
+ Signal.trap(:SIGCHLD, "IGNORE")
122
+ log.info "Send Signal TERM to Child[#{@child}] #{@limit} sec past"
123
+ Process.kill(:TERM, @child)
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,55 @@
1
+ # -*-mode: ruby; coding: utf-8 -*-
2
+
3
+ require 'pathname'
4
+ require 'stringio'
5
+
6
+ module Nand
7
+ module Launcher
8
+ def self.find(target, opts = {}, *argv)
9
+ require 'nand/launcher/executable_file_launcher'
10
+ require 'nand/launcher/shell_launcher'
11
+ require 'nand/launcher/rb_file_launcher'
12
+ require 'nand/launcher/plugin_launcher'
13
+
14
+ err = StringIO.new("", "w")
15
+ launcher_klass = [ExecutableFileLauncher, RbFileLauncher, PluginLauncher, ShellLauncher].find do |klass|
16
+ klass.launchable? target, err, opts
17
+ end
18
+ raise "Not Found Executable #{target}:\n#{io.string}" if launcher_klass.nil?
19
+ launcher = launcher_klass.load(target, opts, *argv)
20
+ raise "Not be Ready for #{target} Launcher" unless launcher.ready?
21
+ launcher
22
+ end
23
+
24
+ class Base
25
+ def self.launchable?(target, io, *argv)
26
+ raise "Not Implemented #{__method__} in #{self.name}"
27
+ end
28
+ def self.load( target, opts, *argv)
29
+ raise "Not Implemented #{__method__} in #{self.name}"
30
+ end
31
+ attr_reader :execname
32
+ def initialize(target, opts, *argv)
33
+ @progname = target
34
+ @execname = opts[:name] || File.basename(target)
35
+ @exec_stdout = opts[:out] || "/dev/null"
36
+ @exec_stderr = opts[:err] || "/dev/null"
37
+ @exec_stdin = opts[:in] || "/dev/null"
38
+ @argv = argv
39
+ end
40
+ def launch; end
41
+ def ready?
42
+ [@exec_stdout, @exec_stderr].each do |out|
43
+ next if out.is_a? IO
44
+ path = Pathname.new(out)
45
+ raise "Illegal Output File #{path.to_s}" unless (path.exist? and path.writable?) or
46
+ (!path.exist? and path.dirname.writable?)
47
+ end
48
+ return true if @exec_stdin.is_a? IO
49
+ path = Pathname.new(@exec_stdin)
50
+ raise "Illegal Input File #{@exec_stdin.to_s}" unless path.exist? and path.readable?
51
+ true
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,35 @@
1
+ # -*-mode: ruby; coding: utf-8 -*-
2
+
3
+ require 'nand/launcher/shell_launcher'
4
+
5
+ module Nand
6
+ module Launcher
7
+ class ExecutableFileLauncher < ShellLauncher
8
+ def self.launchable?(target, io, opts)
9
+ file = exec_file(target)
10
+ raise "Not Found #{file.to_s}" unless file.exist?
11
+ raise "Not Executable #{file.to_s}" unless file.executable?
12
+ true
13
+ rescue => e
14
+ io.puts "\t- " + e.message
15
+ false
16
+ end
17
+ def self.load(target, opts = {}, *argv)
18
+ file = exec_file(target)
19
+ new(target, opts, *argv)
20
+ end
21
+ def self.exec_file(target)
22
+ Pathname.new(target).expand_path
23
+ end
24
+ attr_reader :name
25
+ def initialize(target, opts, *argv)
26
+ super(target, opts, *argv)
27
+ @file = self.class.exec_file(target)
28
+ @name = opts[:name] || @file.basename.to_s
29
+ end
30
+ def cmd; "#{@file} #{@argv.join(" ")}" end
31
+ end
32
+ end
33
+ end
34
+
35
+
@@ -0,0 +1,29 @@
1
+ # -*-mode: ruby; coding: utf-8 -*-
2
+
3
+ require 'nand/plugin'
4
+ require 'nand/launcher/rb_file_launcher'
5
+
6
+ module Nand
7
+ module Launcher
8
+ class PluginLauncher < RbFileLauncher
9
+ def self.require_rb(target)
10
+ require "#{target}/nand/plugin"
11
+ end
12
+ def self.launchable?( target, io, opts )
13
+ specs = Gem::Specification.find_all do |s|
14
+ s.name =~/^(nand-)*#{target}$/ and s.dependencies.find{ |d| d.name == "nand" }
15
+ end
16
+ raise "Not Found #{target} in Installed gems" if specs.empty?
17
+ raise "Target name #{target} is Not Uniq for installed gem" if 1 < specs.size
18
+ require_rb(specs.first.name)
19
+ true
20
+ rescue LoadError => e
21
+ io.puts "\t- " + e.message
22
+ false
23
+ rescue => e
24
+ io.puts "\t- " + e.message
25
+ false
26
+ end
27
+ end
28
+ end
29
+ end