tsm 0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/README +26 -0
- data/lib/tsm.rb +283 -0
- data/tests/ts_tsm.rb +21 -0
- metadata +48 -0
data/README
ADDED
@@ -0,0 +1,26 @@
|
|
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
|
+
TSM::Dsmadmc(id,pass) { |t|
|
9
|
+
t.add_observer(TestObserver.new())
|
10
|
+
t.command('q pr')
|
11
|
+
}
|
12
|
+
|
13
|
+
t = TSM::Dsmadmc(id,pass)
|
14
|
+
t.open
|
15
|
+
t.command('q pr')
|
16
|
+
t.command('quit')
|
17
|
+
t.open
|
18
|
+
t.command('select count(*) from volumes')
|
19
|
+
t.close
|
20
|
+
|
21
|
+
== Installation
|
22
|
+
=== Manual Installation
|
23
|
+
=== Gem Installation
|
24
|
+
gem install tsm-<version>.gem
|
25
|
+
|
26
|
+
== Notes
|
data/lib/tsm.rb
ADDED
@@ -0,0 +1,283 @@
|
|
1
|
+
|
2
|
+
require 'pty'
|
3
|
+
require 'observer'
|
4
|
+
require 'parsedate'
|
5
|
+
|
6
|
+
module TSM
|
7
|
+
class Error < RuntimeError
|
8
|
+
class FatalError < Error; end
|
9
|
+
class CommandError < Error; end
|
10
|
+
class PTYError < Error; end
|
11
|
+
|
12
|
+
class SqlError < Error
|
13
|
+
class NoData < SqlError; end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
class Command; end #:nodoc:
|
17
|
+
|
18
|
+
FORCE_OPT = {:displ => 'list',:out => true,:noc => true}
|
19
|
+
|
20
|
+
class Dsmadmc < Command
|
21
|
+
def initialize(id,password,opts = {},&block)
|
22
|
+
opts[:id] = id
|
23
|
+
opts[:pa] = password
|
24
|
+
|
25
|
+
@path = ['/opt/tivoli/tsm/client/ba/bin','/usr/tivoli/tsm/client/ba/bin']
|
26
|
+
|
27
|
+
super opts,&block
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Dsmc < Command
|
32
|
+
def initialize(opts = {},&block)
|
33
|
+
@path = ['/opt/tivoli/tsm/client/admin/bin','/usr/tivoli/tsm/client/ba/bin']
|
34
|
+
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Command
|
40
|
+
include Observable
|
41
|
+
|
42
|
+
attr_accessor :timeout
|
43
|
+
attr_reader :host, :header, :prompt
|
44
|
+
attr_reader :pty, :cmd
|
45
|
+
attr_accessor :path, :app
|
46
|
+
|
47
|
+
def initialize(opt = {},&block)
|
48
|
+
args = [find(self.class.to_s.split(":").last.downcase)]
|
49
|
+
|
50
|
+
opt.merge(FORCE_OPT).each{|k,v| args.push("-#{k.to_s}#{'=' << v.to_s if v.class != TrueClass}") }
|
51
|
+
opt.each_key do |k|
|
52
|
+
opt.each_key {|k2| opt.delete(k2) if k.to_s.casecmp(k2.to_s) == -1}
|
53
|
+
end
|
54
|
+
|
55
|
+
@cmd = args.join(" ")
|
56
|
+
|
57
|
+
yield self if block_given?
|
58
|
+
|
59
|
+
self
|
60
|
+
ensure
|
61
|
+
close if block_given? and not closed?
|
62
|
+
end
|
63
|
+
|
64
|
+
def open(&block)
|
65
|
+
return self unless closed?
|
66
|
+
|
67
|
+
r,w,pid = PTY.spawn(@cmd)
|
68
|
+
@header,@prompt,@host = r.prompt([/tsm: ([A-Z0-9]*)>/i,/Enter your password: /i],@timeout || 60)
|
69
|
+
Response.new(@header.to_s).validate(Error::CommandError) if @host.to_s.empty?
|
70
|
+
|
71
|
+
@established = Time.now()
|
72
|
+
@pty = {:r => r,:w => w,:pid => pid}
|
73
|
+
|
74
|
+
notify('open',{'host'=>@host})
|
75
|
+
|
76
|
+
yield self if block_given?
|
77
|
+
|
78
|
+
self
|
79
|
+
rescue PTY::ChildExited => details
|
80
|
+
ensure
|
81
|
+
close if block_given? and not closed?
|
82
|
+
end
|
83
|
+
|
84
|
+
def close
|
85
|
+
notify('close',{'closed' => closed?})
|
86
|
+
if @pty
|
87
|
+
@pty[:r].close unless @pty[:r].closed?
|
88
|
+
@pty[:w].close unless @pty[:w].closed?
|
89
|
+
end
|
90
|
+
@pty = nil
|
91
|
+
end
|
92
|
+
|
93
|
+
def closed?
|
94
|
+
return true unless @pty
|
95
|
+
return true if @pty[:r].closed? and @pty[:w].closed?
|
96
|
+
|
97
|
+
begin
|
98
|
+
Process.kill(0,@pty[:pid])
|
99
|
+
rescue Errno::ESRCH
|
100
|
+
return true
|
101
|
+
end
|
102
|
+
|
103
|
+
false
|
104
|
+
end
|
105
|
+
|
106
|
+
def command(cmd,&block)
|
107
|
+
cmd.to_s.strip!
|
108
|
+
|
109
|
+
notify('execute',{'command' => cmd,'closed'=>closed?})
|
110
|
+
|
111
|
+
begin
|
112
|
+
return self
|
113
|
+
ensure
|
114
|
+
close unless closed?
|
115
|
+
end if cmd =~ /quit*/i
|
116
|
+
|
117
|
+
raise Error::PTYError, 'No PTY' if closed?
|
118
|
+
|
119
|
+
@pty[:w].puts(cmd.to_s)
|
120
|
+
|
121
|
+
start = Time.now()
|
122
|
+
res = Response.new
|
123
|
+
@pty[:r].response(@prompt) do |line|
|
124
|
+
res.parse(line,&block)
|
125
|
+
end
|
126
|
+
duration = Time.now() - start
|
127
|
+
|
128
|
+
notify('complete',{'duration' => "%.2f" % duration,'rows' => res[:count] || 0})
|
129
|
+
|
130
|
+
res
|
131
|
+
rescue Error::PTYError
|
132
|
+
open
|
133
|
+
retry
|
134
|
+
end
|
135
|
+
|
136
|
+
def server
|
137
|
+
raise Error::PTYError, "No connection was established" unless @header
|
138
|
+
|
139
|
+
svr = {}
|
140
|
+
@header.each {|l|
|
141
|
+
l = l.chomp
|
142
|
+
l.strip!
|
143
|
+
next if l.empty?
|
144
|
+
|
145
|
+
if (m=/Command Line ([\S^-]+)([\s\S]+) - Version (\d+), Release (\d+), Level (\d(.\d))/i.match(l))
|
146
|
+
svr[:interface] = {:version => m[3],:release => m[4],:level =>m[5],:type => m[1].downcase}
|
147
|
+
elsif (m=/Session established with server (\w+): (\S+)/i.match(l))
|
148
|
+
svr[:host] = {:hostname => m[1],:arch => m[2]}
|
149
|
+
elsif (m=/Server Version (\d+), Release (\d+), Level (\d(.\d))/i.match(l))
|
150
|
+
svr[:version] = {:version => m[1],:release => m[2],:level => m[3]}
|
151
|
+
elsif (m=/Server date.time: ([0-9\/]+) ([0-9:]+)(\s+)Last access: ([0-9\/]+) ([0-9:]+)/i.match(l))
|
152
|
+
svr[:established] = Time.local(*(ParseDate.parsedate("#{m[1]} #{m[2]}")))
|
153
|
+
svr[:access] = Time.local(*(ParseDate.parsedate("#{m[4]} #{m[5]}")))
|
154
|
+
end
|
155
|
+
}
|
156
|
+
svr[:time] = @established if @established
|
157
|
+
|
158
|
+
svr
|
159
|
+
end
|
160
|
+
|
161
|
+
def path
|
162
|
+
@path || ['/usr/local/bin']
|
163
|
+
end
|
164
|
+
|
165
|
+
def path=(path)
|
166
|
+
@path = [path].flatten
|
167
|
+
end
|
168
|
+
|
169
|
+
protected
|
170
|
+
|
171
|
+
def notify(st,opts = {})
|
172
|
+
pid = @pty[:pid] if @pty
|
173
|
+
changed and notify_observers(opts.merge({'status' => st,'time' => Time.now,'pid' => pid || 0}))
|
174
|
+
end
|
175
|
+
|
176
|
+
def find(name)
|
177
|
+
(ENV['PATH'].split(File::PATH_SEPARATOR) + self.path).each { |p|
|
178
|
+
if File.executable?(f=File.join(p,name))
|
179
|
+
return f
|
180
|
+
end
|
181
|
+
}
|
182
|
+
end
|
183
|
+
|
184
|
+
class Response < Hash
|
185
|
+
def initialize(str=nil)
|
186
|
+
{:messages => [],:rows => [],:count => 0,:command => nil}.each do |k,v|
|
187
|
+
self.store(k,v)
|
188
|
+
end
|
189
|
+
|
190
|
+
self.parse(str) if str
|
191
|
+
|
192
|
+
self
|
193
|
+
end
|
194
|
+
|
195
|
+
def parse(chrs,&block)
|
196
|
+
chrs.each { |line|
|
197
|
+
line = line.chomp
|
198
|
+
line.strip!
|
199
|
+
|
200
|
+
if (m = /(([ABDEI][DEKNC][ERSDNOUIP])(\d+)([IESWK])) (.*)/.match(line))
|
201
|
+
self[:messages].push({:id=>m[1],:msg=>m[5]}) and next
|
202
|
+
end
|
203
|
+
|
204
|
+
self[:command] = line and next unless self[:command]
|
205
|
+
|
206
|
+
if line.empty? and not @tmp.empty?
|
207
|
+
self[:count] += 1
|
208
|
+
|
209
|
+
yield @tmp if block_given?
|
210
|
+
|
211
|
+
self[:rows].to_a.push(@tmp.dup)
|
212
|
+
@tmp.clear
|
213
|
+
|
214
|
+
next
|
215
|
+
end if @tmp
|
216
|
+
|
217
|
+
if (sp = line.index(":")) != nil
|
218
|
+
@tmp = {} unless @tmp
|
219
|
+
@tmp[line[0...sp].downcase.tr(" ","_").to_sym] = line[(sp+1)..-1].lstrip
|
220
|
+
end
|
221
|
+
}
|
222
|
+
|
223
|
+
chrs
|
224
|
+
end
|
225
|
+
|
226
|
+
def validate(err=Error::FatalError)
|
227
|
+
self[:messages].each do |msg|
|
228
|
+
raise Error::SqlError::NoData, msg[:msg] if msg[:id] =~ /ANR2034E/i
|
229
|
+
raise err, msg[:msg] if msg[:id][-1..-1] =~ /^[ES]$/i
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
class IO
|
237
|
+
def prompt(pat,timeout=9999999)
|
238
|
+
buf = ''
|
239
|
+
pat = [pat] unless pat.respond_to?('each')
|
240
|
+
result = nil
|
241
|
+
while true
|
242
|
+
return result if IO.select([self],nil,nil,timeout).nil? or eof?
|
243
|
+
begin
|
244
|
+
buf << getc.chr
|
245
|
+
rescue Errno::EIO
|
246
|
+
result = [buf,''] and break
|
247
|
+
end
|
248
|
+
|
249
|
+
pat.each { |p|
|
250
|
+
if mat=p.match(buf)
|
251
|
+
result = [buf,*mat.to_a] and break
|
252
|
+
end
|
253
|
+
}
|
254
|
+
break if result
|
255
|
+
end
|
256
|
+
result
|
257
|
+
end
|
258
|
+
|
259
|
+
def response(str,timeout=9999999,&block)
|
260
|
+
tbuf = '' and buf = ''
|
261
|
+
mode = :chr
|
262
|
+
while true
|
263
|
+
result = nil and break if IO.select([self],nil,nil,timeout).nil? or eof?
|
264
|
+
if mode == :chr
|
265
|
+
begin
|
266
|
+
tbuf << getc.chr
|
267
|
+
rescue Errno::EIO
|
268
|
+
result = result.to_s
|
269
|
+
result << tbuf and break
|
270
|
+
end
|
271
|
+
buf << tbuf and tbuf = '' and mode = :line if str[0...tbuf.size] != tbuf
|
272
|
+
buf << tbuf and result = buf and break if tbuf == str
|
273
|
+
buf << tbuf if tbuf[-($/.size)..-1] == $/
|
274
|
+
next
|
275
|
+
end
|
276
|
+
|
277
|
+
buf << gets and mode = :chr
|
278
|
+
yield buf
|
279
|
+
buf = ''
|
280
|
+
end
|
281
|
+
result
|
282
|
+
end
|
283
|
+
end
|
data/tests/ts_tsm.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'tsm'
|
2
|
+
require 'yaml'
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
USER = YAML.load(File.open(File.expand_path("~/auth.yaml")))
|
6
|
+
|
7
|
+
class TestTsm < Test::Unit::TestCase
|
8
|
+
|
9
|
+
def test_simple
|
10
|
+
assert_raise(TSM::Error::CommandError) {
|
11
|
+
TSM::Dsmadmc.new(USER[:user],'pass') { |t|
|
12
|
+
t.open
|
13
|
+
}
|
14
|
+
}
|
15
|
+
assert_raise(TSM::Error::PTYError) {
|
16
|
+
TSM::Dsmadmc.new(USER[:user],USER[:pass]) { |t|
|
17
|
+
t.server
|
18
|
+
}
|
19
|
+
}
|
20
|
+
end
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.0
|
3
|
+
specification_version: 1
|
4
|
+
name: tsm
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: "0.9"
|
7
|
+
date: 2006-07-30 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.rb
|
34
|
+
- README
|
35
|
+
test_files:
|
36
|
+
- tests/ts_tsm.rb
|
37
|
+
rdoc_options: []
|
38
|
+
|
39
|
+
extra_rdoc_files:
|
40
|
+
- README
|
41
|
+
executables: []
|
42
|
+
|
43
|
+
extensions: []
|
44
|
+
|
45
|
+
requirements: []
|
46
|
+
|
47
|
+
dependencies: []
|
48
|
+
|