luna_scanner 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|