-
Notifications
You must be signed in to change notification settings - Fork 103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding Ability to Set Connection Timeout Values #289
Changes from 6 commits
8a7f60f
19232f0
5b63253
f940c3d
e5860f5
4fd277e
dd33eca
3bdea1e
e8f54c4
80c4e57
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,17 +42,17 @@ def prepare_http_client | |
else | ||
@http.cert_store = @trust_store | ||
end | ||
@headers = {'Content-Type' => 'text/xml'} | ||
@headers = { 'Content-Type' => 'text/xml' } | ||
@success = false | ||
end | ||
|
||
def execute(options = {}) | ||
time_tracker = Time.now | ||
@conn_tries = 0 | ||
|
||
begin | ||
prepare_http_client | ||
@http.read_timeout = options[:timeout] if options.key? :timeout | ||
@http.open_timeout = options.key?(:open_timeout) ? options[:open_timeout] : 60 | ||
@http.read_timeout = options.key?(:timeout) ? options[:timeout] : 120 | ||
@http.open_timeout = options.key?(:open_timeout) ? options[:open_timeout] : 120 | ||
@raw_response = @http.post(@uri.path, @req, @headers) | ||
@raw_response_data = @raw_response.read_body | ||
|
||
|
@@ -62,7 +62,7 @@ def execute(options = {}) | |
@success = true | ||
else | ||
@success = false | ||
@error = "User requested raw XML response. Not parsing failures." | ||
@error = 'User requested raw XML response. Not parsing failures.' | ||
end | ||
else | ||
@res = parse_xml(@raw_response_data) | ||
|
@@ -74,19 +74,19 @@ def execute(options = {}) | |
|
||
@sid = attributes['session-id'] | ||
|
||
if (attributes['success'] and attributes['success'].to_i == 1) | ||
if (attributes['success'] && attributes['success'].to_i == 1) | ||
@success = true | ||
elsif @api_version =~ /1.2/ and @res and (@res.get_elements '//Exception').count < 1 | ||
elsif @api_version =~ /1.2/ && @res && (@res.get_elements '//Exception').count < 1 | ||
@success = true | ||
else | ||
@success = false | ||
if @api_version =~ /1.2/ | ||
@res.elements.each('//Exception/Message') do |message| | ||
@error = message.text.sub(/.*Exception: */, '') | ||
@error = message.text.sub(/.*Exception: */, '') | ||
end | ||
@res.elements.each('//Exception/Stacktrace') do |stacktrace| | ||
@trace = stacktrace.text | ||
end | ||
@res.elements.each('//Exception/Stacktrace') do |stacktrace| | ||
@trace = stacktrace.text | ||
end | ||
else | ||
@res.elements.each('//message') do |message| | ||
@error = message.text.sub(/.*Exception: */, '') | ||
|
@@ -97,27 +97,30 @@ def execute(options = {}) | |
end | ||
end | ||
end | ||
# This is a hack to handle corner cases where a heavily loaded Nexpose instance | ||
# drops our HTTP connection before processing. We try 5 times to establish a | ||
# connection in these situations. The actual exception occurs in the Ruby | ||
# http library, which is why we use such generic error classes. | ||
rescue OpenSSL::SSL::SSLError => e | ||
# This is a hack to handle corner cases where a heavily loaded Nexpose instance | ||
# drops our HTTP connection before processing. We try 5 times to establish a | ||
# connection in these situations. The actual exception occurs in the Ruby | ||
# http library, which is why we use such generic error classes. | ||
rescue OpenSSL::SSL::SSLError | ||
if @conn_tries < 5 | ||
@conn_tries += 1 | ||
retry | ||
end | ||
rescue ::ArgumentError, ::NoMethodError => e | ||
rescue ::ArgumentError, ::NoMethodError | ||
if @conn_tries < 5 | ||
@conn_tries += 1 | ||
retry | ||
end | ||
rescue ::Timeout::Error | ||
rescue ::Timeout::Error => error | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Useless assignment to variable - error. |
||
if @conn_tries < 5 | ||
@conn_tries += 1 | ||
# If an explicit timeout is set, don't retry. | ||
retry unless options.key? :timeout | ||
if options.key?(:timeout) | ||
retry if (time_tracker + options[:timeout].to_i) > Time.now | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid more than 3 levels of block nesting. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This may not really be about this PR but more a general thought, should the retry here even occur now? Originally this was retried up to 5 times if no There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Scott and I were talking about not having retries at all anymore, maybe. At least as far as time out retries go, they are often more of a problem than just failing as many Nexpose API endpoints will do a lot of work on a heavy-load console before returning a response. Retrying in that case just repeats the work being done which we don't want to do IMO. |
||
else | ||
retry | ||
end | ||
end | ||
@error = "Nexpose did not respond within #{@http.read_timeout} seconds." | ||
@error = "Nexpose did not respond within #{@http.read_timeout} seconds after #{@conn_tries} attempt(s)." | ||
rescue ::Errno::EHOSTUNREACH, ::Errno::ENETDOWN, ::Errno::ENETUNREACH, ::Errno::ENETRESET, ::Errno::EHOSTDOWN, ::Errno::EACCES, ::Errno::EINVAL, ::Errno::EADDRNOTAVAIL | ||
@error = 'Nexpose host is unreachable.' | ||
# Handle console-level interrupts | ||
|
@@ -129,15 +132,15 @@ def execute(options = {}) | |
@error = "Error parsing response: #{exc.message}" | ||
end | ||
|
||
if !(@success or @error) | ||
if !(@success || @error) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Favor unless over if for negative conditions. |
||
@error = "Nexpose service returned an unrecognized response: #{@raw_response_data.inspect}" | ||
end | ||
|
||
@sid | ||
end | ||
|
||
def attributes(*args) | ||
return if not @res.root | ||
return unless @res.root | ||
@res.root.attributes(*args) | ||
end | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,14 +51,16 @@ class Connection | |
attr_reader :url | ||
# The token used to login to the NSC | ||
attr_reader :token | ||
|
||
# The last XML request sent by this object, useful for debugging. | ||
attr_reader :request_xml | ||
# The last XML response received by this object, useful for debugging. | ||
attr_reader :response_xml | ||
|
||
# The trust store to validate connections against if any | ||
attr_reader :trust_store | ||
# The main HTTP read_timeout value | ||
attr_accessor :timeout | ||
# The optional HTTP open_timeout value | ||
attr_accessor :open_timeout | ||
|
||
# A constructor to load a Connection object from a URI | ||
def self.from_uri(uri, user, pass, silo_id = nil, token = nil, trust_cert = nil) | ||
|
@@ -76,23 +78,23 @@ def self.from_uri(uri, user, pass, silo_id = nil, token = nil, trust_cert = nil) | |
# @param [String] token The two-factor authentication (2FA) token for Nexpose sessions. | ||
# @param [String] trust_cert The PEM-formatted web certificate of the Nexpose console. Used for SSL validation. | ||
def initialize(ip, user, pass, port = 3780, silo_id = nil, token = nil, trust_cert = nil) | ||
@host = ip | ||
@port = port | ||
@username = user | ||
@password = pass | ||
@token = token | ||
@silo_id = silo_id | ||
unless trust_cert.nil? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry to nit pick this is another case where code inspection is not friendly to debugging, by making this a single line call you can lose break point granularity. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you give an example of what you mean? are you saying you'd want to put a breakdown on the inside the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again is just a convenience thing to me being able to break when In this case it may not be all that useful since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair enough, I see what you're saying. Personally, I am using breakpoints with That's a fair enough concern, and I'll keep it in mind going forward. On this particular line, do you have a preference if we leave it as is, or should I change it back? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is fine to leave as is. |
||
@trust_store = create_trust_store(trust_cert) | ||
end | ||
@session_id = nil | ||
@url = "https://#{@host}:#{@port}/api/API_VERSION/xml" | ||
@host = ip | ||
@username = user | ||
@password = pass | ||
@port = port | ||
@silo_id = silo_id | ||
@token = token | ||
@trust_store = create_trust_store(trust_cert) unless trust_cert.nil? | ||
@session_id = nil | ||
@url = "https://#{@host}:#{@port}/api/API_VERSION/xml" | ||
@timeout = 120 | ||
@open_timeout = 120 | ||
end | ||
|
||
# Establish a new connection and Session ID | ||
def login | ||
begin | ||
login_hash = {'sync-id' => 0, 'password' => @password, 'user-id' => @username, 'token' => @token} | ||
login_hash = { 'sync-id' => 0, 'password' => @password, 'user-id' => @username, 'token' => @token } | ||
login_hash['silo-id'] = @silo_id if @silo_id | ||
r = execute(make_xml('LoginRequest', login_hash)) | ||
if r.success | ||
|
@@ -106,13 +108,15 @@ def login | |
|
||
# Logout of the current connection | ||
def logout | ||
r = execute(make_xml('LogoutRequest', {'sync-id' => 0})) | ||
r = execute(make_xml('LogoutRequest', { 'sync-id' => 0 })) | ||
return true if r.success | ||
raise APIError.new(r, 'Logout failed') | ||
end | ||
|
||
# Execute an API request | ||
def execute(xml, version = '1.1', options = {}) | ||
options.store(:timeout, @timeout) unless options.key?(:timeout) | ||
options.store(:open_timeout, @open_timeout) | ||
@request_xml = xml.to_s | ||
@api_version = version | ||
response = APIRequest.execute(@url, @request_xml, @api_version, options, @trust_store) | ||
|
@@ -127,17 +131,17 @@ def execute(xml, version = '1.1', options = {}) | |
# Would need to do something more sophisticated to grab | ||
# all the associated image files. | ||
def download(url, file_name = nil) | ||
return nil if url.nil? or url.empty? | ||
uri = URI.parse(url) | ||
http = Net::HTTP.new(@host, @port) | ||
return nil if (url.nil? || url.empty?) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't use parentheses around the condition of an if. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. usually right, but currently wrong houndci-bot ... this can cause issues with compound if's . i would leave this |
||
uri = URI.parse(url) | ||
http = Net::HTTP.new(@host, @port) | ||
http.use_ssl = true | ||
if @trust_store.nil? | ||
http.verify_mode = OpenSSL::SSL::VERIFY_NONE # XXX: security issue | ||
else | ||
http.cert_store = @trust_store | ||
end | ||
headers = {'Cookie' => "nexposeCCSessionID=#{@session_id}"} | ||
resp = http.get(uri.to_s, headers) | ||
headers = { 'Cookie' => "nexposeCCSessionID=#{@session_id}" } | ||
resp = http.get(uri.to_s, headers) | ||
|
||
if file_name | ||
::File.open(file_name, 'wb') { |file| file.write(resp.body) } | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know code inspection complains about these but capturing the object makes runtime debugging easier, I would prefer they be kept.