Class: RightScale::ReposeDownloader

Inherits:
Object
  • Object
show all
Includes:
RightSupport::Log::Mixin
Defined in:
lib/instance/cook/repose_downloader.rb

Overview

Abstract download capabilities

Defined Under Namespace

Classes: ConnectionException, DownloadException

Constant Summary collapse

PROXY_ENVIRONMENT_VARIABLES =

Environment variables to examine for proxy settings, in order.

['HTTPS_PROXY', 'HTTP_PROXY', 'http_proxy', 'ALL_PROXY']
CONNECTION_EXCEPTIONS =

Class names of exceptions to be re-raised as a ConnectionException

['Errno::ECONNREFUSED', 'Errno::ETIMEDOUT', 'SocketError',
'RestClient::InternalServerError', 'RestClient::RequestTimeout']
RETRY_BACKOFF_MAX =

max timeout 8 (2**3) minutes for each retry

3
RETRY_MAX_ATTEMPTS =

retry 5 times maximum

5

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(hostnames) ⇒ Downloader

Initializes a Downloader with a list of hostnames

The purpose of this method is to instantiate a Downloader. It will perform DNS resolution on the hostnames provided and will configure a proxy if necessary

Parameters

Return

Parameters:

  • Hostnames ([String])

    to resolve

Raises:

  • (ArgumentError)


73
74
75
76
77
78
79
80
81
# File 'lib/instance/cook/repose_downloader.rb', line 73

def initialize(hostnames)
  raise ArgumentError, "At least one hostname must be provided" if hostnames.empty?
  hostnames = [hostnames] unless hostnames.respond_to?(:each)
  @ips = resolve(hostnames)
  @hostnames = hostnames

  proxy_var = PROXY_ENVIRONMENT_VARIABLES.detect { |v| ENV.has_key?(v) }
  @proxy = ENV[proxy_var].match(/^[[:alpha:]]+:\/\//) ? URI.parse(ENV[proxy_var]) : URI.parse("http://" + ENV[proxy_var]) if proxy_var
end

Instance Attribute Details

#ipsObject (readonly)

Hash of IP Address => Hostname



59
60
61
# File 'lib/instance/cook/repose_downloader.rb', line 59

def ips
  @ips
end

#sanitized_resourceObject (readonly)

(String) Last resource downloaded



56
57
58
# File 'lib/instance/cook/repose_downloader.rb', line 56

def sanitized_resource
  @sanitized_resource
end

#sizeObject (readonly)

(Integer) Size in bytes of last successful download (nil if none)



50
51
52
# File 'lib/instance/cook/repose_downloader.rb', line 50

def size
  @size
end

#speedObject (readonly)

(Integer) Speed in bytes/seconds of last successful download (nil if none)



53
54
55
# File 'lib/instance/cook/repose_downloader.rb', line 53

def speed
  @speed
end

Instance Method Details

#detailsString

Message summarizing last successful download details

Return

Returns:

  • (String)

    Message with last downloaded resource, download size and speed



140
141
142
# File 'lib/instance/cook/repose_downloader.rb', line 140

def details
  "Downloaded '#{@sanitized_resource}' (#{ scale(size.to_i).join(' ') }) at #{ scale(speed.to_i).join(' ') }/s"
end

#download(resource) { ... } ⇒ Object

Downloads an attachment from Repose

The purpose of this method is to download the specified attachment from Repose If a failure is encountered it will provide proper feedback regarding the nature of the failure

Parameters

Block

Parameters:

  • Resource (String)

    URI to parse and fetch

Yields:

  • A block is mandatory

Yield Returns:

  • (String)

    The stream that is being fetched



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/instance/cook/repose_downloader.rb', line 96

def download(resource)
  client              = get_http_client
  @size               = 0
  @speed              = 0
  @sanitized_resource = sanitize_resource(resource)
  resource            = parse_resource(resource)
  attempts            = 0

  begin
    balancer.request do |endpoint|
      RightSupport::Net::SSL.with_expected_hostname(ips[endpoint]) do
        logger.info("Requesting '#{sanitized_resource}' from '#{endpoint}'")

        attempts += 1
        t0 = Time.now

        # Previously we accessed RestClient directly and used it's wrapper method to instantiate
        # a RestClient::Request object.  This wrapper was not passing all options down the stack
        # so now we invoke the RestClient::Request object directly, passing it our desired options
        client.execute(:method => :get, :url => "https://#{endpoint}:443#{resource}", :timeout => calculate_timeout(attempts), :verify_ssl => OpenSSL::SSL::VERIFY_PEER, :ssl_ca_file => get_ca_file, :headers => {:user_agent => "RightLink v#{AgentConfig.protocol_version}", 'X-RightLink-Version' => RightLink.version }) do |response, request, result|
          if result.kind_of?(Net::HTTPSuccess)
            @size = result.content_length || response.size || 0
            @speed = @size / (Time.now - t0)
            yield response
          else
            response.return!(request, result)
          end
        end
      end
    end
  rescue Exception => e
    list = parse_exception_message(e)
    message = list.join(", ")
    logger.error("Request '#{sanitized_resource}' failed - #{message}")
    raise ConnectionException, message unless (list & CONNECTION_EXCEPTIONS).empty?
    raise DownloadException, message
  end
end