net-ssh-simple 1.3.2 → 1.4.0

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.
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