luna_scanner 0.0.3
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.
- checksums.yaml +15 -0
- data/.gitignore +18 -0
- data/Changelog.txt +10 -0
- data/Gemfile +4 -0
- data/INSTALL +21 -0
- data/LICENSE.txt +22 -0
- data/README.md +99 -0
- data/Rakefile +14 -0
- data/bin/luna_scanner +7 -0
- data/lib/luna_scanner.rb +66 -0
- data/lib/luna_scanner/cli.rb +159 -0
- data/lib/luna_scanner/device.rb +132 -0
- data/lib/luna_scanner/logger.rb +32 -0
- data/lib/luna_scanner/rcommand.rb +128 -0
- data/lib/luna_scanner/scanner.rb +156 -0
- data/lib/luna_scanner/update_firmware.sh +273 -0
- data/lib/luna_scanner/util.rb +64 -0
- data/lib/luna_scanner/version.rb +3 -0
- data/lib/luna_scanner/web.rb +98 -0
- data/luna_scanner.gemspec +29 -0
- data/test/device_test.rb +77 -0
- data/test/local_ip_test.rb +14 -0
- data/test/logger_test.rb +16 -0
- data/test/luna_scanner_test.rb +21 -0
- data/test/test_helper.rb +11 -0
- data/test/web_test.rb +2 -0
- metadata +147 -0
@@ -0,0 +1,132 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
require 'csv'
|
3
|
+
|
4
|
+
module LunaScanner
|
5
|
+
class Device
|
6
|
+
attr_accessor :ip, :sn, :model, :dialno, :version, :to_change_ip
|
7
|
+
IP_REGEX = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/
|
8
|
+
|
9
|
+
def initialize(options={})
|
10
|
+
# ip, sn, model, version, to_change_ip=nil
|
11
|
+
@ip = options[:ip] || ""
|
12
|
+
@sn = options[:sn] || ""
|
13
|
+
@model = options[:model] || ""
|
14
|
+
@version = options[:version] || ""
|
15
|
+
@to_change_ip = options[:to_change_ip]
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.display_header
|
19
|
+
"-----SN--------IP-------------MODEL------VERSION------"
|
20
|
+
end
|
21
|
+
|
22
|
+
def ip_from_sn
|
23
|
+
raise "SN not valid. 8 numbers." if self.sn !~ /^\d{8}$/
|
24
|
+
|
25
|
+
sn_integer = self.sn.to_i
|
26
|
+
ip = "8.128.#{2+sn_integer/250}.#{sn_integer%250}"
|
27
|
+
ip
|
28
|
+
end
|
29
|
+
|
30
|
+
def new_ip
|
31
|
+
ip_template = <<TXT
|
32
|
+
# and how to activate them. For more information, see interfaces(5).
|
33
|
+
|
34
|
+
# The loopback network interface
|
35
|
+
auto lo
|
36
|
+
iface lo inet loopback
|
37
|
+
|
38
|
+
#enable this if you are using 100Mbps
|
39
|
+
auto eth0
|
40
|
+
allow-hotplug eth0
|
41
|
+
iface eth0 inet static
|
42
|
+
address 0.0.0.0
|
43
|
+
|
44
|
+
auto eth1
|
45
|
+
allow-hotplug eth1
|
46
|
+
iface eth1 inet static
|
47
|
+
address #{ip_from_sn}
|
48
|
+
netmask 255.255.252.0
|
49
|
+
gateway 8.128.3.254
|
50
|
+
TXT
|
51
|
+
ip_template
|
52
|
+
end
|
53
|
+
|
54
|
+
def write_ip_to_config
|
55
|
+
File.open("/tmp/interfaces", "w") do |f|
|
56
|
+
f.puts self.new_ip
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def display
|
61
|
+
" #{sn} #{ip.ljust(15)} #{model.ljust(8)} #{version}" # two space
|
62
|
+
end
|
63
|
+
|
64
|
+
def dev?
|
65
|
+
sn.start_with?("f")
|
66
|
+
end
|
67
|
+
|
68
|
+
def ==(other)
|
69
|
+
return false if not other.is_a?(Device)
|
70
|
+
|
71
|
+
self.sn == other.sn
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.load_from_file(file_name)
|
75
|
+
if file_name && file_name.to_s.end_with?(".csv")
|
76
|
+
self.validate_change_ip_csv_file!(file_name)
|
77
|
+
|
78
|
+
devices = Array.new
|
79
|
+
CSV.foreach(file_name) do |row|
|
80
|
+
# sn,changed_ip
|
81
|
+
devices << Device.new({
|
82
|
+
:ip => nil, :sn => row[0], :model => nil, :version => nil, :to_change_ip => row[1]
|
83
|
+
})
|
84
|
+
end
|
85
|
+
return devices
|
86
|
+
end
|
87
|
+
|
88
|
+
ip_file = File.read(file_name)
|
89
|
+
devices = Array.new
|
90
|
+
|
91
|
+
ip_file.each_line do |ip_line|
|
92
|
+
temp = ip_line.split(",")
|
93
|
+
if temp.size < 5
|
94
|
+
raise "ip file not valid, please check."
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
ip_file.each_line do |ip_line|
|
99
|
+
temp = ip_line.split(",")
|
100
|
+
#f.puts "#{device.ip},#{device.sn},#{device.model},#{device.version},#{device.ip_from_sn}"
|
101
|
+
devices << Device.new(:ip => temp[0], :sn => temp[1], :model => temp[2], :version => temp[3], :to_change_ip => temp[4]) if temp[1] && !temp[1].start_with?("f")
|
102
|
+
end
|
103
|
+
devices
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.validate_change_ip_csv_file!(file_name)
|
107
|
+
return false if file_name.nil? || !file_name.to_s.end_with?(".csv")
|
108
|
+
|
109
|
+
changed_sn = Array.new
|
110
|
+
changed_ip = Array.new
|
111
|
+
CSV.foreach(file_name) do |row|
|
112
|
+
abort "SN is blank!" if row[0].to_s.strip.length == 0
|
113
|
+
abort "SN length in file #{file_name} should be 8" if row[0].to_s.strip.length != 8
|
114
|
+
changed_sn << row[0].to_s.strip
|
115
|
+
|
116
|
+
abort "IP is blank!" if row[1].to_s.strip.length == 0
|
117
|
+
abort "IP #{row[1]} in file #{file_name} is an invalid ip string!" if row[1].to_s !~ IP_REGEX
|
118
|
+
|
119
|
+
changed_ip << row[1].to_s.strip
|
120
|
+
end
|
121
|
+
|
122
|
+
if changed_sn.uniq.size != changed_sn.size
|
123
|
+
abort "SN duplicated in file #{file_name}"
|
124
|
+
end
|
125
|
+
|
126
|
+
if changed_ip.uniq.size != changed_ip.size
|
127
|
+
abort "IP duplicated in file #{file_name}"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'net/ssh'
|
3
|
+
|
4
|
+
module LunaScanner
|
5
|
+
class Logger
|
6
|
+
class << self
|
7
|
+
def info message, options={}
|
8
|
+
if options[:time] == false
|
9
|
+
$stderr.puts "\r#{message.to_s}"
|
10
|
+
else
|
11
|
+
$stderr.puts "\r#{Time.now.strftime("%H:%M:%S")} > #{message.to_s}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def success message, options={}
|
16
|
+
if options[:time] == false
|
17
|
+
$stdout.puts "\r\e[32m#{message.to_s}\e[0m"
|
18
|
+
else
|
19
|
+
$stdout.puts "\r#{Time.now.strftime("%H:%M:%S")} > \e[32m#{message.to_s}\e[0m"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def error message, options={}
|
24
|
+
if options[:time] == false
|
25
|
+
$stderr.puts "\r\e[33m#{message.to_s}\e[0m"
|
26
|
+
else
|
27
|
+
$stderr.puts "\r#{Time.now.strftime("%H:%M:%S")} > \e[33m#{message.to_s}\e[0m"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
require 'erb'
|
4
|
+
require 'net/ssh'
|
5
|
+
require 'net/scp'
|
6
|
+
|
7
|
+
module LunaScanner
|
8
|
+
class Rcommand # mean remote command execute.
|
9
|
+
|
10
|
+
@@ip_max_length = 7 #1.1.1.1
|
11
|
+
@@total_devices_count = 0
|
12
|
+
@@success_devices_count = 0
|
13
|
+
|
14
|
+
def reboot!(ip)
|
15
|
+
return false if ip.nil?
|
16
|
+
|
17
|
+
Logger.info "Try to reboot #{ip} ..."
|
18
|
+
begin
|
19
|
+
Net::SSH.start(
|
20
|
+
"#{ip}", 'root',
|
21
|
+
:auth_methods => ["publickey"],
|
22
|
+
:user_known_hosts_file => "/dev/null",
|
23
|
+
:timeout => 3,
|
24
|
+
:keys => [ "#{LunaScanner.root}/keys/yu_pri" ] # Fix key permission: chmod g-wr ./yu_pri chmod o-wr ./yu_pri chmod u-w ./yu_pri
|
25
|
+
) do |session|
|
26
|
+
session.exec!("reboot")
|
27
|
+
end
|
28
|
+
rescue
|
29
|
+
Logger.error " #{ip} no response. #{$!.message}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def change_ip(connect_device, is_reboot)
|
34
|
+
if connect_device.ip == ''
|
35
|
+
Logger.error "Device #{connect_device.sn} not found in LAN."
|
36
|
+
return false
|
37
|
+
end
|
38
|
+
|
39
|
+
begin
|
40
|
+
LunaScanner.start_ssh(connect_device.ip) do |shell|
|
41
|
+
Logger.info "Changed device #{connect_device.sn} [#{connect_device.ip.ljust(@@ip_max_length)}] to new ip #{connect_device.to_change_ip.ljust(15)}"
|
42
|
+
shell.exec!("echo '#{connect_device.new_ip}' > /etc/network/interfaces")
|
43
|
+
shell.exec!("reboot") if is_reboot
|
44
|
+
end
|
45
|
+
rescue
|
46
|
+
Logger.error "Failed to change device #{connect_device.sn} to new ip #{connect_device.new_ip.ljust(15)} #Error reason: #{$!.message}"
|
47
|
+
return false
|
48
|
+
else
|
49
|
+
return true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.batch_change_ip(options={})
|
54
|
+
target_devices = Device.load_from_file(options[:input_ip])
|
55
|
+
@@total_devices_count = target_devices.size
|
56
|
+
|
57
|
+
Logger.info "-> Start batch change ip.", :time => false
|
58
|
+
|
59
|
+
thread_pool = []
|
60
|
+
10.times do
|
61
|
+
ssh_thread = Thread.new do
|
62
|
+
go = true
|
63
|
+
while go
|
64
|
+
device = target_devices.pop
|
65
|
+
if device.nil?
|
66
|
+
go = false
|
67
|
+
else
|
68
|
+
rcommand = self.new
|
69
|
+
if rcommand.change_ip(device, options[:reboot])
|
70
|
+
@@success_devices_count += 1
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
thread_pool << ssh_thread
|
77
|
+
end
|
78
|
+
|
79
|
+
thread_pool.each{|thread| thread.join }
|
80
|
+
|
81
|
+
Logger.info "\n#{@@success_devices_count}/#{@@total_devices_count} devices changed.", :time => false
|
82
|
+
Logger.info "Restart all devices to make changes work.", :time => false if !options[:reboot]
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.batch_update(options={})
|
86
|
+
update_script_file = File.expand_path("../update_firmware.sh", __FILE__)
|
87
|
+
target_devices = Device.load_from_file(options[:input_ip])
|
88
|
+
Logger.info "-> Start batch update.", :time => false
|
89
|
+
|
90
|
+
thread_pool = []
|
91
|
+
10.times do
|
92
|
+
ssh_thread = Thread.new do
|
93
|
+
go = true
|
94
|
+
while go
|
95
|
+
device = target_devices.pop
|
96
|
+
if device.nil?
|
97
|
+
go = false
|
98
|
+
else
|
99
|
+
begin
|
100
|
+
|
101
|
+
Logger.info "-> Update #{device.ip} #{device.sn}"
|
102
|
+
LunaScanner.start_ssh(device.ip) do |shell|
|
103
|
+
shell.scp.upload!("#{update_script_file}", "/usr/local/luna-client/script/update_firmware.sh")
|
104
|
+
shell.exec!("chmod a+x /usr/local/luna-client/script/update_firmware.sh")
|
105
|
+
shell.exec!("sed -i 's/iface eth0 inet dhcp/iface eth0 inet static\naddress 0.0.0.0/' /etc/network/interfaces")
|
106
|
+
shell.exec!("/usr/local/luna-client/script/update_firmware.sh http://8.128.3.254 1500k")
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
rescue
|
111
|
+
Logger.error "Failed to update device #{device.sn} #{device.ip} #{$!.message}"
|
112
|
+
# return false
|
113
|
+
else
|
114
|
+
# return true
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
thread_pool << ssh_thread
|
121
|
+
end
|
122
|
+
|
123
|
+
thread_pool.each{|thread| thread.join }
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'net/ssh'
|
3
|
+
|
4
|
+
module LunaScanner
|
5
|
+
class Scanner
|
6
|
+
|
7
|
+
@@found_devices = Array.new
|
8
|
+
|
9
|
+
def initialize(thread_size, start_ip, end_ip)
|
10
|
+
raise "thread pool size not correct!" if thread_size.to_i <= 0
|
11
|
+
@thread_size = thread_size.to_i
|
12
|
+
Logger.info "Local ip #{LunaScanner.local_ip}"
|
13
|
+
|
14
|
+
@scan_ip_range = Util.ip_range(start_ip, end_ip)
|
15
|
+
end
|
16
|
+
|
17
|
+
def generate_ip_range(options)
|
18
|
+
if options[:input_ip].to_s.length > 0 # get ip range from file
|
19
|
+
raise "IP input file not exist." if !File.exist?(options[:input_ip])
|
20
|
+
|
21
|
+
|
22
|
+
else # get ip range from start_ip to end_ip
|
23
|
+
if options[:start_ip].to_s.length == 0 || options[:end_ip].to_s.length == 0
|
24
|
+
raise "Require arguments to get ip range"
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
def scan(is_reboot, shell_command)
|
33
|
+
thread_pool = []
|
34
|
+
@scan_ip_range.reverse!
|
35
|
+
@thread_size.times do
|
36
|
+
ssh_thread = Thread.new do
|
37
|
+
go = true
|
38
|
+
while go
|
39
|
+
ip = @scan_ip_range.pop
|
40
|
+
if ip.nil?
|
41
|
+
go = false
|
42
|
+
else
|
43
|
+
begin
|
44
|
+
Logger.info "Scan ip #{ip} ..."
|
45
|
+
LunaScanner.start_ssh(ip) do |shell|
|
46
|
+
sn = shell.exec!('cat /proc/itc_sn/sn').chomp
|
47
|
+
model = shell.exec!('cat /proc/itc_sn/model').chomp
|
48
|
+
version = shell.exec!('cat /jenkins_version.txt').chomp
|
49
|
+
|
50
|
+
if shell_command.to_s.length > 0
|
51
|
+
Logger.info " execute command: #{shell_command} ...", :time => false
|
52
|
+
shell.exec!("#{shell_command}")
|
53
|
+
end
|
54
|
+
|
55
|
+
if sn != "cat: /proc/itc_sn/sn: No such file or directory"
|
56
|
+
@@found_devices << Device.new(:ip => ip, :sn => sn, :model => model, :version => version)
|
57
|
+
Logger.success " #{ip} #{sn} #{model} #{version}", :time => false
|
58
|
+
shell.exec!("reboot") if is_reboot
|
59
|
+
end
|
60
|
+
end
|
61
|
+
rescue
|
62
|
+
Logger.error " #{ip} no response. #{$!.message}", :time => false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
thread_pool << ssh_thread
|
69
|
+
end
|
70
|
+
|
71
|
+
thread_pool.each{|thread| thread.join }
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.scan!(options={})
|
75
|
+
start_ip = options[:start_ip] || Util.begin_ip(LunaScanner.local_ip)
|
76
|
+
end_ip = options[:end_ip] || Util.end_ip(LunaScanner.local_ip)
|
77
|
+
scanner = self.new(options[:thread_size], start_ip, end_ip)
|
78
|
+
|
79
|
+
Logger.info "Start scan from #{start_ip} to #{end_ip} #{options[:reboot] ? '(reboot)' : ''} ..."
|
80
|
+
scanner.scan(options[:reboot], options[:command])
|
81
|
+
|
82
|
+
Logger.info "\n#{Device.display_header}", :time => false
|
83
|
+
@@found_devices.each do |device|
|
84
|
+
Logger.success device.display, :time => false
|
85
|
+
end
|
86
|
+
Logger.info "\n#{@@found_devices.size} devices found. #{options[:reboot] ? '(reboot)' : ''}", :time => false
|
87
|
+
if options[:result]
|
88
|
+
begin
|
89
|
+
File.open(options[:result], "w") do |f|
|
90
|
+
@@found_devices.each do |device|
|
91
|
+
f.puts "#{device.ip},#{device.sn},#{device.model},#{device.version},#{device.ip_from_sn}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
rescue
|
95
|
+
Logger.error "Failed to write scan result to #{options[:result]}", :time => false
|
96
|
+
else
|
97
|
+
Logger.error "Write scan result to #{options[:result]}", :time => false
|
98
|
+
end
|
99
|
+
end
|
100
|
+
Logger.info "\n", :time => false
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.upload!(source_file, target_file, options={}, &block)
|
104
|
+
require 'net/scp'
|
105
|
+
if options[:input_ip] # Scan from given input ip file.
|
106
|
+
source_devices = File.read(options[:input_ip])
|
107
|
+
upload_hosts = Array.new
|
108
|
+
source_devices.each_line do |device|
|
109
|
+
ip,sn,model,version = device.split(" ")
|
110
|
+
upload_hosts << ip
|
111
|
+
end
|
112
|
+
upload_hosts.uniq!
|
113
|
+
|
114
|
+
return if upload_hosts.size == 0
|
115
|
+
|
116
|
+
thread_pool = []
|
117
|
+
options[:thread_size].times do
|
118
|
+
ssh_thread = Thread.new do
|
119
|
+
go = true
|
120
|
+
while go
|
121
|
+
ip = upload_hosts.pop
|
122
|
+
if ip.nil?
|
123
|
+
go = false
|
124
|
+
else
|
125
|
+
begin
|
126
|
+
Logger.info "Connect to ip #{ip} ..."
|
127
|
+
LunaScanner.start_ssh(ip) do |shell|
|
128
|
+
Logger.info " upload file #{source_file} to #{ip} #{target_file}", :time => false
|
129
|
+
shell.scp.upload!(source_file, target_file)
|
130
|
+
block.call(shell) if block
|
131
|
+
end
|
132
|
+
rescue
|
133
|
+
Logger.error " #{ip} not connected. #{$!.message}"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
thread_pool << ssh_thread
|
140
|
+
end
|
141
|
+
|
142
|
+
thread_pool.each{|thread| thread.join }
|
143
|
+
|
144
|
+
else
|
145
|
+
# self.scan!(options)
|
146
|
+
puts "Not implement yet."
|
147
|
+
exit 4
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.found_devices
|
152
|
+
@@found_devices
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
end
|