Class: CloudnAPI::Client
- Inherits:
-
Object
- Object
- CloudnAPI::Client
- Defined in:
- lib/cloudn-api/client.rb
Overview
In the Client class, there is camel-cased methods such as queryAsyncJobResult. Usually ruby methods are named along with snake-case, but this class needs to handle Cloudstack methods with Ruby’s meta method : method_missing. So if you find camel-cased methods, you can think of that as calling Cloudstack method internally.
Constant Summary collapse
- API_LIST_PATH =
'../api.json'
- SLEEP_TIME_SEC =
5
- RETRY_COUNT =
wait for max 30 minutes
360
- ASYNC_QUERY_FINISHED =
1
- ASYNC_QUERY_ERROR =
2
Instance Attribute Summary collapse
-
#api_key ⇒ Object
readonly
Returns the value of attribute api_key.
-
#api_list ⇒ Object
readonly
Returns the value of attribute api_list.
-
#secret_key ⇒ Object
readonly
Returns the value of attribute secret_key.
-
#url ⇒ Object
readonly
Returns the value of attribute url.
-
#wait_async ⇒ Object
readonly
Returns the value of attribute wait_async.
Instance Method Summary collapse
-
#build_query_str(hash_cloudn_api) ⇒ Object
Build query string such as command=…&response=json&apiKey=…&signature=…
-
#configure_logger ⇒ Object
Set up logger instance.
-
#create_signature(concatenated_str) ⇒ Object
Create signature: Create HMAC with secret key, encode HMAC with Base64, and url-encode it.
-
#encode(param_hash) ⇒ Object
Return url encoded string.
-
#initialize(config_file = nil) ⇒ Client
constructor
A new instance of Client.
- #load_api_list ⇒ Object
-
#load_config(config_file) ⇒ Object
Load configuration to @config_hash as follows 1.
-
#method_missing(method, *args) ⇒ Object
to handle CloudStack methods described in cloudstack.apache.org/docs/api/apidocs-4.0.0/TOC_User.html Note that I didn’t test all methods,so some methods wouldn’t work properly.
-
#queryAsyncJobResult(jobid) ⇒ Object
This method is only explicitly defined method for Cloudstack in order to prevent infinite loop of method_missing.
-
#request(query_string) ⇒ Object
Issue HTTP request to API end point (url).
-
#request_api(method, args = nil) ⇒ Object
build query string, then issue http request to Cloudn end point.
-
#search_config_file ⇒ String
Find config file from HOME/.kneadn/config.
- #setup_logger_and_configs(config_file) ⇒ Object
-
#wait_for_async_job(jobid) ⇒ Object
Wait until async job is finished.
Constructor Details
#initialize(config_file = nil) ⇒ Client
Returns a new instance of Client.
26 27 28 29 30 31 32 33 |
# File 'lib/cloudn-api/client.rb', line 26 def initialize(config_file = nil) setup_logger_and_configs config_file @api_key = @config_hash[:api_key].freeze @secret_key = @config_hash[:secret_key].freeze @url = URI.parse(@config_hash[:url]).freeze @wait_async = true end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method, *args) ⇒ Object
to handle CloudStack methods described in
http://cloudstack.apache.org/docs/api/apidocs-4.0.0/TOC_User.html
Note that I didn’t test all methods,so some methods wouldn’t work properly.
38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/cloudn-api/client.rb', line 38 def method_missing(method, *args) super unless @api_list.include? method.to_s hash_response = args ? request_api(method, args.pop) : request_api(method) @logger.info hash_response if @wait_async and hash_response.values.pop.has_key? 'jobid' result = wait_for_async_job(hash_response.values.pop.fetch('jobid')) result = result['queryasyncjobresultresponse'] else result = hash_response end @logger.debug(result) result end |
Instance Attribute Details
#api_key ⇒ Object (readonly)
Returns the value of attribute api_key.
24 25 26 |
# File 'lib/cloudn-api/client.rb', line 24 def api_key @api_key end |
#api_list ⇒ Object (readonly)
Returns the value of attribute api_list.
24 25 26 |
# File 'lib/cloudn-api/client.rb', line 24 def api_list @api_list end |
#secret_key ⇒ Object (readonly)
Returns the value of attribute secret_key.
24 25 26 |
# File 'lib/cloudn-api/client.rb', line 24 def secret_key @secret_key end |
#url ⇒ Object (readonly)
Returns the value of attribute url.
24 25 26 |
# File 'lib/cloudn-api/client.rb', line 24 def url @url end |
#wait_async ⇒ Object (readonly)
Returns the value of attribute wait_async.
24 25 26 |
# File 'lib/cloudn-api/client.rb', line 24 def wait_async @wait_async end |
Instance Method Details
#build_query_str(hash_cloudn_api) ⇒ Object
Build query string such as
command=...&response=json&apiKey=...&signature=...
142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/cloudn-api/client.rb', line 142 def build_query_str(hash_cloudn_api) # Prepare to create signature and query string. hash_cloudn_api_with_key = hash_cloudn_api.merge({:response => :json, :apikey => @api_key}) # Create signature encoded_str = encode(hash_cloudn_api_with_key) signature = create_signature encoded_str # Create query string with signature initial_str = hash_cloudn_api_with_key.flat_map {|k, v| "#{k}=#{ERB::Util.url_encode(v.to_s)}" }.join('&') query_string = initial_str + "&signature=#{signature}" end |
#configure_logger ⇒ Object
Set up logger instance
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/cloudn-api/client.rb', line 97 def configure_logger @logger.progname = 'CloudnAPI' @logger.datetime_format = "%Y-%m-%d %H:%M:%S " log_level = @config_hash[:log_level] || :error @logger.level = case log_level.to_sym when :info Logger::INFO when :debug Logger::DEBUG when :warn Logger::WARN when :error Logger::ERROR when :fatal Logger::FATAL else Logger::INFO end end |
#create_signature(concatenated_str) ⇒ Object
Create signature: Create HMAC with secret key, encode HMAC with Base64, and url-encode it
156 157 158 159 160 |
# File 'lib/cloudn-api/client.rb', line 156 def create_signature(concatenated_str) hmac = OpenSSL::HMAC::digest(::OpenSSL::Digest::SHA1.new, @secret_key, concatenated_str) base64 = Base64.encode64(hmac).chomp ERB::Util.url_encode(base64) end |
#encode(param_hash) ⇒ Object
Return url encoded string. Note: only values are url encoded.
163 164 165 166 167 168 169 |
# File 'lib/cloudn-api/client.rb', line 163 def encode(param_hash) encoded_hash = param_hash.each_with_object({}) { |(k, v), new_hash| new_hash[k] = ERB::Util.url_encode(v.to_s).downcase } sorted_hash = Hash[encoded_hash.sort] concatnated_str = sorted_hash.flat_map {|k, v| "#{k}=#{v}" }.join('&') end |
#load_api_list ⇒ Object
171 172 173 174 175 |
# File 'lib/cloudn-api/client.rb', line 171 def load_api_list api_list_path = File.(API_LIST_PATH, __FILE__) list = open(api_list_path).read @api_list = JSON.parse(list) end |
#load_config(config_file) ⇒ Object
Load configuration to @config_hash as follows
1. Check config_jfile which is explicitly specified
2. Check default location, ~/.cloudnapi/config
3. Check environmental variables
If none of them can’t be found, then report errors and exit.
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
# File 'lib/cloudn-api/client.rb', line 182 def load_config(config_file) # If config file is not specified, check default location config_file = search_config_file unless config_file # If there are ENV variables, then use them and return from this method. # If none of them, configuration or Env variables, specified, # then report error and exit. unless config_file if ENV['CLOUDN_API_KEY'] and ENV['CLOUDN_SECRET_KEY'] and ENV['CLOUDN_URL'] @config_hash = {} @config_hash[:api_key] = ENV['CLOUDN_API_KEY'] @config_hash[:secret_key] = ENV['CLOUDN_SECRET_KEY'] @config_hash[:url] = ENV['CLOUDN_URL'] return else @logger.error("Couldn't find no configuration file.") @logger.error('~/.cloudnapi/config or specify config file.') exit 1 end end # Read and load @config_hash from config_file config_body = open(config_file).read begin @config_hash = JSON.parse(config_body, {:symbolize_names => true}) rescue Exception => e @logger.error("#{config_file} seems broken. Please check that ...") @logger.error e exit 1 end end |
#queryAsyncJobResult(jobid) ⇒ Object
This method is only explicitly defined method for Cloudstack in order to prevent infinite loop of method_missing.
79 80 81 82 |
# File 'lib/cloudn-api/client.rb', line 79 def queryAsyncJobResult(jobid) @logger.info "Requesting :queryAsyncJobResult - jobid=#{jobid}" request_api(:queryAsyncJobResult, {jobid: jobid}) end |
#request(query_string) ⇒ Object
Issue HTTP request to API end point (url)
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/cloudn-api/client.rb', line 119 def request(query_string) url = @url.dup url.query = query_string begin response_text = OpenURI.open_uri( url, :ssl_verify_mode => OpenSSL::SSL::VERIFY_NONE, 'User-Agent' => "CloudnAPI/#{RUBY_VERSION}" ).read rescue OpenURI::HTTPError => ex @logger.error url @logger.error ex rescue Exception => ex @logger.error url @logger.error ex end parsed_reponse = JSON.parse(response_text) end |
#request_api(method, args = nil) ⇒ Object
build query string, then issue http request to Cloudn end point
85 86 87 88 89 90 91 92 93 94 |
# File 'lib/cloudn-api/client.rb', line 85 def request_api(method, args=nil) if args hash_cloudn_api = args.merge({ command: method.to_s }) query_str = build_query_str hash_cloudn_api else query_str = build_query_str({ command: method.to_s }) end @logger.info "#{method} - #{@config_hash[:url]}?#{query_str}" request query_str end |
#search_config_file ⇒ String
Find config file from HOME/.kneadn/config
217 218 219 220 |
# File 'lib/cloudn-api/client.rb', line 217 def search_config_file default_config_path = ENV['HOME'] + '/.cloudnapi/config' File.exists?(default_config_path) ? default_config_path : nil end |
#setup_logger_and_configs(config_file) ⇒ Object
53 54 55 56 57 58 |
# File 'lib/cloudn-api/client.rb', line 53 def setup_logger_and_configs(config_file) @logger = Logger.new(STDOUT) load_config config_file load_api_list configure_logger end |
#wait_for_async_job(jobid) ⇒ Object
Wait until async job is finished
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/cloudn-api/client.rb', line 61 def wait_for_async_job(jobid) RETRY_COUNT.times { result = queryAsyncJobResult(jobid) # When async job has finished, let's escape from loop if result['queryasyncjobresultresponse']['jobstatus'] == ASYNC_QUERY_FINISHED # @logger.info result break result elsif result['queryasyncjobresultresponse']['jobstatus'] == ASYNC_QUERY_ERROR @logger.error result raise StandardError.new('Error occurred') end sleep SLEEP_TIME_SEC } end |