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.
@@ -5,7 +5,7 @@ notifications:
5
5
  sudo: false
6
6
  language: ruby
7
7
  rvm:
8
- - 2.0.0
8
+ - 2.7.1
9
9
  before_install:
10
- - gem install bundler -v 1.14.3
10
+ - gem install bundler
11
11
  - mkdir ./logs
data/Gemfile CHANGED
@@ -6,5 +6,10 @@ gemspec
6
6
  group :development do
7
7
  gem 'smart_proxy',
8
8
  git: 'https://github.com/theforeman/smart-proxy.git',
9
- branch: '1.15-stable'
9
+ branch: 'develop'
10
+ end
11
+
12
+ group :test do
13
+ gem 'rubocop', require: false
14
+ gem 'simplecov', require: false
10
15
  end
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
- - IPMI IP range scanner
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.
@@ -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
- @invalid_reason = "Invalid CIDR provided"
29
- else
30
- @invalid_reason = "Invalid IP address provided"
31
- end
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.new
48
+ raise NotImplementedError
49
49
  end
50
50
  end
51
51
  end
@@ -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 "IPMIScanner: Ran out of free file descriptors while creating UDPSocket! Consider increasing file open limit."
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 if !valid?
29
- pinged = Array.new
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 = Array.new
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
- socket = UDPSocket.new
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 do |sock|
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 if !valid?
62
- pinged = Array.new
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 if !valid?
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::Onboard
4
- class ApiBmc < Sinatra::Base
5
- helpers ::Proxy::Helpers
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
- # Returns a helpful message that the user should supply a beginning IP and ending IP
14
- get "/scan/range" do
15
- { :message => "You need to supply a range with /bmc/scan/range/:address_first/:address_last"}.to_json
16
- end
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
- # Returns a helpful message that the user should supply a CIDR
19
- get "/scan/cidr" do
20
- { :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\")"}.to_json
21
- end
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
- ["/scan/range/:address_first/?:address_last?",
24
- "/scan/cidr/:address/?:netmask?"].each do |path|
25
- get path do
26
- scanner_setup
27
- if !@scanner.valid?
28
- { :error => @scanner.error_string}.to_json
29
- else
30
- { :result => @scanner.scan_to_list}.to_json
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
- def scanner_setup
36
- args = {}
37
- # /scan/cidr/:address/:netmask
38
- if params.key? "address"
39
- args = { :address => params[:address],
40
- :netmask => params[:netmask] }
41
- # /scan/range/:address_first/:address_last
42
- elsif params.key? "address_first"
43
- args = { :address_first => params[:address_first],
44
- :address_last => params[:address_last] }
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