collins_client 0.2.7

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.
@@ -0,0 +1,137 @@
1
+ require 'ostruct'
2
+
3
+ module Collins; module Api
4
+
5
+ module Logging
6
+
7
+ include Collins::Util
8
+
9
+ # @see http://tumblr.github.com/platform/collinsTutorial/out/logapi.html
10
+ module Severity
11
+ extend self
12
+
13
+ EMERGENCY = "EMERGENCY"
14
+ ALERT = "ALERT"
15
+ CRITICAL = "CRITICAL"
16
+ ERROR = "ERROR"
17
+ WARNING = "WARNING"
18
+ NOTICE = "NOTICE"
19
+ INFORMATIONAL = "INFORMATIONAL"
20
+ DEBUG = "DEBUG"
21
+ NOTE = "NOTE"
22
+
23
+ # Given a severity level, give back the severity, or nil if not valid
24
+ # @param [String,Symbol] level Severity level
25
+ # @return [String] Severity level as string
26
+ def value_of level
27
+ level_s = normalize level
28
+ if valid? level_s then
29
+ level_s.to_s
30
+ else
31
+ nil
32
+ end
33
+ end
34
+
35
+ # Convert a level into one appropriate for validating
36
+ # @param [Symbol,String] level Severity level
37
+ # @return [Symbol] normalized (not neccesarily valid) severity level
38
+ def normalize level
39
+ level.to_s.upcase.to_sym
40
+ end
41
+
42
+ # Check is a level is valid or not
43
+ # @param [Symbol,String] level Severity level
44
+ # @return [Boolean] indicate whether valid or not
45
+ def valid? level
46
+ level_s = normalize level
47
+ Collins::Api::Logging::Severity.constants.include?(level_s)
48
+ end
49
+
50
+ #@return [Array<String>] severity levels
51
+ def to_a
52
+ s = Collins::Api::Logging::Severity
53
+ s.constants.map{|c| s.const_get(c)}
54
+ end
55
+ end # module Severity
56
+
57
+ # Log a message against an asset using the specified level
58
+ # @param [String,Collins::Asset] asset_or_tag
59
+ # @param [String] message
60
+ # @param [Severity] level severity level to use
61
+ # @return [Boolean] true if logged successfully
62
+ # @raise [Collins::ExpectationFailed] the specified level was invalid
63
+ # @raise [Collins::RequestError,Collins::UnexpectedResponseError] if the asset or message invalid
64
+ def log! asset_or_tag, message, level = nil
65
+ asset = get_asset_or_tag asset_or_tag
66
+ parameters = {
67
+ :message => message,
68
+ :type => log_level_from_string(level)
69
+ }
70
+ parameters = select_non_empty_parameters parameters
71
+ logger.debug("Logging to #{asset.tag} with parameters #{parameters.inspect}")
72
+ http_put("/api/asset/#{asset.tag}/log", parameters, asset.location) do |response|
73
+ parse_response response, :as => :status, :expects => 201
74
+ end
75
+ end
76
+
77
+ # Fetch logs for an asset according to the options specified
78
+ # @example
79
+ # :filter => "EMERGENCY;ALERT" # Only retrieve emergency and alert messages
80
+ # :filter => "!DEBUG;!NOTE" # Only retrieve non-debug/non-notes
81
+ # @param [String,Collins::Asset] asset_or_tag
82
+ # @param [Hash] options query options
83
+ # @option options [Fixnum] :page (0) Page of results
84
+ # @option options [Fixnum] :size (25) Number of results to retrieve
85
+ # @option options [String] :sort (DESC) Sort ordering for results
86
+ # @option options [String] :filter Semicolon separated list of severity levels to include or exclude
87
+ # @option options [String] :all_tag If specified, an asset tag is this value, proxy to the all_logs method
88
+ # @note To exclude a level via a filter it must be prepended with a `!`
89
+ # @return [Array<OpenStruct>] Array of log objects
90
+ # @raise [Collins::UnexpectedResponseError] on a non-200 response
91
+ def logs asset_or_tag, options = {}
92
+ asset = get_asset_or_tag asset_or_tag
93
+ all_tag = options.delete(:all_tag)
94
+ if all_tag && all_tag.to_s.downcase == asset.tag.to_s.downcase then
95
+ return all_logs options
96
+ end
97
+ parameters = get_page_options(options).merge(
98
+ :filter => get_option(:filter, options, nil)
99
+ )
100
+ parameters = select_non_empty_parameters parameters
101
+ logger.debug("Fetching logs for #{asset.tag} with parameters #{parameters.inspect}")
102
+ http_get("/api/asset/#{asset.tag}/logs", parameters, asset.location) do |response|
103
+ parse_response response, :as => :paginated, :default => [], :raise => strict?, :expects => 200 do |json|
104
+ json.map{|j| OpenStruct.new(symbolize_hash(j))}
105
+ end
106
+ end
107
+ end
108
+
109
+ # Same as logs but for all assets
110
+ # @see #logs
111
+ def all_logs options = {}
112
+ parameters = get_page_options(options).merge(
113
+ :filter => get_option(:filter, options, nil)
114
+ )
115
+ parameters = select_non_empty_parameters parameters
116
+ logger.debug("Fetching logs for all assets with parameters #{parameters.inspect}")
117
+ http_get("/api/assets/logs", parameters) do |response|
118
+ parse_response response, :as => :paginated, :default => [], :raise => strict?, :expects => 200 do |json|
119
+ json.map{|j| OpenStruct.new(symbolize_hash(j))}
120
+ end
121
+ end
122
+ end
123
+
124
+ private
125
+ def log_level_from_string level
126
+ return nil if (level.nil? || level.empty?)
127
+ s = Collins::Api::Logging::Severity
128
+ if s.valid? level then
129
+ s.value_of level
130
+ else
131
+ raise Collins::ExpectationFailedError.new("#{level} is not a valid log level")
132
+ end
133
+ end
134
+
135
+ end # module Log
136
+
137
+ end; end
@@ -0,0 +1,84 @@
1
+ require 'collins/power'
2
+ require 'collins/profile'
3
+
4
+ module Collins; module Api
5
+
6
+ module Management
7
+
8
+ def provisioning_profiles
9
+ logger.debug("Getting provisioning profiles from collins")
10
+ http_get("/api/provision/profiles") do |response|
11
+ parse_response response, :expects => 200, :as => :data do |json|
12
+ json["PROFILES"].map { |o| Collins::Profile.new(o) }
13
+ end
14
+ end
15
+ end
16
+
17
+ def provision asset_or_tag, profile, contact, options = {}
18
+ asset = get_asset_or_tag asset_or_tag
19
+ parameters = {
20
+ :profile => profile,
21
+ :contact => contact,
22
+ :suffix => options[:suffix],
23
+ :primary_role => options[:primary_role],
24
+ :secondary_role => options[:secondary_role],
25
+ :pool => options[:pool],
26
+ :activate => options[:activate]
27
+ }
28
+ parameters = select_non_empty_parameters parameters
29
+ if parameters.empty? then
30
+ raise CollinsError.new("provision requires at least a profile")
31
+ end
32
+ http_post("/api/provision/#{asset.tag}", parameters, asset.location) do |response|
33
+ parse_response response, :expects => 200, :as => :status
34
+ end
35
+ end
36
+
37
+ def power_status asset_or_tag
38
+ asset = get_asset_or_tag asset_or_tag
39
+ logger.debug("Checking power status of #{asset.tag}")
40
+ http_get("/api/asset/#{asset.tag}/power", {}, asset.location) do |response|
41
+ parse_response response, :expects => 200, :as => :message, :raise => strict?, :default => "Unknown"
42
+ end
43
+ end
44
+
45
+ def power! asset_or_tag, action
46
+ asset = get_asset_or_tag asset_or_tag
47
+ if action.to_s.downcase == "status" then
48
+ return power_status asset_or_tag
49
+ end
50
+ action = Collins::Power.normalize_action action
51
+ parameters = {
52
+ :action => action
53
+ }
54
+ logger.debug("Calling power action on #{asset.tag}, action #{action}")
55
+ http_post("/api/asset/#{asset.tag}/power", parameters, asset.location) do |response|
56
+ parse_response response, :expects => 200, :as => :status
57
+ end
58
+ end
59
+
60
+ def ipmi_create asset_or_tag, username, password, address, gateway, netmask
61
+ ipmi_update asset_or_tag, :username => username, :password => password, :address => address,
62
+ :gateway => gateway, :netmask => netmask
63
+ end
64
+
65
+ def ipmi_update asset_or_tag, options = {}
66
+ asset = get_asset_or_tag asset_or_tag
67
+ parameters = {
68
+ :username => get_option(:username, options, nil),
69
+ :password => get_option(:password, options, nil),
70
+ :address => get_option(:address, options, nil),
71
+ :gateway => get_option(:gateway, options, nil),
72
+ :netmask => get_option(:netmask, options, nil)
73
+ }
74
+ parameters = select_non_empty_parameters parameters
75
+ return true if parameters.empty?
76
+ logger.debug("Updating asset #{asset.tag} IPMI info with parameters #{parameters.inspect}")
77
+ http_post("/api/asset/#{asset.tag}/ipmi", parameters, asset.location) do |response|
78
+ parse_response response, :expects => [200,201], :as => :status
79
+ end
80
+ end
81
+
82
+ end # module Management
83
+
84
+ end; end
@@ -0,0 +1,46 @@
1
+ require 'ostruct'
2
+
3
+ module Collins; module Api
4
+
5
+ # @api collins
6
+ module Tag
7
+
8
+ include Collins::Util
9
+
10
+ # Get all collins tags
11
+ #
12
+ # Sample output
13
+ # @example
14
+ # [
15
+ # {:name => "", :label => "", :description => ""},
16
+ # ]
17
+ #
18
+ # @raise [UnexpectedResponseError] if strict is true and the response is not a 200
19
+ # @return [Array<OpenStruct>] Array of tags containing name, label and description keys
20
+ def get_all_tags
21
+ http_get("/api/tags") do |response|
22
+ parse_response response, :expects => 200, :default => [], :raise => strict? do |json|
23
+ json["data"]["tags"].map{|t| OpenStruct.new(symbolize_hash(t))}
24
+ end
25
+ end
26
+ end
27
+
28
+ # Get all values associated with the specified tag
29
+ #
30
+ # @note You will get a 404 if the tag does not exist. Depending on strict mode, this will result in an empty array or an exception
31
+ # @param [String] tag The tag you would like values for
32
+ # @param [Boolean] strict Here for backwards API compatibility
33
+ # @raise [UnexpectedResponseError] if strict is true and the response is not a 200
34
+ # @return [Array<String>] values associated with this tag
35
+ def get_tag_values tag, strict = false
36
+ http_get("/api/tag/#{tag}") do |response|
37
+ parse_response response, :expects => 200, :default => [], :raise => strict?(strict) do |json|
38
+ json["data"]["values"]
39
+ end
40
+ end
41
+ end
42
+
43
+ end # module Tag
44
+
45
+ end; end
46
+
@@ -0,0 +1,28 @@
1
+ require 'collins/api/util/parameters'
2
+ require 'collins/api/util/requests'
3
+ require 'collins/api/util/responses'
4
+
5
+ module Collins; module Api
6
+
7
+ module Util
8
+
9
+ include Collins::Api::Util::Parameters
10
+ include Collins::Api::Util::Requests
11
+ include Collins::Api::Util::Responses
12
+
13
+
14
+ #returns true if successful ping to collins, false otherwise
15
+ def ping
16
+ begin
17
+ http_get("/api/ping") do |response|
18
+ parse_response response, :expects => 200
19
+ end
20
+ true
21
+ rescue
22
+ false
23
+ end
24
+ end
25
+
26
+ end # module Util
27
+
28
+ end; end
@@ -0,0 +1,45 @@
1
+ module Collins; module Api; module Util
2
+
3
+ module Errors
4
+
5
+ protected
6
+ def handle_error response
7
+ if response.code >= 400 && rich_error_response?(response) then
8
+ raise RichRequestError.new(
9
+ "Error processing request", response.code, error_response(response), error_details(response)
10
+ )
11
+ elsif response.code >= 400 && error_response?(response) then
12
+ raise RequestError.new("Error processing request: #{error_response(response)}", response.code)
13
+ elsif response.code == 401 then
14
+ raise AuthenticationError.new("Invalid username or password")
15
+ elsif response.code > 401 then
16
+ raise RequestError.new("Response code was #{response.code}, #{response.to_s}", response.code)
17
+ end
18
+ end
19
+ def rich_error_response? response
20
+ if error_response?(response) then
21
+ parsed = response.parsed_response
22
+ parsed.key?("data") && parsed["data"].key?("details")
23
+ else
24
+ false
25
+ end
26
+ end
27
+ def error_response? response
28
+ begin
29
+ parsed = response.parsed_response
30
+ parsed["status"] && parsed["status"].include?("error")
31
+ rescue Exception => e
32
+ logger.warn("Could not determine if response #{response} was an error. #{e}")
33
+ false
34
+ end
35
+ end
36
+ def error_details response
37
+ response.parsed_response["data"]["details"]
38
+ end
39
+ def error_response response
40
+ response.parsed_response["data"]["message"]
41
+ end
42
+
43
+ end
44
+
45
+ end; end; end
@@ -0,0 +1,44 @@
1
+ module Collins; module Api; module Util
2
+
3
+ module Parameters
4
+ protected
5
+
6
+ # retrieve a key from a hash or use the default
7
+ def get_option key, hash, default
8
+ if hash.include?(key) then
9
+ hash[key]
10
+ elsif hash.include?(key.to_s) then
11
+ hash[key.to_s]
12
+ else
13
+ default
14
+ end
15
+ end
16
+
17
+ def get_page_options options = {}
18
+ {
19
+ :page => get_option(:page, options, 0),
20
+ :size => get_option(:size, options, 25),
21
+ :sort => get_option(:sort, options, "DESC")
22
+ }
23
+ end
24
+
25
+ # select parameters from a hash matching some options
26
+ def select_non_empty_parameters params
27
+ select_parameters params, :nil => false, :empty_string => false
28
+ end
29
+ def select_parameters params, options = {}
30
+ params.inject({}) do |result, (k,v)|
31
+ if v.nil? && options[:nil] == false then
32
+ # don't include
33
+ elsif v.is_a?(String) && v.empty? && options[:empty_string] == false then
34
+ # don't include
35
+ else
36
+ result[k] = v
37
+ end
38
+ result
39
+ end
40
+ end
41
+
42
+ end # Parameters module
43
+
44
+ end; end; end
@@ -0,0 +1,136 @@
1
+ require 'timeout'
2
+ require 'uri'
3
+
4
+ module Collins; module Api; module Util
5
+
6
+ module Requests
7
+
8
+ protected
9
+ def http_get uri, parameters = {}, remote = nil
10
+ http_call(uri) {
11
+ if parameters.is_a?(Array) then
12
+ params = {:query => parameters.join("&")}
13
+ else
14
+ params = {:query => parameters}
15
+ end
16
+ params = strip_request :query, params
17
+ result = self.class.get(uri, http_options(params, remote))
18
+ if block_given? then
19
+ yield(result)
20
+ else
21
+ result
22
+ end
23
+ }
24
+ end
25
+
26
+ def http_put uri, parameters = {}, remote = nil
27
+ http_call(uri) {
28
+ params = strip_request :body, :body => parameters
29
+ result = self.class.put(uri, http_options(params, remote))
30
+ if block_given? then
31
+ yield(result)
32
+ else
33
+ result
34
+ end
35
+ }
36
+ end
37
+
38
+ def http_post uri, parameters = {}, remote = nil
39
+ http_call(uri) {
40
+ params = strip_request :body, :body => parameters
41
+ result = self.class.post(uri, http_options(params, remote))
42
+ if block_given? then
43
+ yield(result)
44
+ else
45
+ result
46
+ end
47
+ }
48
+ end
49
+
50
+ def http_delete uri, parameters = {}, remote = nil
51
+ http_call(uri) {
52
+ params = strip_request :body, :body => parameters
53
+ result = self.class.delete(uri, http_options(params, remote))
54
+ if block_given? then
55
+ yield(result)
56
+ else
57
+ result
58
+ end
59
+ }
60
+ end
61
+
62
+ def http_options opts = {}, remote = nil
63
+ if remote then
64
+ host_info = get_location_information remote
65
+ auth = {:username => host_info.username, :password => host_info.password}
66
+ base_uri = host_info.host
67
+ else
68
+ auth = {:username => username, :password => password}
69
+ base_uri = host
70
+ end
71
+ http_opts = opts.merge!({:basic_auth => auth, :base_uri => base_uri, :timeout => timeout_i})
72
+ http_opts[:headers] = headers unless headers.empty?
73
+ http_opts[:debug_output] = $stderr if (logger.level < 0 and Module.const_defined?(:HTTP_DEBUG) and HTTP_DEBUG)
74
+ http_opts
75
+ end
76
+
77
+ def http_call uri, &block
78
+ trace("Calling uri #{uri}, waiting for #{timeout_i} seconds")
79
+ begin
80
+ timeout(timeout_i) {
81
+ block.call
82
+ }
83
+ rescue Timeout::Error => e
84
+ raise Timeout::Error.new("Timeout talking to #{uri}: #{e}")
85
+ rescue Collins::RequestError => e
86
+ e.uri = uri
87
+ raise e
88
+ rescue Exception => e
89
+ if e.class.to_s == "WebMock::NetConnectNotAllowedError" then
90
+ raise e
91
+ else
92
+ raise e.class.new("Exception talking to #{uri}: #{e}")
93
+ end
94
+ end
95
+ end
96
+
97
+ def strip_request key, options
98
+ if options[key] && options[key].empty? then
99
+ {}
100
+ else
101
+ options
102
+ end
103
+ end
104
+
105
+ def get_location_information location
106
+ location_s = location.downcase.to_sym
107
+ return locations[location_s] if locations[location_s]
108
+ logger.debug("Fetching credentials for location #{location_s}")
109
+ http_get("/api/asset/#{location}") do |response|
110
+ result = parse_response response, :expects => 200, :as => :asset, :raise => strict?, :default => nil
111
+ if result && result.get_attribute(:location) then
112
+ if result.get_attribute(:location).nil? then
113
+ raise AuthenticationError.new("Could not find LOCATION attribute on asset #{location}")
114
+ end
115
+ uri = URI(result.get_attribute(:location))
116
+ if uri.port then
117
+ port = ":#{uri.port}"
118
+ else
119
+ port = ""
120
+ end
121
+ locations[location_s] = OpenStruct.new Hash[
122
+ :username => uri.user,
123
+ :password => uri.password,
124
+ :host => "#{uri.scheme}://#{uri.host}#{port}"
125
+ ]
126
+ else
127
+ raise AuthenticationError.new("Could not find LOCATION attribute on asset #{location}")
128
+ end
129
+ end
130
+ locations[location_s]
131
+ end
132
+
133
+
134
+ end # module Requests
135
+
136
+ end; end; end