tsm-command 1.2

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,33 @@
1
+ == Description
2
+ A wrapper for TSM dsmadmc and dsmc commands
3
+
4
+ == Prerequisites
5
+ * Ruby 1.8.4 or later
6
+
7
+ == Usage
8
+ t = TSM::Dsmadmc(id,pass)
9
+ t.open { |d|
10
+ d.command('q pr')
11
+ d.command('select count(*) from volumes')
12
+ }
13
+
14
+ TSM::Dsmadmc.command(id,pass,'q pr',{:se => :testserver})
15
+
16
+
17
+ class TestObserver
18
+ def update(msg)
19
+ p msg
20
+ end
21
+ end
22
+
23
+ TSM::Dsmadmc(id,pass) { |t|
24
+ t.add_observer(TestObserver.new())
25
+ t.command('q pr')
26
+ }
27
+
28
+ == Installation
29
+ === Manual Installation
30
+ === Gem Installation
31
+ gem install tsm-<version>.gem
32
+
33
+ == Notes
@@ -0,0 +1,5 @@
1
+ require 'tsm/error'
2
+ require 'tsm/dsmio'
3
+ require 'tsm/response'
4
+ require 'tsm/dsmadmc'
5
+ require 'tsm/dsm'
@@ -0,0 +1,69 @@
1
+ module TSM
2
+ class Dsm
3
+ def self.opt(filename='dsm.opt')
4
+ sys = {}
5
+ self.new(filename) { |line|
6
+ if (m = /^(\S+)(\s+)(\S+)/i.match(line))
7
+ sys[m[1].downcase.to_sym] = m[3]
8
+ end
9
+ }
10
+ sys
11
+ end
12
+
13
+ def self.sys(filename='dsm.sys')
14
+ sys = {} and hsh = {} and name = nil
15
+ self.new(filename) { |line|
16
+ if (m = /^se([a-zA-Z]*)(\s+)(\S+)/i.match(line))
17
+ sys[name] = hsh.dup if name
18
+ hsh.clear
19
+ name = m[3].downcase.to_sym
20
+ next
21
+ end
22
+
23
+ if (m = /^(\S+)(\s+)(\S+)/i.match(line))
24
+ k = m[1].downcase
25
+ if k[0..3] == 'incl'
26
+ h = {}
27
+ self.new(m[3]) { |i|
28
+ if (n = /^(\S+)(\s+)(\S+)/.match(i))
29
+ k2 = n[1].downcase
30
+ h[k2] = [] unless h.has_key?(k2)
31
+ h[k2].push(n[3])
32
+ end
33
+ }
34
+ hsh[k.to_sym] = h.dup
35
+ else
36
+ hsh[k.to_sym] = m[3]
37
+ end
38
+ end
39
+ }
40
+ sys[name] = hsh.dup if name
41
+ sys
42
+ end
43
+
44
+ def initialize(filename,&block)
45
+ process(filename) { |line|
46
+ line = line.chomp
47
+ line.strip!
48
+
49
+ next if line[0..0] == '*' or line == ''
50
+
51
+ yield line
52
+ }
53
+ end
54
+
55
+ private
56
+
57
+ def process(name,&block)
58
+ f = File.readable?(name) ? name : nil
59
+ path = ['/opt/tivoli/tsm/client/ba/bin','/usr/tivoli/tsm/client/ba/bin']
60
+ (ENV['PATH'].split(File::PATH_SEPARATOR) + path).each { |p|
61
+ if File.readable?(x=File.join(p,name))
62
+ f = x
63
+ end
64
+ }
65
+
66
+ IO.foreach(f) { |l| yield l } if f
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,207 @@
1
+ require 'pty'
2
+ require 'observer'
3
+ require 'parsedate'
4
+
5
+ module TSM
6
+ class Dsmadmc
7
+ include Observable
8
+
9
+ FORCE_OPT = {:displ => 'list',:out => true,:noc => true}
10
+ FAKE_QUIT = {:command=>"quit", :messages=>[], :rows=>[], :count=>0}
11
+
12
+ @@path = ['/opt/tivoli/tsm/client/ba/bin','/usr/tivoli/tsm/client/ba/bin']
13
+ @@quit = true
14
+
15
+ attr_accessor :timeout
16
+ attr_reader :host, :header, :prompt
17
+ attr_reader :pty, :cmd
18
+
19
+ def self.command(id,password,command,opts = {})
20
+ self.new(id,password,opts).command(command)
21
+ end
22
+
23
+ def self.quit?
24
+ @@quit
25
+ end
26
+
27
+ def self.quit=(bool)
28
+ @@quit = bool ? true : false
29
+ end
30
+
31
+ def initialize(id,password,opt = {},&block)
32
+ @timeout = 60
33
+
34
+ opt[:id] = id
35
+ opt[:pa] = password
36
+
37
+ args = [find(self.class.to_s.split(":").last.downcase)]
38
+
39
+ opt.merge(FORCE_OPT).each{|k,v| args.push("-#{k.to_s}#{'=' << v.to_s if v.class != TrueClass}") }
40
+ opt.each_key do |k|
41
+ opt.each_key {|k2| opt.delete(k2) if k.to_s.casecmp(k2.to_s) == -1}
42
+ end
43
+
44
+ @cmd = args.join(" ")
45
+
46
+ return self unless block_given?
47
+
48
+ open(&block)
49
+ ensure
50
+ close if block_given? and not closed?
51
+ end
52
+
53
+ def open(&block)
54
+ ptyval = nil and success = false
55
+ begin
56
+ PTY.spawn(@cmd) { |r,w,pid|
57
+ @header,@prompt,@host = r._dsm_prompt([/tsm: ([A-Z0-9]*)>/i,/Enter your password: /i],@timeout)
58
+ parse(TSM::Response.new,@header.to_s).validate(TSM::Error::CommandError) if @host.to_s.empty?
59
+ raise TSM::Error::FatalError if @host.to_s.empty?
60
+
61
+ @established = Time.now()
62
+ @pty = {:r => r,:w => w,:pid => pid}
63
+
64
+ notify('open',{'host'=>@host})
65
+
66
+ ptyval = yield self
67
+ success = true
68
+ }
69
+ rescue PTY::ChildExited
70
+ end
71
+
72
+ raise TSM::Error::CommandError, 'Error establishing connection' if ptyval == nil and not success
73
+
74
+ ptyval
75
+ ensure
76
+ close if not closed?
77
+ end
78
+
79
+ def close
80
+ notify('close',{'closed' => closed?})
81
+ if @pty
82
+ @pty[:r].close unless @pty[:r].closed?
83
+ @pty[:w].close unless @pty[:w].closed?
84
+ end
85
+ @pty = nil
86
+ end
87
+
88
+ def closed?
89
+ return true unless @pty
90
+ return true if @pty[:r].closed? or @pty[:w].closed?
91
+
92
+ begin
93
+ Process.kill(0,@pty[:pid])
94
+ rescue Errno::ESRCH
95
+ return true
96
+ end
97
+
98
+ false
99
+ end
100
+
101
+ def command(cmd,&block)
102
+
103
+ return self.open { |t| t.command(cmd,&block) } if closed?
104
+
105
+ cmd.to_s.strip!
106
+
107
+ notify('execute',{'command' => cmd,'closed'=>closed?})
108
+
109
+ start = Time.now()
110
+
111
+ res = nil
112
+ if cmd =~ /quit/ and not @@quit
113
+ res = TSM::Response.new(FAKE_QUIT)
114
+ else
115
+ res = TSM::Response.new
116
+ @pty[:w].puts(cmd.to_s)
117
+ @pty[:r]._dsm_response(@prompt) do |line| parse(res,line,&block) end
118
+ end
119
+
120
+ duration = Time.now() - start
121
+
122
+ notify('complete',{'duration' => "%.2f" % duration,'rows' => res[:count] || 0})
123
+
124
+ res
125
+ ensure
126
+ close if cmd =~ /quit/ and @@quit
127
+ end
128
+
129
+ def server
130
+ raise TSM::Error::PTYError, "No connection was established" unless @header
131
+
132
+ svr = {}
133
+ @header.each {|l|
134
+ l = l.chomp
135
+ l.strip!
136
+ next if l.empty?
137
+
138
+ if (m=/Command Line ([\S^-]+)([\s\S]+) - Version (\d+), Release (\d+), Level (\d(.\d))/i.match(l))
139
+ svr[:interface] = {:version => m[3],:release => m[4],:level =>m[5],:type => m[1].downcase}
140
+ elsif (m=/Session established with server (\w+): (\S+)/i.match(l))
141
+ svr[:host] = {:hostname => m[1],:arch => m[2]}
142
+ elsif (m=/Server Version (\d+), Release (\d+), Level (\d(.\d))/i.match(l))
143
+ svr[:version] = {:version => m[1],:release => m[2],:level => m[3]}
144
+ elsif (m=/Server date.time: ([0-9\/]+) ([0-9:]+)(\s+)Last access: ([0-9\/]+) ([0-9:]+)/i.match(l))
145
+ svr[:established] = Time.local(*(ParseDate.parsedate("#{m[1]} #{m[2]}")))
146
+ svr[:access] = Time.local(*(ParseDate.parsedate("#{m[4]} #{m[5]}")))
147
+ end
148
+ }
149
+ svr[:time] = @established if @established
150
+
151
+ svr
152
+ end
153
+
154
+ def self.path=(path)
155
+ @@path = [path].flatten
156
+ end
157
+ def self.path
158
+ @@path
159
+ end
160
+
161
+ protected
162
+
163
+ def notify(st,opts = {})
164
+ pid = @pty[:pid] if @pty
165
+ changed and notify_observers(opts.merge({'status' => st,'time' => Time.now,'pid' => pid || 0}))
166
+ end
167
+
168
+ def find(name,path=@@path)
169
+ path ||= ['/usr/local/bin']
170
+ (ENV['PATH'].split(File::PATH_SEPARATOR) + path).each { |p|
171
+ if File.executable?(f=File.join(p,name))
172
+ return f
173
+ end
174
+ }
175
+ end
176
+
177
+ def parse(res,chrs,&block)
178
+ chrs.each { |line|
179
+ line = line.chomp
180
+ line.strip!
181
+
182
+ if (m = res.message?(line))
183
+ res.messages(m[1],m[5]) and next
184
+ end
185
+
186
+ res[:command] = line and next unless res[:command]
187
+
188
+ if line.empty? and not @tmp.empty?
189
+ res.rows(@tmp)
190
+
191
+ yield @tmp if block_given?
192
+
193
+ @tmp.clear
194
+
195
+ next
196
+ end if @tmp
197
+
198
+ if (sp = line.index(":")) != nil
199
+ @tmp = {} unless @tmp
200
+ @tmp[line[0...sp].downcase.tr(" ","_").to_sym] = line[(sp+1)..-1].lstrip
201
+ end
202
+ }
203
+
204
+ res
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,49 @@
1
+
2
+ class IO
3
+ def _dsm_prompt(pat,timeout=9999999)
4
+ buf = ''
5
+ pat = [pat] unless pat.respond_to?('each')
6
+ result = nil
7
+ while true
8
+ break if IO.select([self],nil,nil,timeout).nil?
9
+ begin
10
+ buf << getc.chr
11
+ rescue Errno::EIO
12
+ result = [buf,'']
13
+ end
14
+
15
+ pat.each { |p|
16
+ if mat=p.match(buf)
17
+ result = [buf,*mat.to_a] and break
18
+ end
19
+ }
20
+ break if result
21
+ end
22
+ result
23
+ end
24
+
25
+ def _dsm_response(str,timeout=9999999,&block)
26
+ tbuf = '' and buf = ''
27
+ mode = :chr
28
+ while true
29
+ result = nil and break if IO.select([self],nil,nil,timeout).nil?
30
+ if mode == :chr
31
+ begin
32
+ tbuf << getc.chr
33
+ rescue Errno::EIO
34
+ result = result.to_s
35
+ result << tbuf and break
36
+ end
37
+ buf << tbuf and tbuf = '' and mode = :line if str[0...tbuf.size] != tbuf
38
+ buf << tbuf and result = buf and break if tbuf == str
39
+ buf << tbuf if tbuf[-($/.size)..-1] == $/
40
+ next
41
+ end
42
+
43
+ buf << gets and mode = :chr
44
+ yield buf
45
+ buf = ''
46
+ end
47
+ result
48
+ end
49
+ end
@@ -0,0 +1,8 @@
1
+ module TSM
2
+ class Error < RuntimeError
3
+ class FatalError < Error; end
4
+ class CommandError < Error; end
5
+ class PTYError < Error; end
6
+ class NoData < Error; end
7
+ end
8
+ end
@@ -0,0 +1,45 @@
1
+ module TSM
2
+ class Response < Hash
3
+ def initialize(hsh = {})
4
+ {:messages => [],:rows => [],:count => 0,:command => nil}.each do |k,v|
5
+ if hsh.has_key?(k)
6
+ self.store(k,hsh[k])
7
+ else
8
+ self.store(k,v)
9
+ end
10
+ end
11
+
12
+ self
13
+ end
14
+
15
+ def message?(str)
16
+ if (m = /(([ABDEI][DEKNC][ERSDNOUIP])(\d+)([IESWK])) (.*)/.match(str))
17
+ return m
18
+ end
19
+
20
+ false
21
+ end
22
+
23
+ def messages(id,msg)
24
+ self[:messages].push({:id=>id,:msg=>msg})
25
+ end
26
+
27
+ def rows(val)
28
+ self[:rows].push(val.dup)
29
+ self[:count] += 1
30
+ end
31
+
32
+ def validate(err=TSM::Error::FatalError)
33
+ self[:messages].each do |msg|
34
+ raise TSM::Error::NoData, msg[:msg] if msg[:id] =~ /ANR2034E/i
35
+ raise err, msg[:msg] if msg[:id][-1..-1] =~ /^[ES]$/i
36
+ end
37
+
38
+ true
39
+ end
40
+
41
+ def to_hash
42
+ Hash.new().replace(self)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,24 @@
1
+ require 'tsm'
2
+
3
+ require 'yaml'
4
+ require 'test/unit'
5
+
6
+ USER = YAML.load(File.open(File.expand_path("~/auth.yaml")))
7
+
8
+ class TestTsm < Test::Unit::TestCase
9
+
10
+ def test_simple
11
+ assert_raise(TSM::Error::CommandError) {
12
+ TSM::Dsmadmc.new(USER['user'],'pass') { |t| }
13
+ }
14
+
15
+ res = TSM::Dsmadmc.new(USER['user'],USER['pass']) { |t|
16
+ assert_kind_of(Hash,t.server)
17
+ assert_kind_of(TSM::Response,t.command('q pr'))
18
+ }
19
+
20
+ assert_kind_of(TSM::Dsmadmc,res)
21
+
22
+ assert_kind_of(TSM::Response,TSM::Dsmadmc.command(USER['user'],USER['pass'],'q pr'))
23
+ end
24
+ end
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.0
3
+ specification_version: 1
4
+ name: tsm-command
5
+ version: !ruby/object:Gem::Version
6
+ version: "1.2"
7
+ date: 2006-11-07 00:00:00 -05:00
8
+ summary: Wrapper for TSM client and administrative command interfaces
9
+ require_paths:
10
+ - lib
11
+ email: s.e.collison@gmail.com
12
+ homepage: http://tsm-command.rubyforge.org/
13
+ rubyforge_project:
14
+ description:
15
+ autorequire: tsm
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: "true"
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Sarah Collison
31
+ files:
32
+ - tests/ts_tsm.rb
33
+ - lib/tsm
34
+ - lib/tsm.rb
35
+ - lib/tsm/response.rb
36
+ - lib/tsm/dsm.rb
37
+ - lib/tsm/dsmadmc.rb
38
+ - lib/tsm/error.rb
39
+ - lib/tsm/dsmio.rb
40
+ - README
41
+ test_files:
42
+ - tests/ts_tsm.rb
43
+ rdoc_options: []
44
+
45
+ extra_rdoc_files:
46
+ - README
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ requirements: []
52
+
53
+ dependencies: []
54
+