smart_proxy_onboard 0.1.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.
data/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # Smart Proxy - Onboard
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/smart_proxy_onboard.svg)](https://badge.fury.io/rb/smart_proxy_onboard)
4
+ [![Build Status](https://travis-ci.org/Deltik/smart_proxy_onboard.svg?branch=develop)](https://travis-ci.org/Deltik/smart_proxy_onboard)
5
+ [![Code Climate](https://codeclimate.com/github/Deltik/smart_proxy_onboard/badges/gpa.svg)](https://codeclimate.com/github/Deltik/smart_proxy_onboard)
6
+
7
+ The **Smart Proxy Onboard Plugin** (**`smart_proxy_onboard`**) exposes useful API methods for the earliest part of the server lifecycle management process: onboarding new servers into [Foreman](https://github.com/theforeman/foreman).
8
+
9
+ This plugin is used for the onboarding process before server discovery. It plugs into [Smart Proxy](https://github.com/theforeman/smart-proxy) to cover the same network ranges as the Smart Proxy.
10
+
11
+ [Rails DCIM Portal](https://github.com/buddwm/Rails_DCIM_Portal) uses the API methods in this plugin to scan a range of IP addresses to see which ones respond to IPMI. Rails DCIM Portal then iterates over the list, finding IPMI credentials that work for each IP address. These IPMI hosts are then considered scanned and can subsequently be discovered into Foreman by PXE booting into the discovery image from [`foreman_discovery`](https://github.com/theforeman/foreman_discovery). Finally, while Foreman provisions the discovered hosts, Rails DCIM Portal can write inventory facts into the provisioned hosts to complete the onboarding process.
12
+
13
+ ## Installation
14
+
15
+ Follow the ["Advanced Installation from Gems" instructions in the Foreman plugins documentation](https://theforeman.org/plugins/#2.3AdvancedInstallationfromGems) to install this plugin.
16
+
17
+ The gem name is `smart_proxy_onboard`.
18
+
19
+ ## Configuration
20
+
21
+ All configurable options for this plugin are documented at [settings.d/onboard.yml.example](settings.d/onboard.yml.example) and should be copied to `/etc/foreman-proxy/settings.d/onboard.yml`.
22
+
23
+ ## Compatible Software
24
+
25
+ This plugin is made for the following combination of software:
26
+
27
+ - [**Rails DCIM Portal**](https://github.com/buddwm/Rails_DCIM_Portal)
28
+ - [**Smart Proxy**](https://github.com/theforeman/smart-proxy) (>= 1.16)
29
+ - [**Foreman - Discovery**](https://github.com/theforeman/foreman_discovery)
30
+ - [**Smart Proxy - Discovery**](https://github.com/theforeman/smart_proxy_discovery)
31
+
32
+ ## Features
33
+
34
+ ### Currently Implemented
35
+
36
+ - IPMI IP range scanner
37
+
38
+ ### Planned
39
+
40
+ No additional features have been planned so far.
41
+
42
+ ## API
43
+
44
+ ### `GET /bmc/scan`
45
+
46
+ Shows available resources `/bmc/scan/range` and `/bmc/scan/cidr`
47
+
48
+ {
49
+ "available_resources": [
50
+ "range",
51
+ "cidr"
52
+ ]
53
+ }
54
+
55
+ ### `GET /bmc/scan/range`
56
+
57
+ Shows usage on specifying a beginning IP address and an ending IP address for making a scan request
58
+
59
+ {
60
+ "message": "You need to supply a range with /bmc/scan/range/:address_first/:address_last"
61
+ }
62
+
63
+ ### `GET /bmc/scan/cidr`
64
+
65
+ Shows usage on specifying an IP address and its netmask in dot decimal format or prefixlen format for making a scan request
66
+
67
+ {
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
+ }
70
+
71
+ ### `GET /bmc/scan/range/:address_first/:address_last`
72
+
73
+ Performs an IPMI ping scan from `:address_first` to `:address_last` and returns the result in key "`result`" of a JSON hash
74
+
75
+ Sample output for `/bmc/scan/range/10.246.0.65/10.246.0.71`:
76
+
77
+ {
78
+ "result": [
79
+ "10.246.0.65",
80
+ "10.246.0.69",
81
+ "10.246.0.70",
82
+ "10.246.0.66",
83
+ "10.246.0.68",
84
+ "10.246.0.71",
85
+ "10.246.0.67"
86
+ ]
87
+ }
88
+
89
+ ### `GET /bmc/scan/cidr/:address/:netmask`
90
+
91
+ 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
+ Sample output for `/bmc/scan/cidr/10.246.0.65/29`:
94
+
95
+ {
96
+ "result": [
97
+ "10.246.0.69",
98
+ "10.246.0.70",
99
+ "10.246.0.65",
100
+ "10.246.0.66",
101
+ "10.246.0.67",
102
+ "10.246.0.68",
103
+ "10.246.0.71"
104
+ ]
105
+ }
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << 'test'
6
+ t.libs << 'lib'
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task default: :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'smart_proxy_onboard'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,53 @@
1
+ require 'smart_proxy_onboard'
2
+ require 'ipaddr'
3
+
4
+ module Proxy
5
+ module Onboard
6
+ module BMC
7
+ # This is the interface for scanning BMC IP address ranges.
8
+ class BaseScanner
9
+ def initialize(args = { })
10
+ if args.key? :address_first
11
+ address_first = IPAddr.new args[:address_first]
12
+ address_last = IPAddr.new args[:address_last]
13
+ @range = (address_first..address_last)
14
+ elsif args.key?(:address) && args.key?(:netmask)
15
+ @range = IPAddr.new("#{args[:address]}/#{args[:netmask]}").to_range
16
+ else
17
+ @range = IPAddr.new(args[:address]).to_range
18
+ end
19
+ # Disallow range too large
20
+ scanner_max_range_size = max_range_size
21
+ if @range.first(scanner_max_range_size+1).size > scanner_max_range_size
22
+ @range = nil
23
+ @invalid_reason = "Range too large. Batch supports only #{scanner_max_range_size} IP addresses at a time."
24
+ end
25
+ rescue
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
32
+ end
33
+
34
+ def valid?
35
+ @range.is_a? Range
36
+ end
37
+
38
+ def error_string
39
+ @invalid_reason
40
+ end
41
+
42
+ def max_range_size
43
+ Proxy::Onboard::Plugin.settings.bmc_scanner_max_range_size || 65_536
44
+ end
45
+
46
+ # Run the scanner and return results as array
47
+ def scan_to_list
48
+ raise NotImplementedError.new
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,83 @@
1
+ require 'bmc/basescanner'
2
+ require 'concurrent'
3
+
4
+ module Proxy
5
+ module Onboard
6
+ module BMC
7
+ class IPMIScanner < BaseScanner
8
+ include Proxy::Log
9
+ include Proxy::Util
10
+
11
+ # This is an IPMI ping, not an ICMP ping.
12
+ def address_pings?(address)
13
+ begin
14
+ socket = UDPSocket.new
15
+ rescue Errno::EMFILE
16
+ logger.warn "IPMIScanner: Ran out of free file descriptors while creating UDPSocket! Consider increasing file open limit."
17
+ retry
18
+ end
19
+ socket.connect(address.to_s, 623)
20
+ socket.send([0x6, 0x0, 0xff, 0x6, 0x0, 0x0, 0x11, 0xbe, 0x80, 0x0, 0x0, 0x0].pack('C*'), 0)
21
+ selections = IO.select([socket], nil, nil, (Proxy::Onboard::Plugin.settings.bmc_scanner_socket_timeout_seconds || 1))
22
+ socket.close
23
+ !selections.nil?
24
+ end
25
+
26
+ # Not used because of slowness
27
+ def scan_unthreaded_to_list
28
+ return false if !valid?
29
+ pinged = Array.new
30
+ @range.each do |address|
31
+ if address_pings?(address)
32
+ pinged << address
33
+ end
34
+ end
35
+ pinged
36
+ end
37
+
38
+ # Determine maximum number of threads
39
+ def calculate_max_threads
40
+ max_threads = Proxy::Onboard::Plugin.settings.bmc_scanner_max_threads_per_request || 500
41
+ begin
42
+ sockets = Array.new
43
+ # @range.first(max_threads).size performs much better than @range.count if @range is large.
44
+ (1..[max_threads, @range.first(max_threads).size].min).each do
45
+ socket = UDPSocket.new
46
+ sockets << socket
47
+ end
48
+ rescue Errno::EMFILE
49
+ # Running low on free file descriptors; only allow use of half of the remaining
50
+ max_threads = [sockets.length / 2, 1].max
51
+ # Clean up sockets
52
+ sockets.each do |sock|
53
+ sock.close
54
+ end
55
+ logger.warn "IPMIScanner: Running low on free file descriptors! Can only allocate #{sockets.length}, so using #{max_threads} to avoid hitting the limit."
56
+ end
57
+ max_threads
58
+ end
59
+
60
+ def scan_threaded_to_list
61
+ return false if !valid?
62
+ pinged = Array.new
63
+ pool = Concurrent::ThreadPoolExecutor.new(max_threads: calculate_max_threads)
64
+ @range.each do |address|
65
+ pool.post do
66
+ if address_pings?(address)
67
+ pinged << address
68
+ end
69
+ end
70
+ end
71
+ pool.shutdown
72
+ pool.wait_for_termination
73
+ pinged
74
+ end
75
+
76
+ def scan_to_list
77
+ return false if !valid?
78
+ scan_threaded_to_list
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,49 @@
1
+ require 'smart_proxy'
2
+
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
12
+
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
17
+
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
22
+
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
31
+ end
32
+ end
33
+ end
34
+
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] }
45
+ end
46
+ @scanner = Proxy::Onboard::BMC::IPMIScanner.new(args)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,5 @@
1
+ require 'smart_proxy_onboard/api_bmc'
2
+
3
+ map '/bmc' do
4
+ run Proxy::Onboard::ApiBmc.new
5
+ end
@@ -0,0 +1,13 @@
1
+ module Proxy::Onboard
2
+ # Plugin definition
3
+ class Plugin < ::Proxy::Plugin
4
+ plugin 'onboard', Proxy::Onboard::VERSION
5
+ default_settings bmc_scanner_max_range_size: 65_536,
6
+ bmc_scanner_max_threads_per_request: 500,
7
+ bmc_scanner_socket_timeout_seconds: 1
8
+
9
+ http_rackup_path File.expand_path('http_config.ru', File.expand_path('../', __FILE__))
10
+ https_rackup_path File.expand_path('http_config.ru', File.expand_path('../', __FILE__))
11
+ default_settings node_scheme: 'https', node_port: 8443
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ module Proxy
2
+ module Onboard
3
+ VERSION = '0.1.1'.freeze
4
+ end
5
+ end
@@ -0,0 +1,2 @@
1
+ require 'smart_proxy_onboard/version'
2
+ require 'smart_proxy_onboard/onboard'
@@ -0,0 +1,19 @@
1
+ ---
2
+ :enabled: true
3
+
4
+ #####################################
5
+ # Settings for scanner at /bmc/scan #
6
+ #####################################
7
+ # The largest number of IP addresses that may be scanned in one request. Set
8
+ # this value to 0 to turn off the scanner. Defaults to 65536, which covers a
9
+ # CIDR prefixlen of /16.
10
+ :bmc_scanner_max_range_size: 65536
11
+
12
+ # The maximum number of IP addresses to scan at the same time, per request.
13
+ # Defaults to 500. Before increasing this number, you should increase the
14
+ # maximum number of open files for Smart Proxy.
15
+ :bmc_scanner_max_threads_per_request: 500
16
+
17
+ # How many seconds to wait for each scanned IP address to respond before timing
18
+ # out. Defaults to 1.
19
+ :bmc_scanner_socket_timeout_seconds: 1
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'smart_proxy_onboard/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'smart_proxy_onboard'
8
+ spec.version = Proxy::Onboard::VERSION
9
+ spec.authors = ['Nick Liu']
10
+ spec.email = ['deltik@gmx.com']
11
+
12
+ spec.summary = %q{Support functions for onboarding new servers into Foreman}
13
+ spec.description = %q{This plugin exposes API calls that can be used to onboard new hosts in bulk into Foreman through PXE boot and the foreman_discovery image.}
14
+ spec.homepage = 'https://github.com/theforeman/smart_proxy_onboard'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = 'exe'
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ['lib']
22
+ spec.license = 'GPLv3'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.14'
25
+ spec.add_development_dependency 'rake', '~> 10.0'
26
+ spec.add_development_dependency 'test-unit'
27
+ spec.add_development_dependency 'rack'
28
+ spec.add_development_dependency 'rack-test'
29
+ spec.add_development_dependency 'mocha'
30
+ spec.add_development_dependency 'webmock'
31
+
32
+ spec.add_runtime_dependency 'concurrent-ruby'
33
+ end
metadata ADDED
@@ -0,0 +1,176 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: smart_proxy_onboard
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Nick Liu
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-05-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.14'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.14'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: test-unit
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rack
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rack-test
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: mocha
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: webmock
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: concurrent-ruby
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: This plugin exposes API calls that can be used to onboard new hosts in
126
+ bulk into Foreman through PXE boot and the foreman_discovery image.
127
+ email:
128
+ - deltik@gmx.com
129
+ executables: []
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - .gitignore
134
+ - .rubocop.yml
135
+ - .rubocop_todo.yml
136
+ - .travis.yml
137
+ - Gemfile
138
+ - LICENSE
139
+ - README.md
140
+ - Rakefile
141
+ - bin/console
142
+ - bin/setup
143
+ - lib/bmc/basescanner.rb
144
+ - lib/bmc/ipmiscanner.rb
145
+ - lib/smart_proxy_onboard.rb
146
+ - lib/smart_proxy_onboard/api_bmc.rb
147
+ - lib/smart_proxy_onboard/http_config.ru
148
+ - lib/smart_proxy_onboard/onboard.rb
149
+ - lib/smart_proxy_onboard/version.rb
150
+ - settings.d/onboard.yml.example
151
+ - smart_proxy_onboard.gemspec
152
+ homepage: https://github.com/theforeman/smart_proxy_onboard
153
+ licenses:
154
+ - GPLv3
155
+ metadata: {}
156
+ post_install_message:
157
+ rdoc_options: []
158
+ require_paths:
159
+ - lib
160
+ required_ruby_version: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - '>='
163
+ - !ruby/object:Gem::Version
164
+ version: '0'
165
+ required_rubygems_version: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - '>='
168
+ - !ruby/object:Gem::Version
169
+ version: '0'
170
+ requirements: []
171
+ rubyforge_project:
172
+ rubygems_version: 2.6.11
173
+ signing_key:
174
+ specification_version: 4
175
+ summary: Support functions for onboarding new servers into Foreman
176
+ test_files: []