net-ssh-simple 1.3.2 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -57,21 +57,7 @@ Net::SSH::Simple is a simple wrapper around Net::SSH and Net::SCP.
57
57
  s.scp_dl 'example3.com', '/tmp/remote_foo', '/tmp/local_bar'
58
58
  s.close
59
59
 
60
- === Handling a Timeout
61
60
 
62
- require 'net/ssh/simple'
63
-
64
- # Hint: Set timeout=0 to disable, default is 60
65
- begin
66
- Net::SSH::Simple.sync({:timeout => 5}) do
67
- ssh('example1.com', 'sleep 1') # I will (probably) succeed!
68
- ssh('example2.com', 'sleep 60') # I will fail :(
69
- end
70
- rescue Net::SSH::Simple::Error => e
71
- puts e.result.timed_out #=> true
72
- end
73
-
74
-
75
61
  == Documentation
76
62
 
77
63
  See {Net::SSH::Simple}[http://rubydoc.info/gems/net-ssh-simple/Net/SSH/Simple] for more examples and full API.
@@ -157,20 +157,6 @@ module Net
157
157
  # end
158
158
  #
159
159
  # @example
160
- # # Timeout handling
161
- # #
162
- # # Note: The timeout applies to each command independently.
163
- # # Hint: Set timeout=0 to disable, default is 60
164
- # begin
165
- # Net::SSH::Simple.sync({:timeout => 5}) do
166
- # ssh('example1.com', 'sleep 1') # I will (probably) succeed!
167
- # ssh('example2.com', 'sleep 60') # I will fail :(
168
- # end
169
- # rescue Net::SSH::Simple::Error => e
170
- # puts e.result.timed_out #=> true
171
- # end
172
- #
173
- # @example
174
160
  # # Using the SCP progress callback
175
161
  # Net::SSH::Simple.sync do
176
162
  # scp_ul 'example1.com', '/tmp/local_foo', '/tmp/remote_bar' do |sent, total|
@@ -459,7 +445,17 @@ module Net
459
445
  # the max number of packets to process before rekeying
460
446
  #
461
447
  # @option opts [Integer] :timeout (60)
462
- # connection timeout. this is enforced by Net::SSH::Simple.
448
+ # maximum idle time before a connection will time out (0 = disable).
449
+ #
450
+ # @option opts [Integer] :operation_timeout (3600)
451
+ # maximum time before aborting an operation (0 = disable).
452
+ # you may use this to guard against run-away processes.
453
+ #
454
+ # @option opts [Integer] :close_timeout (5)
455
+ # grace-period on close before the connection will be terminated forcefully
456
+ # (0 = terminate immediately).
457
+ #
458
+ # @option opts [String] :user
463
459
  #
464
460
  # @option opts [String] :user
465
461
  # the username to log in as
@@ -482,26 +478,31 @@ module Net
482
478
  with_session(host, opts) do |session|
483
479
  @result = Result.new(
484
480
  { :op => :ssh, :host => host, :cmd => cmd, :start_at => Time.new,
485
- :opts => opts, :stdout => '', :stderr => '', :success => nil
481
+ :last_event_at => Time.new, :opts => opts, :stdout => '', :stderr => '',
482
+ :success => nil
486
483
  } )
487
484
 
488
485
  channel = session.open_channel do |chan|
489
486
  chan.exec cmd do |ch, success|
490
487
  @result[:success] = success
491
488
  ch.on_data do |c, data|
489
+ @result[:last_event_at] = Time.new
492
490
  r = block.call(:stdout, ch, data) if block
493
491
  @result[:stdout] += data.to_s unless r == :no_append
494
492
  end
495
493
  ch.on_extended_data do |c, type, data|
494
+ @result[:last_event_at] = Time.new
496
495
  r = block.call(:stderr, ch, data) if block
497
496
  @result[:stderr] += data.to_s unless r == :no_append
498
497
  end
499
498
  ch.on_request('exit-status') do |c, data|
499
+ @result[:last_event_at] = Time.new
500
500
  exit_code = data.read_long
501
501
  block.call(:exit_code, ch, exit_code) if block
502
502
  @result[:exit_code] = exit_code
503
503
  end
504
504
  ch.on_request('exit-signal') do |c, data|
505
+ @result[:last_event_at] = Time.new
505
506
  exit_signal = data.read_string
506
507
  r = block.call(:exit_signal, ch, exit_signal) if block
507
508
  @result[:exit_signal] = exit_signal
@@ -513,7 +514,7 @@ module Net
513
514
  block.call(:start, ch, nil) if block
514
515
  end
515
516
  end
516
- channel.wait
517
+ wait_for_channel session, channel, @result, opts[:timeout]
517
518
  @result[:finish_at] = Time.new
518
519
  block.call(:finish, channel, nil) if block
519
520
  @result
@@ -561,18 +562,34 @@ module Net
561
562
  #
562
563
  def close
563
564
  Thread.current[:ssh_simple_sessions].values.each do |session|
564
- session.close
565
+ begin
566
+ Timeout.timeout(@opts[:close_timeout] || 5) { session.close }
567
+ rescue => e
568
+ begin
569
+ session.shutdown!
570
+ rescue
571
+ end
572
+ end
565
573
  end
566
574
  @result
567
575
  end
568
576
 
577
+
569
578
  private
570
- def with_session(host, opts={:timeout => 60}, &block)
579
+ EXTRA_OPTS = [:operation_timeout, :close_timeout]
580
+
581
+ def with_session(host, opts={}, &block)
582
+ opts[:timeout] ||= 60
583
+ opts[:timeout] = 2**32 if opts[:timeout] == 0
584
+ opts[:operation_timeout] ||= 3600
585
+ opts[:operation_timeout] = 2**32 if opts[:operation_timeout] == 0
586
+ opts[:close_timeout] ||= 5
571
587
  begin
572
- Timeout.timeout(opts[:timeout]) do
588
+ net_ssh_opts = opts.reject{|k,v| EXTRA_OPTS.include? k }
589
+ Timeout.timeout(opts[:operation_timeout]) do
573
590
  session = Thread.current[:ssh_simple_sessions][host.hash] \
574
591
  = Thread.current[:ssh_simple_sessions][host.hash] \
575
- || Net::SSH.start(*[host, opts[:user], opts])
592
+ || Net::SSH.start(*[host, opts[:user], net_ssh_opts])
576
593
  block.call(session)
577
594
  end
578
595
  rescue => e
@@ -585,10 +602,20 @@ module Net
585
602
  end
586
603
  end
587
604
 
605
+ def wait_for_channel(session, channel, result, timeout)
606
+ session.loop(1) do
607
+ if timeout < Time.now - result[:last_event_at]
608
+ raise Timeout::Error, 'idle timeout'
609
+ end
610
+ channel.active?
611
+ end
612
+ end
613
+
588
614
  def scp(mode, host, src, dst, opts={}, &block)
589
615
  @result = Result.new(
590
616
  { :op => :scp, :host => host, :opts => opts, :cmd => :scp_dl,
591
- :start_at => Time.new, :src => src, :dst => dst, :success => false
617
+ :last_event_at => Time.new, :start_at => Time.new,
618
+ :src => src, :dst => dst, :success => false
592
619
  } )
593
620
  with_session(host, opts) do |session|
594
621
  lt = 0
@@ -596,9 +623,10 @@ module Net
596
623
  @result[:name] ||= name
597
624
  @result[:total] ||= total
598
625
  @result[:sent] = sent
626
+ @result[:last_event_at] = Time.new
599
627
  block.call(sent, total) unless block.nil?
600
628
  end
601
- channel.wait
629
+ wait_for_channel session, channel, @result, opts[:timeout]
602
630
  @result[:finish_at] = Time.new
603
631
  @result[:success] = @result[:sent] == @result[:total]
604
632
  @result
@@ -634,6 +662,7 @@ module Net
634
662
  # @attr [String] cmd Shell command (SSH only)
635
663
  # @attr [Time] start_at Operation start timestamp
636
664
  # @attr [Time] finish_at Operation finish timestamp
665
+ # @attr [Time] last_event_at Timestamp of last activity
637
666
  # @attr [Boolean] timed_out Set to true if the operation timed out
638
667
  # @attr [String] stdout Output to stdout (SSH only)
639
668
  # @attr [String] stderr Output to stderr (SSH only)
@@ -1,7 +1,7 @@
1
1
  module Net
2
2
  module SSH
3
3
  class Simple
4
- VERSION = "1.3.2"
4
+ VERSION = "1.4.0"
5
5
  end
6
6
  end
7
7
  end
@@ -42,10 +42,57 @@ describe Net::SSH::Simple do
42
42
  end
43
43
  end
44
44
 
45
- it "enforces timeout" do
45
+ it "enforces idle timeout" do
46
46
  raised = false
47
47
  begin
48
48
  r = Net::SSH::Simple.ssh('localhost', 'sleep 60', {:timeout => 1})
49
+ rescue => e
50
+ raised = true
51
+ e.to_s.should match /^idle timeout @ .*/
52
+ e.result.op == :ssh
53
+ e.result.timed_out.should == true
54
+ end
55
+ raised.should == true
56
+ end
57
+
58
+ it "enforces operation timeout on ssh" do
59
+ raised = false
60
+ begin
61
+ r = Net::SSH::Simple.ssh('localhost', 'while true; do echo "buh"; sleep 1; done', {:operation_timeout => 2})
62
+ rescue => e
63
+ raised = true
64
+ e.to_s.should match /^execution expired @ .*/
65
+ e.result.op == :ssh
66
+ e.result.timed_out.should == true
67
+ end
68
+ raised.should == true
69
+ end
70
+
71
+ it "enforces operation timeout on scp_ul" do
72
+ raised = false
73
+ begin
74
+ r = Net::SSH::Simple.scp_ul('localhost', '/tmp/ssh_test_in0',
75
+ '/tmp/ssh_test_out0', {:operation_timeout=>1}) \
76
+ do |sent,total|
77
+ sleep 5
78
+ end
79
+ rescue => e
80
+ raised = true
81
+ e.to_s.should match /^execution expired @ .*/
82
+ e.result.op == :ssh
83
+ e.result.timed_out.should == true
84
+ end
85
+ raised.should == true
86
+ end
87
+
88
+ it "enforces operation timeout on scp_dl" do
89
+ raised = false
90
+ begin
91
+ r = Net::SSH::Simple.scp_dl('localhost', '/tmp/ssh_test_in0',
92
+ '/tmp/ssh_test_out0', {:operation_timeout=>1}) \
93
+ do |sent,total|
94
+ sleep 5
95
+ end
49
96
  rescue => e
50
97
  raised = true
51
98
  e.to_s.should match /^execution expired @ .*/
@@ -59,6 +106,10 @@ describe Net::SSH::Simple do
59
106
  Net::SSH::Simple.ssh('localhost', 'sleep 2', {:timeout => 0})
60
107
  end
61
108
 
109
+ it "interprets operation_timeout=0 as no timeout" do
110
+ Net::SSH::Simple.ssh('localhost', 'sleep 2', {:operation_timeout => 0})
111
+ end
112
+
62
113
  it "fails gently" do
63
114
  raised = false
64
115
  begin
@@ -182,6 +233,13 @@ describe Net::SSH::Simple do
182
233
  it "returns a result" do
183
234
  Net::SSH::Simple.sync do
184
235
  ssh('localhost', 'true').success.should == true
236
+ # see coverage-report to see if session#shutdown! was exercised
237
+ end
238
+ end
239
+
240
+ it "force closes" do
241
+ Net::SSH::Simple.sync({:close_timeout => true}) do
242
+ ssh('localhost', 'true').success.should == true
185
243
  end
186
244
  end
187
245
 
@@ -317,7 +375,7 @@ describe Net::SSH::Simple do
317
375
  end
318
376
 
319
377
  it "handles signals" do
320
- victim = Net::SSH::Simple.async do
378
+ victim = Net::SSH::Simple.async({:timeout => 10}) do
321
379
  begin
322
380
  ssh('localhost', 'sleep 1020304157')
323
381
  rescue => e
@@ -325,12 +383,14 @@ describe Net::SSH::Simple do
325
383
  end
326
384
  end
327
385
 
328
- killer = Net::SSH::Simple.async do
386
+ killer = Net::SSH::Simple.async({:operation_timeout => 5}) do
387
+ sleep 1 while 0 != ssh('localhost', "pgrep -f 'sleep 1020304157'").exit_code
329
388
  ssh('localhost', "pkill -f 'sleep 1020304157'")
330
389
  end
331
390
 
332
391
  k = killer.value
333
392
  k.success.should == true
393
+ k.exit_code.should == 0
334
394
 
335
395
  v = victim.value
336
396
  v.to_s.should match /Killed by SIGTERM @ .*/
@@ -610,7 +670,8 @@ describe Net::SSH::Simple do
610
670
  end
611
671
  end
612
672
 
613
- killer = Net::SSH::Simple.async do
673
+ killer = Net::SSH::Simple.async({:operation_timeout => 5}) do
674
+ sleep 1 while 0 != ssh('localhost', "pgrep -f 'sleep 1020304157'").exit_code
614
675
  ssh('localhost', "pkill -f 'sleep 1020304157'")
615
676
  end
616
677
 
@@ -649,10 +710,12 @@ describe Net::SSH::Simple do
649
710
  end
650
711
  end
651
712
 
652
- killer = Net::SSH::Simple.async do
713
+ killer = Net::SSH::Simple.async({:operation_timeout => 5}) do
714
+ sleep 1 while 0 != ssh('localhost', "pgrep -f 'sleep 1020304157'").exit_code
653
715
  ssh('localhost', "pkill -f 'sleep 1020304157'")
654
716
  end
655
717
 
718
+
656
719
  k = killer.value
657
720
  k.success.should == true
658
721
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: net-ssh-simple
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.2
4
+ version: 1.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-10-26 00:00:00.000000000Z
12
+ date: 2011-10-28 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: net-ssh
16
- requirement: &14644940 !ruby/object:Gem::Requirement
16
+ requirement: &9197060 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 2.1.4
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *14644940
24
+ version_requirements: *9197060
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: net-scp
27
- requirement: &14643240 !ruby/object:Gem::Requirement
27
+ requirement: &9194900 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 1.0.4
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *14643240
35
+ version_requirements: *9194900
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: blockenspiel
38
- requirement: &14642340 !ruby/object:Gem::Requirement
38
+ requirement: &9193640 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 0.4.3
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *14642340
46
+ version_requirements: *9193640
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: hashie
49
- requirement: &14641260 !ruby/object:Gem::Requirement
49
+ requirement: &9192620 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ~>
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: 1.1.0
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *14641260
57
+ version_requirements: *9192620
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: rake
60
- requirement: &14630380 !ruby/object:Gem::Requirement
60
+ requirement: &9191660 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ~>
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: 0.9.2.2
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *14630380
68
+ version_requirements: *9191660
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rspec
71
- requirement: &14629180 !ruby/object:Gem::Requirement
71
+ requirement: &9180700 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: '0'
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *14629180
79
+ version_requirements: *9180700
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: cover_me
82
- requirement: &14628240 !ruby/object:Gem::Requirement
82
+ requirement: &9179600 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ! '>='
@@ -87,7 +87,7 @@ dependencies:
87
87
  version: '0'
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *14628240
90
+ version_requirements: *9179600
91
91
  description: Net::SSH::Simple is a simple wrapper around Net::SSH and Net::SCP.
92
92
  email:
93
93
  - moe@busyloop.net
@@ -124,7 +124,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
124
124
  version: '0'
125
125
  segments:
126
126
  - 0
127
- hash: -3101248032376322476
127
+ hash: 2108785634158212741
128
128
  requirements: []
129
129
  rubyforge_project:
130
130
  rubygems_version: 1.8.10