smart_proxy_onboard 0.1.1 → 0.2.1
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 +5 -5
- data/.gitignore +5 -1
- data/.rubocop.yml +11 -35
- data/.rubocop_todo.yml +7 -508
- data/.travis.yml +2 -2
- data/Gemfile +6 -1
- data/README.md +51 -11
- data/lib/bmc/basescanner.rb +13 -13
- data/lib/bmc/ipmiscanner.rb +16 -23
- data/lib/bmc/sdr_cache.rb +68 -0
- data/lib/smart_proxy_onboard/api_bmc.rb +51 -37
- data/lib/smart_proxy_onboard/http_config.ru +1 -1
- data/lib/smart_proxy_onboard/onboard.rb +12 -10
- data/lib/smart_proxy_onboard/version.rb +1 -1
- data/smart_proxy_onboard.gemspec +6 -6
- metadata +31 -31
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -33,7 +33,8 @@ This plugin is made for the following combination of software:
|
|
33
33
|
|
34
34
|
### Currently Implemented
|
35
35
|
|
36
|
-
-
|
36
|
+
- Scan IP ranges for IPMI appliances
|
37
|
+
- Clear FreeIPMI sensor data repository (SDR) cache
|
37
38
|
|
38
39
|
### Planned
|
39
40
|
|
@@ -41,9 +42,9 @@ No additional features have been planned so far.
|
|
41
42
|
|
42
43
|
## API
|
43
44
|
|
44
|
-
### `GET /bmc/scan`
|
45
|
+
### `GET /onboard/bmc/scan`
|
45
46
|
|
46
|
-
Shows available resources `/bmc/scan/range` and `/bmc/scan/cidr`
|
47
|
+
Shows available resources `/onboard/bmc/scan/range` and `/onboard/bmc/scan/cidr`
|
47
48
|
|
48
49
|
{
|
49
50
|
"available_resources": [
|
@@ -52,27 +53,27 @@ Shows available resources `/bmc/scan/range` and `/bmc/scan/cidr`
|
|
52
53
|
]
|
53
54
|
}
|
54
55
|
|
55
|
-
### `GET /bmc/scan/range`
|
56
|
+
### `GET /onboard/bmc/scan/range`
|
56
57
|
|
57
58
|
Shows usage on specifying a beginning IP address and an ending IP address for making a scan request
|
58
59
|
|
59
60
|
{
|
60
|
-
"message": "You need to supply a range with /bmc/scan/range/:address_first/:address_last"
|
61
|
+
"message": "You need to supply a range with /onboard/bmc/scan/range/:address_first/:address_last"
|
61
62
|
}
|
62
63
|
|
63
|
-
### `GET /bmc/scan/cidr`
|
64
|
+
### `GET /onboard/bmc/scan/cidr`
|
64
65
|
|
65
66
|
Shows usage on specifying an IP address and its netmask in dot decimal format or prefixlen format for making a scan request
|
66
67
|
|
67
68
|
{
|
68
|
-
"message": "You need to supply a CIDR with /bmc/scan/cidr/:address/:netmask (e.g. \"192.168.1.1/24\" or \"192.168.1.1/255.255.255.0\")"
|
69
|
+
"message": "You need to supply a CIDR with /onboard/bmc/scan/cidr/:address/:netmask (e.g. \"192.168.1.1/24\" or \"192.168.1.1/255.255.255.0\")"
|
69
70
|
}
|
70
71
|
|
71
|
-
### `GET /bmc/scan/range/:address_first/:address_last`
|
72
|
+
### `GET /onboard/bmc/scan/range/:address_first/:address_last`
|
72
73
|
|
73
74
|
Performs an IPMI ping scan from `:address_first` to `:address_last` and returns the result in key "`result`" of a JSON hash
|
74
75
|
|
75
|
-
Sample output for `/bmc/scan/range/10.246.0.65/10.246.0.71`:
|
76
|
+
Sample output for `/onboard/bmc/scan/range/10.246.0.65/10.246.0.71`:
|
76
77
|
|
77
78
|
{
|
78
79
|
"result": [
|
@@ -86,11 +87,11 @@ Sample output for `/bmc/scan/range/10.246.0.65/10.246.0.71`:
|
|
86
87
|
]
|
87
88
|
}
|
88
89
|
|
89
|
-
### `GET /bmc/scan/cidr/:address/:netmask`
|
90
|
+
### `GET /onboard/bmc/scan/cidr/:address/:netmask`
|
90
91
|
|
91
92
|
Performs an IPMI ping scan in the CIDR range of `:address`/`:netmask`, where `:netmask` is in decimal format (e.g. "`255.255.255.0`") or in prefixlen format (e.g. "`24`")
|
92
93
|
|
93
|
-
Sample output for `/bmc/scan/cidr/10.246.0.65/29`:
|
94
|
+
Sample output for `/onboard/bmc/scan/cidr/10.246.0.65/29`:
|
94
95
|
|
95
96
|
{
|
96
97
|
"result": [
|
@@ -103,3 +104,42 @@ Sample output for `/bmc/scan/cidr/10.246.0.65/29`:
|
|
103
104
|
"10.246.0.71"
|
104
105
|
]
|
105
106
|
}
|
107
|
+
|
108
|
+
### `DELETE /onboard/bmc/sdr_cache`
|
109
|
+
|
110
|
+
(_FreeIPMI only_) Deletes the sensor data repository (SDR) cache from the Smart Proxy. This is useful when you run something like `GET /bmc/10.246.0.69/fru/list` with `bmc_provider=freeipmi` and you get a reply like this:
|
111
|
+
|
112
|
+
{
|
113
|
+
"action": "list",
|
114
|
+
"result": {
|
115
|
+
"": {
|
116
|
+
"sdr_cache_'/tmp/.freeipmi-foreman-proxy/.freeipmi/sdr-cache/sdr-cache-smartproxyhostname.10.246.0.69'_invalid": "Please flush the cache and regenerate it"
|
117
|
+
}
|
118
|
+
}
|
119
|
+
}
|
120
|
+
|
121
|
+
Instead of manually logging in to the Smart Proxy and deleting `/tmp/.freeipmi-foreman-proxy/.freeipmi/sdr-cache`, just run this method, and you'll get this reply:
|
122
|
+
|
123
|
+
{
|
124
|
+
"result": true,
|
125
|
+
"message": "SDR cache deleted"
|
126
|
+
}
|
127
|
+
|
128
|
+
If there's no SDR cache present, you'll see:
|
129
|
+
|
130
|
+
{
|
131
|
+
"result": true,
|
132
|
+
"message": "No SDR cache to delete"
|
133
|
+
}
|
134
|
+
|
135
|
+
If the deletion failed, you'll see something like this:
|
136
|
+
|
137
|
+
{
|
138
|
+
"errors": [
|
139
|
+
"Permission denied @ dir_s_rmdir - /tmp/.freeipmi-foreman-proxy/.freeipmi/sdr-cache"
|
140
|
+
],
|
141
|
+
"result": false,
|
142
|
+
"message": "Failed to delete one or more SDR cache location candidates"
|
143
|
+
}
|
144
|
+
|
145
|
+
As long as `{"result":true}`, you will not encounter the `sdr_cache_…_invalid` error next time.
|
data/lib/bmc/basescanner.rb
CHANGED
@@ -6,7 +6,7 @@ module Proxy
|
|
6
6
|
module BMC
|
7
7
|
# This is the interface for scanning BMC IP address ranges.
|
8
8
|
class BaseScanner
|
9
|
-
def initialize(args = {
|
9
|
+
def initialize(args = {})
|
10
10
|
if args.key? :address_first
|
11
11
|
address_first = IPAddr.new args[:address_first]
|
12
12
|
address_last = IPAddr.new args[:address_last]
|
@@ -18,34 +18,34 @@ module Proxy
|
|
18
18
|
end
|
19
19
|
# Disallow range too large
|
20
20
|
scanner_max_range_size = max_range_size
|
21
|
-
if @range.first(scanner_max_range_size+1).size > scanner_max_range_size
|
21
|
+
if @range.first(scanner_max_range_size + 1).size > scanner_max_range_size
|
22
22
|
@range = nil
|
23
23
|
@invalid_reason = "Range too large. Batch supports only #{scanner_max_range_size} IP addresses at a time."
|
24
24
|
end
|
25
|
-
rescue
|
25
|
+
rescue StandardError
|
26
26
|
@range = nil
|
27
|
-
if args.is_a?(Hash) && args.key?(:address)
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
27
|
+
@invalid_reason = if args.is_a?(Hash) && args.key?(:address)
|
28
|
+
'Invalid CIDR provided'
|
29
|
+
else
|
30
|
+
'Invalid IP address provided'
|
31
|
+
end
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
def valid?
|
35
35
|
@range.is_a? Range
|
36
36
|
end
|
37
|
-
|
37
|
+
|
38
38
|
def error_string
|
39
39
|
@invalid_reason
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
def max_range_size
|
43
43
|
Proxy::Onboard::Plugin.settings.bmc_scanner_max_range_size || 65_536
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
46
|
# Run the scanner and return results as array
|
47
47
|
def scan_to_list
|
48
|
-
raise NotImplementedError
|
48
|
+
raise NotImplementedError
|
49
49
|
end
|
50
50
|
end
|
51
51
|
end
|
data/lib/bmc/ipmiscanner.rb
CHANGED
@@ -7,13 +7,13 @@ module Proxy
|
|
7
7
|
class IPMIScanner < BaseScanner
|
8
8
|
include Proxy::Log
|
9
9
|
include Proxy::Util
|
10
|
-
|
10
|
+
|
11
11
|
# This is an IPMI ping, not an ICMP ping.
|
12
12
|
def address_pings?(address)
|
13
13
|
begin
|
14
14
|
socket = UDPSocket.new
|
15
15
|
rescue Errno::EMFILE
|
16
|
-
logger.warn
|
16
|
+
logger.warn 'IPMIScanner: Ran out of free file descriptors while creating UDPSocket! Consider increasing file open limit.'
|
17
17
|
retry
|
18
18
|
end
|
19
19
|
socket.connect(address.to_s, 623)
|
@@ -22,59 +22,52 @@ module Proxy
|
|
22
22
|
socket.close
|
23
23
|
!selections.nil?
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
# Not used because of slowness
|
27
27
|
def scan_unthreaded_to_list
|
28
|
-
return false
|
29
|
-
pinged =
|
28
|
+
return false unless valid?
|
29
|
+
pinged = []
|
30
30
|
@range.each do |address|
|
31
|
-
if address_pings?(address)
|
32
|
-
pinged << address
|
33
|
-
end
|
31
|
+
pinged << address if address_pings?(address)
|
34
32
|
end
|
35
33
|
pinged
|
36
34
|
end
|
37
|
-
|
35
|
+
|
38
36
|
# Determine maximum number of threads
|
39
37
|
def calculate_max_threads
|
40
38
|
max_threads = Proxy::Onboard::Plugin.settings.bmc_scanner_max_threads_per_request || 500
|
41
39
|
begin
|
42
|
-
sockets =
|
40
|
+
sockets = []
|
43
41
|
# @range.first(max_threads).size performs much better than @range.count if @range is large.
|
44
42
|
(1..[max_threads, @range.first(max_threads).size].min).each do
|
45
|
-
|
46
|
-
sockets << socket
|
43
|
+
sockets << UDPSocket.new
|
47
44
|
end
|
48
45
|
rescue Errno::EMFILE
|
49
46
|
# Running low on free file descriptors; only allow use of half of the remaining
|
50
47
|
max_threads = [sockets.length / 2, 1].max
|
51
48
|
# Clean up sockets
|
52
|
-
sockets.each
|
53
|
-
sock.close
|
54
|
-
end
|
49
|
+
sockets.each(&:close)
|
55
50
|
logger.warn "IPMIScanner: Running low on free file descriptors! Can only allocate #{sockets.length}, so using #{max_threads} to avoid hitting the limit."
|
56
51
|
end
|
57
52
|
max_threads
|
58
53
|
end
|
59
|
-
|
54
|
+
|
60
55
|
def scan_threaded_to_list
|
61
|
-
return false
|
62
|
-
pinged =
|
56
|
+
return false unless valid?
|
57
|
+
pinged = []
|
63
58
|
pool = Concurrent::ThreadPoolExecutor.new(max_threads: calculate_max_threads)
|
64
59
|
@range.each do |address|
|
65
60
|
pool.post do
|
66
|
-
if address_pings?(address)
|
67
|
-
pinged << address
|
68
|
-
end
|
61
|
+
pinged << address if address_pings?(address)
|
69
62
|
end
|
70
63
|
end
|
71
64
|
pool.shutdown
|
72
65
|
pool.wait_for_termination
|
73
66
|
pinged
|
74
67
|
end
|
75
|
-
|
68
|
+
|
76
69
|
def scan_to_list
|
77
|
-
return false
|
70
|
+
return false unless valid?
|
78
71
|
scan_threaded_to_list
|
79
72
|
end
|
80
73
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'smart_proxy_onboard'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module Proxy
|
5
|
+
module Onboard
|
6
|
+
module BMC
|
7
|
+
class SDRCache
|
8
|
+
include Proxy::Log
|
9
|
+
include Proxy::Util
|
10
|
+
|
11
|
+
# Determined from
|
12
|
+
# https://git.savannah.gnu.org/cgit/freeipmi.git/tree/common/toolcommon/tool-sdr-cache-common.c?h=Release-1_4_11
|
13
|
+
def possible_paths
|
14
|
+
paths = []
|
15
|
+
sdr_cache_directory = sdr_cache_directory_from_freeipmi_conf
|
16
|
+
paths << sdr_cache_directory unless sdr_cache_directory.nil?
|
17
|
+
etc = Etc.getpwuid(Process.uid)
|
18
|
+
paths + [
|
19
|
+
"#{etc.dir}/.freeipmi/sdr-cache",
|
20
|
+
"/tmp/.freeipmi-#{etc.name}/.freeipmi/sdr-cache"
|
21
|
+
]
|
22
|
+
end
|
23
|
+
|
24
|
+
def existing_possible_paths
|
25
|
+
paths = []
|
26
|
+
possible_paths.each do |path|
|
27
|
+
paths << path if File.exist?(path)
|
28
|
+
end
|
29
|
+
paths
|
30
|
+
end
|
31
|
+
|
32
|
+
def present?
|
33
|
+
!existing_possible_paths.empty?
|
34
|
+
end
|
35
|
+
|
36
|
+
def delete
|
37
|
+
errors = []
|
38
|
+
existing_possible_paths.each do |path|
|
39
|
+
begin
|
40
|
+
FileUtils.remove_entry_secure(path)
|
41
|
+
rescue Errno::ENOENT
|
42
|
+
next
|
43
|
+
rescue SystemCallError => e
|
44
|
+
errors << e
|
45
|
+
next
|
46
|
+
end
|
47
|
+
end
|
48
|
+
return { errors: errors } unless errors.empty?
|
49
|
+
true
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def sdr_cache_directory_from_freeipmi_conf
|
55
|
+
freeipmi_conf_handle = File.open('/etc/freeipmi/freeipmi.conf', 'r')
|
56
|
+
match = %r{^[[:blank:]]*sdr-cache-directory[[:blank:]]+\/}
|
57
|
+
freeipmi_conf_handle.grep(match).last.gsub(match, '/').gsub(/\n$/, '')
|
58
|
+
rescue SystemCallError
|
59
|
+
logger.debug 'Cannot determine sdr-cache-directory from FreeIPMI configuration file'
|
60
|
+
nil
|
61
|
+
rescue NoMethodError
|
62
|
+
logger.debug 'FreeIPMI configuration file does not contain sdr-cache-directory'
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -1,49 +1,63 @@
|
|
1
1
|
require 'smart_proxy'
|
2
|
+
require 'bmc/ipmiscanner'
|
3
|
+
require 'bmc/sdr_cache'
|
2
4
|
|
3
|
-
module Proxy
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
# Scan IP range for BMC hosts
|
8
|
-
# Returns a list of available scanning options
|
9
|
-
get "/scan" do
|
10
|
-
{ :available_resources => %w[range cidr] }.to_json
|
11
|
-
end
|
5
|
+
module Proxy
|
6
|
+
module Onboard
|
7
|
+
class ApiBmc < Sinatra::Base
|
8
|
+
helpers ::Proxy::Helpers
|
12
9
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
10
|
+
# Scan IP range for BMC hosts
|
11
|
+
# Returns a list of available scanning options
|
12
|
+
get '/scan' do
|
13
|
+
{ available_resources: %w[range cidr] }.to_json
|
14
|
+
end
|
17
15
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
16
|
+
# Returns a helpful message that the user should supply a beginning IP and ending IP
|
17
|
+
get '/scan/range' do
|
18
|
+
{ message: 'You need to supply a range with /onboard/bmc/scan/range/:address_first/:address_last' }.to_json
|
19
|
+
end
|
22
20
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
21
|
+
# Returns a helpful message that the user should supply a CIDR
|
22
|
+
get '/scan/cidr' do
|
23
|
+
{ message: 'You need to supply a CIDR with /onboard/bmc/scan/cidr/:address/:netmask (e.g. "192.168.1.1/24" or "192.168.1.1/255.255.255.0")' }.to_json
|
24
|
+
end
|
25
|
+
|
26
|
+
['/scan/range/:address_first/?:address_last?',
|
27
|
+
'/scan/cidr/:address/?:netmask?'].each do |path|
|
28
|
+
get path do
|
29
|
+
scanner_setup
|
30
|
+
if !@scanner.valid?
|
31
|
+
{ error: @scanner.error_string }.to_json
|
32
|
+
else
|
33
|
+
{ result: @scanner.scan_to_list }.to_json
|
34
|
+
end
|
31
35
|
end
|
32
36
|
end
|
33
|
-
end
|
34
37
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
38
|
+
def scanner_setup
|
39
|
+
args = {}
|
40
|
+
# /scan/cidr/:address/:netmask
|
41
|
+
if params.key? 'address'
|
42
|
+
args = { address: params[:address],
|
43
|
+
netmask: params[:netmask] }
|
44
|
+
# /scan/range/:address_first/:address_last
|
45
|
+
elsif params.key? 'address_first'
|
46
|
+
args = { address_first: params[:address_first],
|
47
|
+
address_last: params[:address_last] }
|
48
|
+
end
|
49
|
+
@scanner = Proxy::Onboard::BMC::IPMIScanner.new(args)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Clear the SDR cache
|
53
|
+
delete '/sdr_cache' do
|
54
|
+
sdr_cache = Proxy::Onboard::BMC::SDRCache.new
|
55
|
+
return { result: true, message: 'No SDR cache to delete' }.to_json unless sdr_cache.present?
|
56
|
+
result = sdr_cache.delete
|
57
|
+
return result.merge(result: false, message: 'Failed to delete one or more SDR cache location candidates').to_json if result.is_a? Hash
|
58
|
+
return { result: true, message: 'SDR cache deleted' }.to_json if result == true
|
59
|
+
return { result: false, message: 'Unexpected response from delete function' }.to_json
|
45
60
|
end
|
46
|
-
@scanner = Proxy::Onboard::BMC::IPMIScanner.new(args)
|
47
61
|
end
|
48
62
|
end
|
49
63
|
end
|