kanrisuru 0.19.3 → 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bf7609a6bca967a3e143022048f72bd48a78bd31c10441e3d65915ecec6d8fd4
4
- data.tar.gz: cf583cafffe5cc3186bd7822be52609a62f0cae704f978a87f5afab3d3b7eb74
3
+ metadata.gz: 38c3969fd06510c73c0237e611fb1b0acd31dade42489f21b9d118a456e5b834
4
+ data.tar.gz: 604ca2ef34b74dc435aa35ee24853f8f563e42cff6309d6d7d2c6b6437aa7bc3
5
5
  SHA512:
6
- metadata.gz: 23c93c9743a9b41f57754b81f580d5db88819ca1185432bc96463a786def78396dff7711675159a843b25d7d7ce3b39025ef33374b012b62bd0954c81483374c
7
- data.tar.gz: fee1f68f24dba3c8973d5e3e2263cd4dfa0192dc77af3bf13b77f4c9cfd3204acc073d070a4200da93a6c9da6a0afec53cb25ee080f7b41440990712f2250a44
6
+ metadata.gz: 6ef8d8316b07a088e88a4aad0f5d0bb48419890ba0f00895c2bbae01eb356db246104e095d4e7fe06b91d9cebeaa2c2efe973d3302a40e3eeb4714d3e394dd83
7
+ data.tar.gz: b78410887ec8b5b3e809f4e59934f7c7f6e5d0363ddf9e4a38f436bd42ca0a43de8117f837646edec963c07051f4e546a0705ae2e4581a8971ead44943f22cca
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## Kanrisuru 1.0.0.beta1 (February 27, 2022) ##
2
+ * Add parallel mode for cluster. Allows hosts to run commands concurrently, reducing time it takes due to the high I/O blocking nature of the network requests.
3
+ * Add test cases for cluster parallel mode.
4
+ * Clean up methods on cluster class to use map and each methods in a simplified manner.
5
+ * Remove `/spec` dir from codecoverage.
6
+
7
+ ## Kanrisuru 0.20.0 (February 21, 2022) ##
8
+ * Allow hosts to be connected via proxy host. This is much like using a bastion / jump server.
9
+ * Add integration test cases for proxy host connection.
10
+
11
+ ## Kanrisuru 0.19.4 (February 19, 2022) ##
12
+ * Add additional family types to `ss` command.
13
+
1
14
  ## Kanrisuru 0.19.3 (February 19, 2022) ##
2
15
  * Fix bug in `ss` command with passing opts to parser.
3
16
 
data/README.md CHANGED
@@ -54,6 +54,27 @@ host = Kanrisuru::Remote::Host.new(
54
54
  )
55
55
  ```
56
56
 
57
+ #### Connect with a Jump / Bastion Host
58
+ To connect to a host behind a firewall through a jump / bastion host, pass either an instance of another Kanrisuru::Remote::Host, or a hash of host config values
59
+
60
+ ```ruby
61
+ proxy = Kanrisuru::Remote::Host.new(
62
+ host: 'proxy-host',
63
+ username: 'ubuntu',
64
+ keys: ['~/.ssh/proxy.pem']
65
+ )
66
+
67
+ host = Kanrisuru::Remote::Host.new(
68
+ host: '1.2.3.4',
69
+ username: 'ubuntu',
70
+ keys: ['~/.ssh/id_rsa'],
71
+ proxy: proxy
72
+ )
73
+
74
+ host.whoami
75
+ 'ubuntu'
76
+ ```
77
+
57
78
  #### run a simple echo command on the remote host
58
79
  ```ruby
59
80
  host.env['VAR'] = 'world'
@@ -124,7 +145,24 @@ host = Kanrisuru::Remote::Host.new(host: 'remote-host-4', username: 'rhel', keys
124
145
  cluster << host
125
146
  ```
126
147
 
127
- Kanrisuru at this point only runs commands sequentially. We plan on creating a parallel run mode in a future release.
148
+ #### Run cluster in parallel mode to reduce time waiting on blocking IO
149
+ ```ruby
150
+ Benchmark.measure do
151
+ cluster.each do |host|
152
+ puts cluster.pwd
153
+ end
154
+ end
155
+ # => 0.198980 0.029681 0.228661 ( 5.258496)
156
+
157
+ cluster.parallel = true
158
+
159
+ Benchmark.measure do
160
+ cluster.each do |host|
161
+ puts cluster.pwd
162
+ end
163
+ end
164
+ # => 0.016478 0.007956 0.024434 ( 0.120066)
165
+ ```
128
166
 
129
167
  #### To run across all hosts with a single command, cluster will return a array of result hashes
130
168
  ```ruby
data/kanrisuru.gemspec CHANGED
@@ -27,6 +27,7 @@ Gem::Specification.new do |gem|
27
27
  gem.add_runtime_dependency 'net-ping', '~> 2.0'
28
28
  gem.add_runtime_dependency 'net-scp', '~> 3.0'
29
29
  gem.add_runtime_dependency 'net-ssh', '~> 6.1'
30
+ gem.add_runtime_dependency 'net-ssh-gateway', '~> 2.0'
30
31
 
31
32
  gem.files = `git ls-files`.split("\n")
32
33
  gem.require_paths = ['lib']
@@ -22,7 +22,7 @@ module Kanrisuru
22
22
  }.freeze
23
23
 
24
24
  NETWORK_FAMILIES = %w[
25
- unix inet inet6 link netlink
25
+ unix inet inet6 link netlink vsock xdp
26
26
  ].freeze
27
27
  end
28
28
  end
@@ -14,7 +14,7 @@ module Kanrisuru
14
14
  else
15
15
  command.append_flag('--all')
16
16
  end
17
-
17
+
18
18
  execute_shell(command)
19
19
 
20
20
  Kanrisuru::Result.new(command) do |cmd|
@@ -1,18 +1,19 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'ostruct'
3
4
 
4
5
  module Kanrisuru
5
6
  module Core
6
7
  module System
7
8
  module Parser
8
- class Sysctl
9
+ class Sysctl
9
10
  class << self
10
11
  def parse(command)
11
12
  result = {}
12
13
 
13
14
  lines = command.to_a
14
15
  lines.each do |line|
15
- next if line.include?("permission denied on key")
16
+ next if line.include?('permission denied on key')
16
17
 
17
18
  keys, value = parse_line(line)
18
19
  next if Kanrisuru::Util.blank?(value)
@@ -27,7 +28,7 @@ module Kanrisuru
27
28
  def build_struct(hash)
28
29
  struct = Struct.new(*hash.keys).new
29
30
 
30
- hash.keys.each do |key|
31
+ hash.each_key do |key|
31
32
  struct[key] = hash[key].is_a?(Hash) ? build_struct(hash[key]) : hash[key]
32
33
  end
33
34
 
@@ -35,7 +36,7 @@ module Kanrisuru
35
36
  end
36
37
 
37
38
  def merge_recursively(h1, h2)
38
- h1.merge(h2) do |k, v1, v2|
39
+ h1.merge(h2) do |_k, v1, v2|
39
40
  if v1.is_a?(Hash) && v2.is_a?(Hash)
40
41
  merge_recursively(v1, v2)
41
42
  else
@@ -64,7 +65,6 @@ module Kanrisuru
64
65
 
65
66
  [string.split('.').map(&:to_sym), value]
66
67
  end
67
-
68
68
  end
69
69
  end
70
70
  end
@@ -34,9 +34,8 @@ module Kanrisuru
34
34
  os_method_names.each do |method_name|
35
35
  define_method method_name do |*args, &block|
36
36
  cluster = namespace_instance.instance_variable_get(:@cluster)
37
- hosts = cluster.instance_variable_get(:@hosts)
38
- hosts.map do |host_addr, host|
39
- { host: host_addr, result: host.send(namespace).send(method_name, *args, &block) }
37
+ cluster.map do |host|
38
+ host.send(namespace).send(method_name, *args, &block)
40
39
  end
41
40
  end
42
41
  end
@@ -45,8 +44,8 @@ module Kanrisuru
45
44
  class_eval do
46
45
  os_method_names.each do |method_name|
47
46
  define_method method_name do |*args, &block|
48
- @hosts.map do |host_addr, host|
49
- { host: host_addr, result: host.send(method_name, *args, &block) }
47
+ map do |host|
48
+ host.send(method_name, *args, &block)
50
49
  end
51
50
  end
52
51
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+ require 'etc'
3
+
4
+ module Kanrisuru
5
+ # https://github.com/grosser/parallel/blob/master/lib/parallel/processor_count.rb
6
+ module ProcessorCount
7
+ # Number of processors seen by the OS, used for process scheduling
8
+ def self.processor_count
9
+ @processor_count ||= Integer(ENV['PARALLEL_PROCESSOR_COUNT'] || Etc.nprocessors)
10
+ end
11
+
12
+ # Number of physical processor cores on the current system.
13
+ def self.physical_processor_count
14
+ @physical_processor_count ||= begin
15
+ ppc =
16
+ case RbConfig::CONFIG["target_os"]
17
+ when /darwin[12]/
18
+ IO.popen("/usr/sbin/sysctl -n hw.physicalcpu").read.to_i
19
+ when /linux/
20
+ cores = {} # unique physical ID / core ID combinations
21
+ phy = 0
22
+ IO.read("/proc/cpuinfo").scan(/^physical id.*|^core id.*/) do |ln|
23
+ if ln.start_with?("physical")
24
+ phy = ln[/\d+/]
25
+ elsif ln.start_with?("core")
26
+ cid = "#{phy}:#{ln[/\d+/]}"
27
+ cores[cid] = true unless cores[cid]
28
+ end
29
+ end
30
+ cores.count
31
+ when /mswin|mingw/
32
+ require 'win32ole'
33
+ result_set = WIN32OLE.connect("winmgmts://").ExecQuery(
34
+ "select NumberOfCores from Win32_Processor"
35
+ )
36
+ result_set.to_enum.collect(&:NumberOfCores).reduce(:+)
37
+ else
38
+ processor_count
39
+ end
40
+ # fall back to logical count if physical info is invalid
41
+ ppc > 0 ? ppc : processor_count
42
+ end
43
+ end
44
+ end
45
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require 'thread'
2
3
 
3
4
  module Kanrisuru
4
5
  module Remote
@@ -6,7 +7,12 @@ module Kanrisuru
6
7
  extend OsPackage::Collection
7
8
  include Enumerable
8
9
 
10
+ attr_accessor :parallel, :concurrency
11
+
9
12
  def initialize(*hosts)
13
+ @parallel = false
14
+ @concurrency = local_concurrency
15
+
10
16
  @hosts = {}
11
17
  hosts.each do |host_opts|
12
18
  add_host(host_opts)
@@ -30,39 +36,23 @@ module Kanrisuru
30
36
  end
31
37
 
32
38
  def execute(command)
33
- @hosts.map do |host_addr, host|
34
- ## Need to evaluate each host independently for the command.
35
- cmd = create_command(command)
36
-
37
- { host: host_addr, result: host.execute(cmd) }
38
- end
39
+ map { |host| host.execute(create_command(command)) }
39
40
  end
40
41
 
41
42
  def execute_shell(command)
42
- @hosts.map do |host_addr, host|
43
- ## Need to evaluate each host independently for the command.
44
- cmd = create_command(command)
45
-
46
- { host: host_addr, result: host.execute_shell(cmd) }
47
- end
48
- end
49
-
50
- def each(&block)
51
- @hosts.each { |_host_addr, host| block.call(host) }
43
+ map { |host| host.execute_shell(create_command(command)) }
52
44
  end
53
45
 
54
46
  def hostname
55
- map_host_results(:hostname)
47
+ map { |host| host.send(:hostname) }
56
48
  end
57
49
 
58
50
  def ping?
59
- map_host_results(:ping?)
51
+ map { |host| host.send(:ping?) }
60
52
  end
61
53
 
62
54
  def su(user)
63
- @hosts.each do |_host_addr, host|
64
- host.su(user)
65
- end
55
+ each { |host| host.su(user) }
66
56
  end
67
57
 
68
58
  def chdir(path = '~')
@@ -70,19 +60,70 @@ module Kanrisuru
70
60
  end
71
61
 
72
62
  def cd(path = '~')
73
- @hosts.each do |_host_addr, host|
74
- host.cd(path)
75
- end
63
+ each { |host| host.cd(path) }
76
64
  end
77
65
 
78
66
  def disconnect
79
- @hosts.each do |_host_addr, host|
80
- host.disconnect
81
- end
67
+ each { |host| host.disconnect }
68
+ end
69
+
70
+ def map(&block)
71
+ parallel? ? each_parallel(preserve: true, &block) : each_sequential(preserve: true, &block)
72
+ end
73
+
74
+ def each(&block)
75
+ parallel? ? each_parallel(preserve: false, &block) : each_sequential(preserve: false, &block)
76
+ end
77
+
78
+ def parallel?
79
+ @parallel
80
+ end
81
+
82
+ def sequential?
83
+ !parallel?
82
84
  end
83
85
 
84
86
  private
85
87
 
88
+ def each_sequential(opts = {}, &block)
89
+ results = @hosts.map do |host_addr, host|
90
+ { host: host_addr, result: block.call(host) }
91
+ end
92
+
93
+ opts[:preserve] ? results : self
94
+ end
95
+
96
+ def each_parallel(opts = {}, &block)
97
+ queue = Queue.new.tap do |q|
98
+ @hosts.each { |_, host| q << host }
99
+ end
100
+
101
+ threads = []
102
+ results = []
103
+ mutex = Mutex.new
104
+
105
+ ## No need to spawn more threads then number of hosts in cluster
106
+ concurrency = queue.length < @concurrency ? queue.length : @concurrency
107
+ concurrency.times do
108
+ threads << Thread.new do
109
+ loop do
110
+ host = queue.pop(true) rescue Thread.exit
111
+
112
+ begin
113
+ result = block.call(host)
114
+ mutex.synchronize { results.push({ host: host.host, result: result }) }
115
+ rescue Exception => exception
116
+ mutex.synchronize { results.push({ host: host.host, result: exception }) }
117
+ end
118
+ end
119
+ end
120
+ end
121
+
122
+ threads.each(&:join)
123
+
124
+ opts[:preserve] ? results : self
125
+ end
126
+
86
127
  def create_command(command)
87
128
  case command
88
129
  when String
@@ -94,12 +135,6 @@ module Kanrisuru
94
135
  end
95
136
  end
96
137
 
97
- def map_host_results(action)
98
- @hosts.map do |host_addr, host|
99
- { host: host_addr, result: host.send(action) }
100
- end
101
- end
102
-
103
138
  def remove_host(host)
104
139
  if host.instance_of?(Kanrisuru::Remote::Host)
105
140
  removed = false
@@ -125,14 +160,22 @@ module Kanrisuru
125
160
  end
126
161
 
127
162
  def add_host(host_opts)
128
- if host_opts.instance_of?(Hash)
163
+ case host_opts
164
+ when Hash
129
165
  @hosts[host_opts[:host]] = Kanrisuru::Remote::Host.new(host_opts)
130
- elsif host_opts.instance_of?(Kanrisuru::Remote::Host)
166
+ when Kanrisuru::Remote::Host
131
167
  @hosts[host_opts.host] = host_opts
168
+ when Kanrisuru::Remote::Cluster
169
+ host_opts.send(:each_sequential) { |host| @hosts[host.host] = host }
132
170
  else
133
171
  raise ArgumentError, 'Invalid host option'
134
172
  end
135
173
  end
174
+
175
+ def local_concurrency
176
+ ProcessorCount.physical_processor_count
177
+ end
178
+
136
179
  end
137
180
  end
138
181
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'net/ssh'
4
+ require 'net/ssh/gateway'
4
5
  require 'net/scp'
5
6
  require 'net/ping'
6
7
 
@@ -12,6 +13,8 @@ module Kanrisuru
12
13
  attr_reader :host, :username, :password, :port, :keys
13
14
 
14
15
  def initialize(opts = {})
16
+ @opts = opts
17
+
15
18
  @host = opts[:host]
16
19
  @username = opts[:username]
17
20
  @login_user = @username
@@ -84,7 +87,11 @@ module Kanrisuru
84
87
  end
85
88
 
86
89
  def ssh
87
- @ssh ||= Net::SSH.start(@host, @username, keys: @keys, password: @password, port: @port)
90
+ @ssh ||= init_ssh
91
+ end
92
+
93
+ def proxy
94
+ @proxy ||= init_proxy
88
95
  end
89
96
 
90
97
  def ping?
@@ -110,6 +117,7 @@ module Kanrisuru
110
117
 
111
118
  def disconnect
112
119
  ssh.close
120
+ proxy&.close(ssh.transport.port)
113
121
  end
114
122
 
115
123
  private
@@ -158,6 +166,35 @@ module Kanrisuru
158
166
  end
159
167
  end
160
168
 
169
+ def init_ssh
170
+ if proxy&.active?
171
+ proxy.ssh(@host, @username,
172
+ keys: @keys, password: @password, port: @port)
173
+ else
174
+ Net::SSH.start(@host, @username,
175
+ keys: @keys, password: @password, port: @port)
176
+ end
177
+ end
178
+
179
+ def init_proxy
180
+ return unless @opts[:proxy]
181
+
182
+ proxy = @opts[:proxy]
183
+
184
+ case proxy
185
+ when Hash
186
+ Net::SSH::Gateway.new(proxy[:host], proxy[:username],
187
+ keys: proxy[:keys], password: proxy[:password], port: proxy[:port])
188
+ when Kanrisuru::Remote::Host
189
+ Net::SSH::Gateway.new(proxy.host, proxy.username,
190
+ keys: proxy.keys, password: proxy.password, port: proxy.port)
191
+ when Net::SSH::Gateway
192
+ proxy
193
+ else
194
+ raise ArgumentError, 'Invalid proxy type'
195
+ end
196
+ end
197
+
161
198
  def init_hostname
162
199
  command = Kanrisuru::Command.new('hostname')
163
200
  execute(command)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kanrisuru
4
- VERSION = '0.19.3'
4
+ VERSION = '1.0.0.beta1'
5
5
  end
data/lib/kanrisuru.rb CHANGED
@@ -29,6 +29,7 @@ require_relative 'kanrisuru/util'
29
29
  require_relative 'kanrisuru/mode'
30
30
  require_relative 'kanrisuru/os_package'
31
31
  require_relative 'kanrisuru/command'
32
+ require_relative 'kanrisuru/processor_count'
32
33
  require_relative 'kanrisuru/remote'
33
34
  require_relative 'kanrisuru/result'
34
35
  require_relative 'kanrisuru/core'
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Kanrisuru::ProcessorCount do
6
+ it 'gets processor count' do
7
+ expect(described_class.processor_count).to be > 0
8
+ end
9
+
10
+ it 'gets physical processor count' do
11
+ expect(described_class.physical_processor_count).to be > 0
12
+ end
13
+
14
+ it "is even factor of logical cpus" do
15
+ expect(described_class.processor_count % described_class.physical_processor_count).to be == 0
16
+ end
17
+ end
@@ -27,6 +27,14 @@ RSpec.describe Kanrisuru::Remote::Cluster do
27
27
  )
28
28
  end
29
29
 
30
+ let(:host3) do
31
+ Kanrisuru::Remote::Host.new(
32
+ host: 'centos-host',
33
+ username: 'centos',
34
+ keys: ['id_rsa']
35
+ )
36
+ end
37
+
30
38
  it 'adds host to a cluster' do
31
39
  cluster = described_class.new(host1)
32
40
  expect(cluster.hosts.length).to eq(1)
@@ -46,7 +54,7 @@ RSpec.describe Kanrisuru::Remote::Cluster do
46
54
  username: 'centos',
47
55
  keys: ['id_rsa']
48
56
  }
49
-
57
+
50
58
  expect(cluster.hosts.length).to eq(3)
51
59
  expect(cluster.count).to eq(3)
52
60
  expect(cluster.count).to eq(3)
@@ -55,6 +63,19 @@ RSpec.describe Kanrisuru::Remote::Cluster do
55
63
  expect(cluster.hosts).to include(host2)
56
64
  end
57
65
 
66
+ it 'adds a cluster to another cluster' do
67
+ cluster1 = described_class.new(host1, host2)
68
+ cluster2 = described_class.new(host3)
69
+
70
+ cluster1 << cluster2
71
+ expect(cluster1.hosts.length).to eq(3)
72
+ expect(cluster1.count).to eq(3)
73
+ expect(cluster1.count).to eq(3)
74
+ expect(cluster1['centos-host'].username).to eq('centos')
75
+ expect(cluster1.hosts).to include(host1)
76
+ expect(cluster1.hosts).to include(host2)
77
+ end
78
+
58
79
  it 'fails to add host to a cluster' do
59
80
  cluster = described_class.new
60
81
  expect { cluster << 1 }.to raise_error(ArgumentError)
@@ -90,6 +111,58 @@ RSpec.describe Kanrisuru::Remote::Cluster do
90
111
  end.to raise_error(ArgumentError)
91
112
  end
92
113
 
114
+ it 'initializes cluster and is run in sequential mode by default' do
115
+ cluster = described_class.new
116
+ expect(cluster).to be_sequential
117
+ end
118
+
119
+ it 'sets cluster to parallel mode' do
120
+ cluster = described_class.new
121
+ cluster.parallel = true
122
+ expect(cluster).to be_parallel
123
+ end
124
+
125
+ it 'runs commands on a cluster sequentialy' do
126
+ cluster = described_class.new(host1, host2)
127
+
128
+ expect(cluster).to receive(:each_sequential)
129
+
130
+ command = Kanrisuru::Command.new('pwd')
131
+ cluster.execute(command)
132
+ end
133
+
134
+ it 'runs commands on a cluster in parallel across hosts' do
135
+ cluster = described_class.new(host1, host2)
136
+ cluster.parallel = true
137
+
138
+ expect(cluster).to receive(:each_parallel)
139
+
140
+ command = Kanrisuru::Command.new('pwd')
141
+ cluster.execute(command)
142
+ end
143
+
144
+ it 'gets cluster back from each method' do
145
+ cluster = described_class.new(host1, host2)
146
+
147
+ c = cluster.each { |h| h.su('root') }
148
+ expect(c).to be_instance_of(Kanrisuru::Remote::Cluster)
149
+ expect(c.object_id).to eq(cluster.object_id)
150
+ end
151
+
152
+ it 'gets results back from map method' do
153
+ cluster = described_class.new(host1, host2)
154
+
155
+ results = cluster.map do |host|
156
+ host.execute('hello')
157
+ end
158
+
159
+ expect(results).to be_instance_of(Array)
160
+ results.each do |result|
161
+ expect(result).to have_key(:host)
162
+ expect(result).to have_key(:result)
163
+ end
164
+ end
165
+
93
166
  it 'runs execute for a command on a cluster' do
94
167
  cluster = described_class.new
95
168
  cluster << host1
@@ -185,6 +258,19 @@ RSpec.describe Kanrisuru::Remote::Cluster do
185
258
  StubNetwork.unstub_command!(:realpath)
186
259
  end
187
260
 
261
+ it 'raises and catches exception in parallel mode' do
262
+ cluster = described_class.new(host1, host2, host3)
263
+ cluster.parallel = true
264
+
265
+ results = cluster.map do |host|
266
+ raise ArgumentError
267
+ end
268
+
269
+ results.each do |result|
270
+ expect(result[:result]).to be_instance_of(ArgumentError)
271
+ end
272
+ end
273
+
188
274
  it 'changes current working directory for each host in a cluster' do
189
275
  cluster = described_class.new(host1, host2)
190
276
 
@@ -8,6 +8,15 @@
8
8
  "kernel": "Linux",
9
9
  "home": "/home/ubuntu"
10
10
  },
11
+ {
12
+ "name": "proxy",
13
+ "port": 22,
14
+ "username": "ubuntu",
15
+ "ssh_key": "~/.ssh/id_rsa",
16
+ "hostname": "proxy-host",
17
+ "kernel": "Linux",
18
+ "home": "/home/ubuntu"
19
+ },
11
20
  {
12
21
  "name": "ubuntu",
13
22
  "port": 22,
@@ -4,6 +4,8 @@ require 'simplecov'
4
4
  require 'simplecov-cobertura'
5
5
 
6
6
  SimpleCov.start do
7
+ add_filter '/spec/'
8
+
7
9
  command_name "kanrisuru-tests#{ENV['TEST_ENV_NUMBER'] || ''}"
8
10
  merge_timeout 2400
9
11
 
@@ -20,6 +20,11 @@ class TestHosts
20
20
  hosts(name)
21
21
  end
22
22
 
23
+ def resolve(hostname)
24
+ s = Socket.getaddrinfo(hostname, nil)
25
+ s[0][2]
26
+ end
27
+
23
28
  def spec_dir(host_json)
24
29
  random_string = SecureRandom.hex
25
30
  "#{host_json['home']}/.kanrisuru_spec_files_#{random_string[0..5]}"
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'benchmark'
4
+ require 'spec_helper'
5
+
6
+ RSpec.describe Kanrisuru::Remote::Cluster do
7
+ let(:host1) do
8
+ Kanrisuru::Remote::Host.new(
9
+ host: TestHosts.host('ubuntu')['hostname'],
10
+ username: TestHosts.host('ubuntu')['username'],
11
+ keys: [TestHosts.host('ubuntu')['ssh_key']]
12
+ )
13
+ end
14
+
15
+ let(:host2) do
16
+ Kanrisuru::Remote::Host.new(
17
+ host: TestHosts.host('debian')['hostname'],
18
+ username: TestHosts.host('debian')['username'],
19
+ keys: [TestHosts.host('debian')['ssh_key']]
20
+ )
21
+ end
22
+
23
+ let(:host3) do
24
+ Kanrisuru::Remote::Host.new(
25
+ host: TestHosts.host('centos')['hostname'],
26
+ username: TestHosts.host('centos')['username'],
27
+ keys: [TestHosts.host('centos')['ssh_key']]
28
+ )
29
+ end
30
+
31
+ let(:host4) do
32
+ Kanrisuru::Remote::Host.new(
33
+ host: TestHosts.host('opensuse')['hostname'],
34
+ username: TestHosts.host('opensuse')['username'],
35
+ keys: [TestHosts.host('opensuse')['ssh_key']]
36
+ )
37
+ end
38
+
39
+ it 'gets hostname for cluster' do
40
+ cluster = described_class.new(host1, host2, host3, host4)
41
+ expect(cluster.hostname).to match([
42
+ { host: 'ubuntu-host', result: 'ubuntu-host' },
43
+ { host: 'debian-host', result: 'debian-host' },
44
+ { host: 'centos-host', result: 'centos-host' },
45
+ { host: 'opensuse-host', result: 'opensuse-host' },
46
+ ])
47
+
48
+ cluster.disconnect
49
+ end
50
+
51
+ it 'can ping host cluster' do
52
+ cluster = described_class.new(host1, host2, host3, host4)
53
+ expect(cluster.ping?).to match([
54
+ { host: 'ubuntu-host', result: true },
55
+ { host: 'debian-host', result: true },
56
+ { host: 'centos-host', result: true },
57
+ { host: 'opensuse-host', result: true },
58
+ ])
59
+
60
+ cluster.disconnect
61
+ end
62
+
63
+ it 'should use specific number of threads as number of hosts' do
64
+ cluster = described_class.new(host1, host2, host3, host4)
65
+ cluster.parallel = true
66
+
67
+ ## Called x2 b/c two methods invoke the parallel runner
68
+ expect(Thread).to receive(:new).exactly(cluster.count * 2).times.and_call_original
69
+
70
+ cluster.hostname
71
+ cluster.disconnect
72
+ end
73
+
74
+ it 'should respect concurrency setting' do
75
+ concurrency = 2
76
+
77
+ cluster = described_class.new(host1, host2, host3, host4)
78
+ cluster.parallel = true
79
+ cluster.concurrency = concurrency
80
+
81
+ ## Called x2 b/c two methods invoke the parallel runner
82
+ expect(Thread).to receive(:new).exactly(concurrency * 2).times.and_call_original
83
+
84
+ cluster.hostname
85
+ cluster.disconnect
86
+ end
87
+
88
+ it 'saves time running cluster in parallel mode' do
89
+ cluster = described_class.new(host1, host2, host3, host4)
90
+ time1 = Benchmark.measure {
91
+ cluster.each do |host|
92
+ expect(host.pwd).to be_success
93
+ end
94
+ }
95
+
96
+ cluster.parallel = true
97
+ time2 = Benchmark.measure {
98
+ cluster.each do |host|
99
+ expect(host.pwd).to be_success
100
+ end
101
+ }
102
+
103
+ expect(time1.total).to be > time2.total
104
+ cluster.disconnect
105
+ end
106
+
107
+ it 'disconnects all hosts' do
108
+ cluster = described_class.new(host1, host2, host3, host4)
109
+ cluster.disconnect
110
+
111
+ cluster.each do |host|
112
+ expect(host.ssh).to be_closed
113
+ end
114
+ end
115
+ end
@@ -103,7 +103,7 @@ RSpec.shared_examples 'system' do |os_name, host_json, _spec_dir|
103
103
  it 'gets kernel sysctl info' do
104
104
  result = host.sysctl
105
105
  expect(result).to be_success
106
- expect(result).to respond_to :net
106
+ expect(result).to respond_to :net
107
107
  expect(result).to respond_to :kernel
108
108
  expect(result).to respond_to :dev
109
109
 
@@ -24,6 +24,45 @@ RSpec.shared_examples 'host' do |os_name, host_json, _spec_dir|
24
24
  expect(host.hostname).to eq(host_json['hostname'])
25
25
  end
26
26
 
27
+ it 'connects with proxy host' do
28
+ config = TestHosts.host('proxy')
29
+ proxy = Kanrisuru::Remote::Host.new(
30
+ host: config['hostname'],
31
+ username: config['username'],
32
+ keys: [config['ssh_key']]
33
+ )
34
+
35
+ expect(proxy).to be_ping
36
+
37
+ host = Kanrisuru::Remote::Host.new(
38
+ host: TestHosts.resolve(host_json['hostname']),
39
+ username: host_json['username'],
40
+ keys: [host_json['ssh_key']],
41
+ proxy: proxy
42
+ )
43
+
44
+ ## Test instantiation
45
+ expect(host.proxy).to be_instance_of(Net::SSH::Gateway)
46
+ expect(host.ssh).to be_instance_of(Net::SSH::Connection::Session)
47
+
48
+ ## Test basic commands
49
+ expect(host.hostname).to eq(host_json['hostname'])
50
+
51
+ host.cd('../')
52
+ expect(host.pwd.path).to eq('/home')
53
+
54
+ expect(host.whoami.user).to eq(host_json['username'])
55
+ host.su 'root'
56
+ expect(host.whoami.user).to eq('root')
57
+
58
+ ## Test download
59
+ src_path = '/etc/hosts'
60
+ result = host.download(src_path)
61
+ expect(result).to be_instance_of(String)
62
+ lines = result.split("\n")
63
+ expect(lines.length).to be >= 1
64
+ end
65
+
27
66
  it 'changes directories' do
28
67
  expect(host.pwd.path).to eq(host_json['home'])
29
68
 
@@ -42,7 +42,7 @@ RSpec.describe Kanrisuru::Core::Socket do
42
42
  )
43
43
  expect(Kanrisuru::Core::Socket::NETWORK_FAMILIES).to eq(
44
44
  %w[
45
- unix inet inet6 link netlink
45
+ unix inet inet6 link netlink vsock xdp
46
46
  ]
47
47
  )
48
48
  expect(Kanrisuru::Core::Socket::OTHER_STATES).to eq(
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Kanrisuru::ProcessorCount do
6
+ it 'responds to methods' do
7
+ expect(described_class).to respond_to(:processor_count)
8
+ expect(described_class).to respond_to(:physical_processor_count)
9
+ end
10
+ end
@@ -26,11 +26,16 @@ RSpec.describe Kanrisuru::Remote::Cluster do
26
26
  expect(cluster).to respond_to(:execute)
27
27
  expect(cluster).to respond_to(:execute_shell)
28
28
  expect(cluster).to respond_to(:each)
29
+ expect(cluster).to respond_to(:map)
29
30
  expect(cluster).to respond_to(:hostname)
30
31
  expect(cluster).to respond_to(:ping?)
31
32
  expect(cluster).to respond_to(:su)
32
33
  expect(cluster).to respond_to(:chdir)
33
34
  expect(cluster).to respond_to(:cd)
34
35
  expect(cluster).to respond_to(:disconnect)
36
+ expect(cluster).to respond_to(:parallel)
37
+ expect(cluster).to respond_to(:concurrency)
38
+ expect(cluster).to respond_to(:parallel?)
39
+ expect(cluster).to respond_to(:sequential?)
35
40
  end
36
41
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Kanrisuru::Remote::Host do
4
+ before(:all) do
5
+ StubNetwork.stub!
6
+ end
7
+
8
+ after(:all) do
9
+ StubNetwork.unstub!
10
+ end
11
+
12
+ let(:host) do
13
+ described_class.new(
14
+ host: 'localhost',
15
+ username: 'ubuntu',
16
+ keys: ['id_rsa']
17
+ )
18
+ end
19
+
20
+ it 'responds to methods' do
21
+ expect(host).to respond_to(:remote_user)
22
+ expect(host).to respond_to(:hostname)
23
+ expect(host).to respond_to(:os)
24
+ expect(host).to respond_to(:env)
25
+ expect(host).to respond_to(:template)
26
+ expect(host).to respond_to(:fstab)
27
+ expect(host).to respond_to(:chdir)
28
+ expect(host).to respond_to(:cd)
29
+ expect(host).to respond_to(:cpu)
30
+ expect(host).to respond_to(:memory)
31
+ expect(host).to respond_to(:su)
32
+ expect(host).to respond_to(:file)
33
+ expect(host).to respond_to(:ssh)
34
+ expect(host).to respond_to(:proxy)
35
+ expect(host).to respond_to(:ping?)
36
+ expect(host).to respond_to(:execute)
37
+ expect(host).to respond_to(:execute_shell)
38
+ expect(host).to respond_to(:disconnect)
39
+ end
40
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kanrisuru
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.19.3
4
+ version: 1.0.0.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Mammina
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-19 00:00:00.000000000 Z
11
+ date: 2022-02-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parallel_tests
@@ -136,6 +136,20 @@ dependencies:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
138
  version: '6.1'
139
+ - !ruby/object:Gem::Dependency
140
+ name: net-ssh-gateway
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '2.0'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '2.0'
139
153
  description: " Kanrisuru helps manage remote servers with objected oriented ruby.
140
154
  \n Results come back as structured data, parsed, prepared and ready.\n"
141
155
  email: ryan@avamia.com
@@ -426,6 +440,7 @@ files:
426
440
  - lib/kanrisuru/os_package/collection.rb
427
441
  - lib/kanrisuru/os_package/define.rb
428
442
  - lib/kanrisuru/os_package/include.rb
443
+ - lib/kanrisuru/processor_count.rb
429
444
  - lib/kanrisuru/remote.rb
430
445
  - lib/kanrisuru/remote/cluster.rb
431
446
  - lib/kanrisuru/remote/cpu.rb
@@ -472,6 +487,7 @@ files:
472
487
  - spec/functional/core/user_spec.rb
473
488
  - spec/functional/core/yum_spec.rb
474
489
  - spec/functional/core/zypper_spec.rb
490
+ - spec/functional/processor_count_spec.rb
475
491
  - spec/functional/remote/cluster_spec.rb
476
492
  - spec/functional/remote/cpu_spec.rb
477
493
  - spec/functional/remote/env_spec.rb
@@ -588,7 +604,7 @@ files:
588
604
  - spec/integration/core/zypper/opensuse_spec.rb
589
605
  - spec/integration/core/zypper/sles_spec.rb
590
606
  - spec/integration/os_package_spec.rb
591
- - spec/integration/remote/cluster/ubuntu_spec.rb
607
+ - spec/integration/remote/cluster/cluster_spec.rb
592
608
  - spec/integration/remote/cpu/centos_spec.rb
593
609
  - spec/integration/remote/cpu/debian_spec.rb
594
610
  - spec/integration/remote/cpu/fedora_spec.rb
@@ -656,7 +672,6 @@ files:
656
672
  - spec/support/shared_examples/integration/core/user.rb
657
673
  - spec/support/shared_examples/integration/core/yum.rb
658
674
  - spec/support/shared_examples/integration/core/zypper.rb
659
- - spec/support/shared_examples/integration/remote/cluster.rb
660
675
  - spec/support/shared_examples/integration/remote/cpu.rb
661
676
  - spec/support/shared_examples/integration/remote/env_spec.rb
662
677
  - spec/support/shared_examples/integration/remote/fstab.rb
@@ -683,10 +698,12 @@ files:
683
698
  - spec/unit/core/zypper_spec.rb
684
699
  - spec/unit/kanrisuru_spec.rb
685
700
  - spec/unit/mode_spec.rb
701
+ - spec/unit/processor_count_spec.rb
686
702
  - spec/unit/remote/cluster_spec.rb
687
703
  - spec/unit/remote/cpu_spec.rb
688
704
  - spec/unit/remote/env_spec.rb
689
705
  - spec/unit/remote/fstab_spec.rb
706
+ - spec/unit/remote/host_spec.rb
690
707
  - spec/unit/template_spec.rb
691
708
  - spec/unit/util_spec.rb
692
709
  homepage: https://kanrisuru.com
@@ -707,9 +724,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
707
724
  version: 2.5.0
708
725
  required_rubygems_version: !ruby/object:Gem::Requirement
709
726
  requirements:
710
- - - ">="
727
+ - - ">"
711
728
  - !ruby/object:Gem::Version
712
- version: '0'
729
+ version: 1.3.1
713
730
  requirements: []
714
731
  rubygems_version: 3.3.5
715
732
  signing_key:
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- TestHosts.each_os(only: %w[ubuntu]) do |os_name, host_json, spec_dir|
6
- RSpec.describe Kanrisuru::Core::Disk do
7
- include_examples 'disk', os_name, host_json, spec_dir
8
- end
9
- end
@@ -1,70 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.shared_examples 'cluster' do |os_name, host_json, _spec_dir|
6
- context "with #{os_name}" do
7
- let(:host1) do
8
- Kanrisuru::Remote::Host.new(
9
- host: host_json['hostname'],
10
- username: host_json['username'],
11
- keys: [host_json['ssh_key']]
12
- )
13
- end
14
-
15
- let(:host2) do
16
- Kanrisuru::Remote::Host.new(
17
- host: 'localhost',
18
- username: 'ubuntu',
19
- keys: ['~/.ssh/id_rsa']
20
- )
21
- end
22
-
23
- it 'gets hostname for cluster' do
24
- cluster = described_class.new({
25
- host: 'localhost',
26
- username: 'ubuntu',
27
- keys: ['~/.ssh/id_rsa']
28
- }, {
29
- host: '127.0.0.1',
30
- username: 'ubuntu',
31
- keys: ['~/.ssh/id_rsa']
32
- })
33
-
34
- expect(cluster.hostname).to match([
35
- { host: 'localhost', result: 'ubuntu' },
36
- { host: '127.0.0.1', result: 'ubuntu' }
37
- ])
38
-
39
- cluster.disconnect
40
- end
41
-
42
- it 'can ping host cluster' do
43
- cluster = described_class.new({
44
- host: 'localhost',
45
- username: 'ubuntu',
46
- keys: ['~/.ssh/id_rsa']
47
- }, {
48
- host: '127.0.0.1',
49
- username: 'ubuntu',
50
- keys: ['~/.ssh/id_rsa']
51
- })
52
-
53
- expect(cluster.ping?).to match([
54
- { host: 'localhost', result: true },
55
- { host: '127.0.0.1', result: true }
56
- ])
57
-
58
- cluster.disconnect
59
- end
60
-
61
- it 'disconnects all hosts' do
62
- cluster = described_class.new(host1, host2)
63
- cluster.disconnect
64
-
65
- cluster.each do |host|
66
- expect(host.ssh.closed).to be_truthy
67
- end
68
- end
69
- end
70
- end