hawkular-client 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +2 -1
  4. data/.rubocop.yml +19 -6
  5. data/CHANGES.rdoc +13 -3
  6. data/README.rdoc +9 -3
  7. data/hawkularclient.gemspec +8 -4
  8. data/lib/alerts/alerts_api.rb +202 -32
  9. data/lib/hawkular.rb +35 -67
  10. data/lib/hawkular_all.rb +2 -0
  11. data/lib/inventory/inventory_api.rb +459 -130
  12. data/lib/metrics/metric_api.rb +14 -9
  13. data/lib/operations/operations_api.rb +247 -0
  14. data/lib/tokens/tokens_api.rb +33 -0
  15. data/lib/version.rb +1 -1
  16. data/spec/integration/alerts_spec.rb +272 -20
  17. data/spec/integration/hello-world-definitions.json +46 -0
  18. data/spec/integration/inventory_spec.rb +253 -89
  19. data/spec/integration/metric_spec.rb +36 -0
  20. data/spec/integration/operations_spec.rb +420 -0
  21. data/spec/integration/tokens_spec.rb +45 -0
  22. data/spec/resources/driver.jar +0 -0
  23. data/spec/resources/sample.war +0 -0
  24. data/spec/spec_helper.rb +30 -6
  25. data/spec/unit/base_spec.rb +22 -1
  26. data/spec/unit/canonical_path_spec.rb +92 -0
  27. data/spec/vcr/vcr_setup.rb +3 -4
  28. data/spec/vcr_cassettes/Alert/EndToEnd/Should_create_and_fire_a_trigger.yml +1187 -0
  29. data/spec/vcr_cassettes/Alert/Events/Should_list_events.yml +101 -0
  30. data/spec/vcr_cassettes/Alert/Events/Should_list_events_using_criteria.yml +79 -0
  31. data/spec/vcr_cassettes/Alert/Events/Should_not_list_events_using_criteria.yml +49 -0
  32. data/spec/vcr_cassettes/Alert/Triggers/Should_bulk_load_triggers.yml +225 -0
  33. data/spec/vcr_cassettes/Alert/Triggers/Should_create_a_basic_trigger_with_action.yml +355 -0
  34. data/spec/vcr_cassettes/Alert/Triggers/Should_create_an_action.yml +134 -0
  35. data/spec/vcr_cassettes/Alert/Triggers/Should_create_an_action_for_webhooks.yml +220 -0
  36. data/spec/vcr_cassettes/Alert/Triggers/Should_get_the_action_definitions.yml +218 -0
  37. data/spec/vcr_cassettes/Alert/Triggers/Should_not_create_an_action_for_unknown_plugin.yml +46 -0
  38. data/spec/vcr_cassettes/Alert/Triggers/Should_not_create_an_action_for_unknown_properties.yml +134 -0
  39. data/spec/vcr_cassettes/Counter_metrics/Should_get_metrics_with_limit_and_order.yml +270 -0
  40. data/spec/vcr_cassettes/Inventory/Client_should_listen_on_various_inventory_events.json +47 -0
  41. data/spec/vcr_cassettes/Inventory/Client_should_listen_on_various_inventory_events.yml +191 -0
  42. data/spec/vcr_cassettes/Inventory/Helpers/generate_some_events_for_websocket.yml +507 -0
  43. data/spec/vcr_cassettes/Inventory/Should_List_datasources_with_no_props.yml +74 -69
  44. data/spec/vcr_cassettes/Inventory/{Should_list_types_without_feed.yml → Should_create_a_feed.yml} +29 -32
  45. data/spec/vcr_cassettes/Inventory/Should_create_a_feed_again.yml +211 -0
  46. data/spec/vcr_cassettes/Inventory/Should_create_a_resource_.yml +355 -0
  47. data/spec/vcr_cassettes/Inventory/Should_create_a_resource_with_metric.yml +786 -0
  48. data/spec/vcr_cassettes/Inventory/Should_create_a_resourcetype.yml +205 -0
  49. data/spec/vcr_cassettes/Inventory/Should_create_and_delete_feed.yml +201 -0
  50. data/spec/vcr_cassettes/Inventory/Should_create_and_get_a_resource.yml +419 -0
  51. data/spec/vcr_cassettes/Inventory/Should_list_URLs.yml +39 -27
  52. data/spec/vcr_cassettes/Inventory/Should_list_WildFlys.yml +37 -29
  53. data/spec/vcr_cassettes/Inventory/Should_list_WildFlys_with_props.yml +53 -45
  54. data/spec/vcr_cassettes/Inventory/Should_list_all_the_resource_types.yml +177 -0
  55. data/spec/vcr_cassettes/Inventory/Should_list_children_of_WildFly.yml +230 -56
  56. data/spec/vcr_cassettes/Inventory/Should_list_feeds.yml +27 -24
  57. data/spec/vcr_cassettes/Inventory/Should_list_heap_metrics_for_WildFlys.yml +586 -119
  58. data/spec/vcr_cassettes/Inventory/Should_list_metrics_for_WildFlys.yml +220 -59
  59. data/spec/vcr_cassettes/Inventory/Should_list_metrics_of_given_metric_type.yml +613 -0
  60. data/spec/vcr_cassettes/Inventory/Should_list_metrics_of_given_resource_type.yml +333 -0
  61. data/spec/vcr_cassettes/Inventory/Should_list_recursive_children_of_WildFly.yml +2064 -0
  62. data/spec/vcr_cassettes/Inventory/Should_list_relationships_of_WildFly.yml +460 -0
  63. data/spec/vcr_cassettes/Inventory/Should_list_types_with_bad_feed.yml +26 -25
  64. data/spec/vcr_cassettes/Inventory/Should_list_types_with_feed.yml +102 -36
  65. data/spec/vcr_cassettes/Inventory/Should_not_find_an_unknown_resource.yml +54 -0
  66. data/spec/vcr_cassettes/Inventory/Tenants/Should_Get_Tenant_For_Explicit_Credentials.yml +50 -0
  67. data/spec/vcr_cassettes/Inventory/Tenants/Should_Get_Tenant_For_Implicit_Credentials.yml +50 -0
  68. data/spec/vcr_cassettes/Operation/Helpers/get_tenant.yml +50 -0
  69. data/spec/vcr_cassettes/Operation/Operation/Add_JDBC_driver_should_add_the_driver.json +26 -0
  70. data/spec/vcr_cassettes/Operation/Operation/Add_datasource_should_be_doable.json +26 -0
  71. data/spec/vcr_cassettes/Operation/Operation/Add_deployment_should_be_doable.json +26 -0
  72. data/spec/vcr_cassettes/Operation/Operation/Redeploy_can_be_run_multiple_times_in_parallel.json +40 -0
  73. data/spec/vcr_cassettes/Operation/Operation/Redeploy_should_be_performed_and_eventually_respond_with_success.json +26 -0
  74. data/spec/vcr_cassettes/Operation/Operation/Redeploy_should_not_be_performed_if_resource_path_is_wrong.json +26 -0
  75. data/spec/vcr_cassettes/Operation/Operation/Remove_JDBC_driver_should_be_performed_and_eventually_respond_with_success.json +26 -0
  76. data/spec/vcr_cassettes/Operation/Operation/Remove_datasource_should_be_performed_and_eventually_respond_with_success.json +26 -0
  77. data/spec/vcr_cassettes/Operation/Operation/Remove_deployment_should_be_performed_and_eventually_respond_with_success.json +26 -0
  78. data/spec/vcr_cassettes/Operation/Operation/Undeploy_should_be_performed_and_eventually_respond_with_success.json +26 -0
  79. data/spec/vcr_cassettes/Operation/Operation/should_not_be_possible_to_perform_on_closed_client.json +12 -0
  80. data/spec/vcr_cassettes/Operation/Websocket_connection/should_be_established.json +9 -0
  81. data/spec/vcr_cassettes/Tokens/Should_be_able_to_create_a_new_token_for_an_actual_user.yml +57 -0
  82. data/spec/vcr_cassettes/Tokens/Should_be_able_to_list_the_available_tokens.yml +49 -0
  83. data/spec/vcr_cassettes/Tokens/Should_get_a_401_when_attempting_to_create_a_token_with_a_wrong_password.yml +56 -0
  84. data/spec/vcr_cassettes/Tokens/Should_get_a_401_when_attempting_to_create_a_token_without_a_password.yml +55 -0
  85. metadata +175 -11
  86. data/lib/metrics/version.rb +0 -7
data/lib/hawkular.rb CHANGED
@@ -21,7 +21,7 @@ module Hawkular
21
21
  @credentials = {
22
22
  username: nil,
23
23
  password: nil,
24
- token: nil
24
+ token: nil
25
25
  }.merge(credentials)
26
26
  @options = {
27
27
  tenant: nil,
@@ -50,7 +50,13 @@ module Hawkular
50
50
  # @param [String] url_part Part of an url to be escaped
51
51
  # @return [String] escaped url_part as new string
52
52
  def hawk_escape(url_part)
53
- sub_url = url_part.dup
53
+ return url_part.to_s if url_part.is_a?(Numeric)
54
+
55
+ if url_part.is_a? Symbol
56
+ sub_url = url_part.to_s
57
+ else
58
+ sub_url = url_part.dup
59
+ end
54
60
  sub_url.gsub!('%', '%25')
55
61
  sub_url.gsub!(' ', '%20')
56
62
  sub_url.gsub!('[', '%5b')
@@ -91,28 +97,17 @@ module Hawkular
91
97
  # @!visibility private
92
98
  def rest_client(suburl)
93
99
  options[:timeout] = ENV['HAWKULARCLIENT_REST_TIMEOUT'] if ENV['HAWKULARCLIENT_REST_TIMEOUT']
94
- options[:ssl_ca_file] = @options[:ssl_ca_file]
95
- options[:verify_ssl] = @options[:verify_ssl]
100
+ options[:ssl_ca_file] = @options[:ssl_ca_file]
101
+ options[:verify_ssl] = @options[:verify_ssl]
96
102
  options[:ssl_client_cert] = @options[:ssl_client_cert]
97
- options[:ssl_client_key] = @options[:ssl_client_key]
98
- options[:user] = @credentials[:username]
99
- options[:password] = @credentials[:password]
103
+ options[:ssl_client_key] = @options[:ssl_client_key]
104
+ options[:user] = @credentials[:username]
105
+ options[:password] = @credentials[:password]
100
106
  # strip @endpoint in case suburl is absolute
101
107
  suburl = suburl[@entrypoint.length, suburl.length] if suburl.match(/^http/)
102
108
  RestClient::Resource.new(@entrypoint, options)[suburl]
103
109
  end
104
110
 
105
- # @!visibility private
106
- def base_url
107
- url = URI.parse(@entrypoint)
108
- "#{url.scheme}://#{url.host}:#{url.port}"
109
- end
110
-
111
- # @!visibility private
112
- def self.parse_response(response)
113
- JSON.parse(response)
114
- end
115
-
116
111
  # @!visibility private
117
112
  def http_headers(headers = {})
118
113
  {}.merge(tenant_header)
@@ -140,50 +135,34 @@ module Hawkular
140
135
  encoded.rstrip!
141
136
  end
142
137
 
143
- # Generate a query string from the passed hash.
144
- # This string starts with a `?` if the hash is
145
- # not empty.
138
+ # Generate a query string from the passed hash, starting with '?'
146
139
  # Values may be an array, in which case the array values are joined together by `,`.
147
- # @param param_hash [Hash] key-values pairs
148
- # @return [String] complete query string to append to a base url
149
- def generate_query_params(param_hash = {})
150
- return '' if param_hash.size == 0
151
-
152
- num = count_non_nil_values(param_hash)
153
-
154
- i = 0
155
- ret = ''
156
- ret = '?' if num > 0
157
- param_hash.each do |k, v|
158
- next if not_suitable?(v)
159
-
160
- if v.instance_of? Array
161
- part = "#{k}=#{v.join(',')}"
162
- else
163
- part = "#{k}=#{v}"
164
- end
165
-
166
- ret += hawk_escape part
167
-
168
- i += 1
169
- ret += '&' if i < num
140
+ # @param params [Hash] key-values pairs
141
+ # @return [String] complete query string to append to a base url, '' if no valid params
142
+ def generate_query_params(params = {})
143
+ params = params.select { |_k, v| !(v.nil? || ((v.instance_of? Array) && v.empty?)) }
144
+ return '' if params.empty?
145
+
146
+ params.inject('?') do |ret, (k, v)|
147
+ ret += '&' unless ret == '?'
148
+ part = (v.instance_of? Array) ? "#{k}=#{v.join(',')}" : "#{k}=#{v}"
149
+ ret + hawk_escape(part)
170
150
  end
171
- ret
172
151
  end
173
152
 
174
- private
153
+ # Specialized exception to be thrown
154
+ # when the interaction with Hawkular fails
155
+ class HawkularException < StandardError
156
+ def initialize(message, status_code = 0)
157
+ @message = message
158
+ @status_code = status_code
159
+ super(message)
160
+ end
175
161
 
176
- def not_suitable?(v)
177
- v.nil? || (v.instance_of? Array) && v.size == 0
162
+ attr_reader :message, :status_code
178
163
  end
179
164
 
180
- def count_non_nil_values(param_hash)
181
- num = 0
182
- param_hash.each do |_k, v|
183
- num += 1 unless not_suitable?(v)
184
- end
185
- num
186
- end
165
+ private
187
166
 
188
167
  def token_header
189
168
  @credentials[:token].nil? ? {} : { 'Authorization' => "Bearer #{@credentials[:token]}" }
@@ -202,21 +181,10 @@ module Hawkular
202
181
  rescue JSON::ParserError
203
182
  fault_message = f.http_body
204
183
  end
205
- fail HawkularException, fault_message
184
+ fail HawkularException.new(fault_message, (f.respond_to?(:http_code) ? f.http_code : 0))
206
185
  else
207
186
  fail f
208
187
  end
209
188
  end
210
189
  end
211
-
212
- # Specialized exception to be thrown
213
- # when the interction with Hawkular fails
214
- class HawkularException < StandardError
215
- def initialize(message)
216
- @message = message
217
- super
218
- end
219
-
220
- attr_reader :message
221
- end
222
190
  end
data/lib/hawkular_all.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  require 'inventory/inventory_api'
2
2
  require 'metrics/metrics_client.rb'
3
3
  require 'alerts/alerts_api'
4
+ require 'tokens/tokens_api'
5
+ require 'operations/operations_api'
4
6
  require 'hawkular'
5
7
 
6
8
  module Hawkular
@@ -1,4 +1,6 @@
1
1
  require 'hawkular'
2
+ require 'websocket-client-simple'
3
+ require 'json'
2
4
 
3
5
  # Inventory module provides access to the Hawkular Inventory REST API.
4
6
  # @see http://www.hawkular.org/docs/rest/rest-inventory.html
@@ -14,10 +16,19 @@ module Hawkular::Inventory
14
16
  # @param credentials [Hash{String=>String}] Hash of username, password, token(optional)
15
17
  def initialize(entrypoint = nil, credentials = {})
16
18
  @entrypoint = entrypoint
17
-
18
19
  super(entrypoint, credentials)
19
20
  end
20
21
 
22
+ # Creates a new Inventory Client
23
+ # @param hash [Hash{String=>Object}] a hash containing base url of Hawkular-inventory - e.g
24
+ # entrypoint: http://localhost:8080/hawkular/inventory
25
+ # and another sub-hash containing the hash with username[String], password[String], token(optional)
26
+ def self.create(hash)
27
+ hash[:entrypoint] ||= 'http://localhost:8080/hawkular/inventory'
28
+ hash[:credentials] ||= {}
29
+ InventoryClient.new(hash[:entrypoint], hash[:credentials])
30
+ end
31
+
21
32
  # Retrieve the tenant id for the passed credentials.
22
33
  # If no credentials are passed, the ones from the constructor are used
23
34
  # @param credentials [Hash{String=>String}] Hash of username, password, token(optional)
@@ -32,7 +43,7 @@ module Hawkular::Inventory
32
43
  end
33
44
 
34
45
  # TODO: revisit and potentially move to Base ?
35
- def impersonate(credentials = {})
46
+ def impersonate!(credentials = {})
36
47
  @tenant = get_tenant(credentials)
37
48
  @options[:tenant] = @tenant
38
49
  end
@@ -41,9 +52,7 @@ module Hawkular::Inventory
41
52
  # @return [Array<String>] List of feed ids
42
53
  def list_feeds(_environment = 'test')
43
54
  ret = http_get('feeds')
44
- val = []
45
- ret.each { |f| val.push(f['id']) }
46
- val
55
+ ret.map { |f| f['id'] }
47
56
  end
48
57
 
49
58
  # List resource types. If no need is given all types are listed
@@ -54,17 +63,32 @@ module Hawkular::Inventory
54
63
  ret = http_get('/resourceTypes')
55
64
  else
56
65
  the_feed = hawk_escape feed
57
- ret = http_get('/feeds/' + the_feed + '/resourceTypes')
66
+ ret = http_get("/feeds/#{the_feed}/resourceTypes")
67
+ end
68
+ ret.map { |rt| ResourceType.new(rt) }
69
+ end
70
+
71
+ # Return all resources for a feed
72
+ # @param [String] feed Id of the feed that hosts the resources
73
+ # @param [Boolean] fetch_properties Should the config data be fetched too
74
+ # @return [Array<Resource>] List of resources, which can be empty.
75
+ def list_resources_for_feed(feed, fetch_properties = false)
76
+ fail 'Feed must be given' unless feed
77
+ the_feed = hawk_escape feed
78
+ ret = http_get("/feeds/#{the_feed}/resources")
79
+ ret.map do |r|
80
+ if fetch_properties
81
+ p = get_config_data_for_resource(r['id'], feed)
82
+ r['properties'] = p['value']
83
+ end
84
+ Resource.new(r)
58
85
  end
59
- val = []
60
- ret.each { |rt| val.push(ResourceType.new(rt)) }
61
- val
62
86
  end
63
87
 
64
88
  # List the resources for the passed feed and resource type. The representation for
65
89
  # resources under a feed are sparse and additional data must be retrived separately.
66
90
  # It is possible though to also obtain runtime properties by setting #fetch_properties to true.
67
- # @param [String] feed The id of the feed the type lives under. Can be nil for feedless types
91
+ # @param [String] feed The id of the feed the type lives under. Can be nil for all feeds
68
92
  # @param [String] type Name of the type to look for. Can be obtained from {ResourceType}.id.
69
93
  # Must not be nil
70
94
  # @param [Boolean] fetch_properties Shall additional runtime properties be fetched?
@@ -73,21 +97,19 @@ module Hawkular::Inventory
73
97
  fail 'Type must not be nil' unless type
74
98
  the_type = hawk_escape type
75
99
  if feed.nil?
76
- ret = http_get('resourceTypes/' + the_type + '/resources')
100
+ ret = http_get("resourceTypes/#{the_type}/resources")
77
101
  else
78
102
 
79
103
  the_feed = hawk_escape feed
80
- ret = http_get('/feeds/' + the_feed + '/resourceTypes/' + the_type + '/resources')
104
+ ret = http_get("/feeds/#{the_feed}/resourceTypes/#{the_type}/resources")
81
105
  end
82
- val = []
83
- ret.each do |r|
106
+ ret.map do |r|
84
107
  if fetch_properties && !feed.nil?
85
108
  p = get_config_data_for_resource(r['id'], feed)
86
109
  r['properties'] = p['value']
87
110
  end
88
- val.push(Resource.new(r))
111
+ Resource.new(r)
89
112
  end
90
- val
91
113
  end
92
114
 
93
115
  # Retrieve runtime properties for the passed resource
@@ -97,7 +119,8 @@ module Hawkular::Inventory
97
119
  def get_config_data_for_resource(resource_id, feed)
98
120
  the_id = hawk_escape resource_id
99
121
  the_feed = hawk_escape feed
100
- http_get('feeds/' + the_feed + '/resources/' + the_id + '/data?dataType=configuration')
122
+ query = generate_query_params dataType: 'configuration'
123
+ http_get("feeds/#{the_feed}/resources/#{the_id}/data#{query}")
101
124
  rescue
102
125
  {}
103
126
  end
@@ -105,47 +128,48 @@ module Hawkular::Inventory
105
128
  # Obtain the child resources of the passed resource. In case of a WildFly server,
106
129
  # those would be Datasources, Deployments and so on.
107
130
  # @param [Resource] parent_resource Resource to obtain children from
131
+ # @param [Boolean] recursive Whether to fetch also all the children of children of ...
108
132
  # @return [Array<Resource>] List of resources that are children of the given parent resource.
109
133
  # Can be empty
110
- def list_child_resources(parent_resource)
134
+ def list_child_resources(parent_resource, recursive = false)
111
135
  the_feed = hawk_escape parent_resource.feed
112
136
  the_id = hawk_escape parent_resource.id
113
137
 
114
- ret = http_get('/feeds/' + the_feed +
115
- '/resources/' + the_id + '/children')
116
- val = []
117
- ret.each { |r| val.push(Resource.new(r)) }
118
- val
138
+ which_children = (recursive ? '/recursiveChildren' : '/children')
139
+ ret = http_get("/feeds/#{the_feed}/resources/#{the_id}#{which_children}")
140
+ ret.map { |r| Resource.new(r) }
119
141
  end
120
142
 
121
143
  # Obtain a list of relationships starting at the passed resource
122
144
  # @param [Resource] resource One end of the relationship
145
+ # @param [String] named Name of the relationship
123
146
  # @return [Array<Relationship>] List of relationships
124
- def list_relationships(resource)
125
- the_feed = hawk_escape resource.feed
126
- the_id = hawk_escape resource.id
127
-
128
- ret = http_get('/feeds/' + the_feed + '/resources/' + the_id + '/relationships')
129
- val = []
130
- ret.each { |r| val.push(Relationship.new(r)) }
131
- val
132
- rescue
133
- []
147
+ def list_relationships(resource, named = nil)
148
+ query = named.nil? ? '' : (generate_query_params named: named)
149
+ ret = http_get("/path#{resource.path}/relationships#{query}")
150
+ ret.map { |r| Relationship.new(r) }
134
151
  end
135
152
 
136
153
  # Obtain a list of relationships for the passed feed
137
154
  # @param [String] feed_id Id of the feed
155
+ # @param [String] named Name of the relationship
138
156
  # @return [Array<Relationship>] List of relationships
139
- def list_relationships_for_feed(feed_id)
157
+ def list_relationships_for_feed(feed_id, named = nil)
140
158
  the_feed = hawk_escape feed_id
141
- ret = http_get('/feeds/' + the_feed + '/relationships')
142
- val = []
143
- ret.each { |r| val.push(Relationship.new(r)) }
144
- val
159
+ query = named.nil? ? '' : (generate_query_params named: named)
160
+ ret = http_get("/feeds/#{the_feed}/relationships#{query}")
161
+ ret.map { |r| Relationship.new(r) }
145
162
  rescue
146
163
  []
147
164
  end
148
165
 
166
+ # Retrieve a single entity from inventory by its canonical path
167
+ # @param [CanonicalPath] path canonical path of the entity
168
+ # @return inventory entity
169
+ def get_entity(path)
170
+ http_get("path#{path}")
171
+ end
172
+
149
173
  # [15:01:51] <jkremser> pilhuhn, this works for me curl -XPOST
150
174
  # -H "Content-Type: application/json"
151
175
  # -u jdoe:password -d
@@ -166,9 +190,59 @@ module Hawkular::Inventory
166
190
  # rel.to_h)
167
191
  # end
168
192
 
169
- # def list_metrics_for_resource_type
170
- # # TODO implement me
171
- # end
193
+ # List the metrics for the passed feed and metric type. If feed is not passed,
194
+ # all the metrics across all the feeds of a given type will be retrieved
195
+ # This method may perform multiple REST calls.
196
+ # @param [String] feed The id of the feed the type lives under. Can be nil for all feeds
197
+ # @param [String] type Name of the metric type to look for. Can be obtained from {MetricType}.id.
198
+ # Must not be nil
199
+ # @return [Array<Metric>] List of metrics. Can be empty
200
+ def list_metrics_for_metric_type(feed, type)
201
+ fail 'Type must not be nil' unless type
202
+ the_type = hawk_escape type
203
+ if feed.nil?
204
+ type_hash = http_get("metricTypes/#{the_type}")
205
+ else
206
+ the_feed = hawk_escape feed
207
+ type_hash = http_get("/feeds/#{the_feed}/metricTypes/#{the_type}")
208
+ end
209
+
210
+ rels = list_relationships(ResourceType.new(type_hash), 'defines')
211
+ rels.map do |rel|
212
+ path = CanonicalPath.parse(rel.target_id.to_s)
213
+ metric_hash = get_entity path
214
+ Metric.new(metric_hash)
215
+ end
216
+ rescue
217
+ []
218
+ end
219
+
220
+ # List the metrics for the passed feed and all the resources of given resource type.
221
+ # If feed is not passed, all the metrics across all the feeds of a resource type will be retrieved
222
+ # This method may perform multiple REST calls.
223
+ # @param [String] feed The id of the feed the type lives under. Can be nil for all feeds
224
+ # @param [String] type Name of the resource type to look for. Can be obtained from {ResourceType}.id.
225
+ # Must not be nil
226
+ # @return [Array<Metric>] List of metrics. Can be empty
227
+ def list_metrics_for_resource_type(feed, type)
228
+ fail 'Type must not be nil' unless type
229
+ the_type = hawk_escape type
230
+ if feed.nil?
231
+ ret = http_get("resourceTypes/#{the_type}/resources")
232
+ else
233
+ the_feed = hawk_escape feed
234
+ ret = http_get("feeds/#{the_feed}/resourceTypes/#{the_type}/resources")
235
+ end
236
+ ret.flat_map do |r|
237
+ path = CanonicalPath.parse(r['path'])
238
+ if !path.feed_id.nil?
239
+ nested_ret = http_get("feeds/#{path.feed_id}/resources/#{path.resource_ids.join('/')}/metrics")
240
+ else
241
+ nested_ret = http_get("#{path.environment_id}/resources/#{path.resource_ids.join('/')}/metrics")
242
+ end
243
+ nested_ret.map { |m| Metric.new(m) }
244
+ end
245
+ end
172
246
 
173
247
  # List metric (definitions) for the passed resource. It is possible to filter down the
174
248
  # result by a filter to only return a subset. The
@@ -176,7 +250,7 @@ module Hawkular::Inventory
176
250
  # @param [Hash{Symbol=>String}] filter for 'type' and 'match'
177
251
  # Metric type can be one of 'GAUGE', 'COUNTER', 'AVAILABILITY'. If a key is missing
178
252
  # it will not be used for filtering
179
- # @return [Aray<Metric>] List of metrics that can be empty.
253
+ # @return [Array<Metric>] List of metrics that can be empty.
180
254
  # @example
181
255
  # # Filter by type and match on metrics id
182
256
  # client.list_metrics_for_resource(wild_fly, type: 'GAUGE', match: 'Metrics~Heap')
@@ -188,20 +262,229 @@ module Hawkular::Inventory
188
262
  the_feed = hawk_escape resource.feed
189
263
  the_id = hawk_escape resource.id
190
264
 
191
- ret = http_get('/feeds/' +
192
- the_feed + '/resources/' +
193
- the_id + '/metrics')
194
- val = []
195
- ret.each do |m|
265
+ ret = http_get("/feeds/#{the_feed}/resources/#{the_id}/metrics")
266
+ with_nils = ret.map do |m|
196
267
  metric_new = Metric.new(m)
197
268
  found = should_include?(metric_new, filter)
198
- val.push(metric_new) if found
269
+ metric_new if found
270
+ end
271
+ with_nils.compact
272
+ end
273
+
274
+ # Create a new feed
275
+ # @param [String] feed_id Id of a feed - required
276
+ # @param [String] feed_name A display name for the feed
277
+ # @return [Object]
278
+ def create_feed(feed_id, feed_name = nil)
279
+ feed = create_blueprint
280
+ feed[:id] = feed_id
281
+ feed[:name] = feed_name
282
+
283
+ begin
284
+ return http_post('/feeds/', feed)
285
+ rescue HawkularException => error
286
+ # 409 We already exist -> that is ok
287
+ if error.status_code == 409
288
+ the_feed = hawk_escape feed_id
289
+ http_get("/feeds/#{the_feed}")
290
+ else
291
+ raise
292
+ end
293
+ end
294
+ end
295
+
296
+ # Delete the feed with the passed feed id.
297
+ # @param feed Id of the feed to be deleted.
298
+ def delete_feed(feed)
299
+ the_feed = hawk_escape feed
300
+ http_delete("/feeds/#{the_feed}")
301
+ end
302
+
303
+ # Create a new resource type
304
+ # @param [String] feed_id Id of the feed to add the type to
305
+ # @param [String] type_id Id of the new type
306
+ # @param [String] type_name Name of the type
307
+ # @return [ResourceType] ResourceType object just created
308
+ def create_resource_type(feed_id, type_id, type_name)
309
+ the_feed = hawk_escape feed_id
310
+
311
+ type = create_blueprint
312
+ type[:id] = type_id
313
+ type[:name] = type_name
314
+
315
+ begin
316
+ http_post("/feeds/#{the_feed}/resourceTypes", type)
317
+ rescue HawkularException => error
318
+ # 409 We already exist -> that is ok
319
+ raise unless error.status_code == 409
320
+ ensure
321
+ the_type = hawk_escape type_id
322
+ res = http_get("/feeds/#{the_feed}/resourceTypes/#{the_type}")
199
323
  end
200
- val
324
+ ResourceType.new(res)
325
+ end
326
+
327
+ # Create a resource of a given type under a given feed. To retrieve that resource
328
+ # you need to call {#get_resource}
329
+ # @param [String] feed_id Id of the feed to add the resource to
330
+ # @param [String] type_path Path of the resource type of this resource
331
+ # @param [String] resource_id Id of the resource
332
+ # @param [String] resource_name Name of the resource
333
+ # @param [Hash<String,Object>] properties Additional properties. Those are not the config-properties
334
+ # TODO allow to create this as child of another resource
335
+ def create_resource(feed_id, type_path, resource_id, resource_name = nil, properties = {})
336
+ the_feed = hawk_escape feed_id
337
+
338
+ res = create_blueprint
339
+ res[:properties] = properties
340
+ res[:id] = resource_id
341
+ res[:name] = resource_name
342
+ res[:resourceTypePath] = type_path
343
+
344
+ begin
345
+ http_post("/feeds/#{the_feed}/resources", res)
346
+ rescue HawkularException => error
347
+ # 409 We already exist -> that is ok
348
+ raise unless error.status_code == 409
349
+ end
350
+
351
+ get_resource feed_id, resource_id, false
352
+ end
353
+
354
+ # Return the resource object for the passed id
355
+ # @param [String] feed_id Id of the feed this resource belongs to
356
+ # @param [String] res_id Id of the resource to fetch
357
+ # @param [Boolean] fetch_resource_config Should the resource config data be fetched?
358
+ def get_resource(feed_id, res_id, fetch_resource_config = true)
359
+ the_feed = hawk_escape feed_id
360
+ the_resource = hawk_escape res_id
361
+
362
+ res = http_get("/feeds/#{the_feed}/resources/#{the_resource}")
363
+ if fetch_resource_config
364
+ p = get_config_data_for_resource(res_id, feed_id)
365
+ res['properties'].merge p['value'] unless p['value'].nil?
366
+ end
367
+ Resource.new(res)
368
+ end
369
+
370
+ # Create a new metric type for a feed
371
+ # @param [String] feed_id Id of the feed
372
+ # @param [String] metric_type_id Id of the metric type to create
373
+ # @param [String] type Type of the Metric. Allowed are GAUGE,COUNTER, AVAILABILITY
374
+ # @param [String] unit Unit of the metric
375
+ # @param [Numeric] collection_interval
376
+ # @return [MetricType] Type just created or the one from the server if it already existed.
377
+ def create_metric_type(feed_id, metric_type_id, type = 'GAUGE', unit = 'NONE', collection_interval = 60)
378
+ the_feed = hawk_escape feed_id
379
+
380
+ metric_kind = type.nil? ? 'GAUGE' : type.upcase
381
+ fail "Unknown type #{metric_kind}" unless %w(GAUGE COUNTER AVAILABILITY').include?(metric_kind)
382
+
383
+ mt = build_metric_type_hash(collection_interval, metric_kind, metric_type_id, unit)
384
+
385
+ begin
386
+ http_post("/feeds/#{the_feed}/metricTypes", mt)
387
+ rescue HawkularException => error
388
+ # 409 We already exist -> that is ok
389
+ raise unless error.status_code == 409
390
+ end
391
+
392
+ new_mt = http_get("/feeds/#{the_feed}/metricTypes/#{metric_type_id}")
393
+
394
+ MetricType.new(new_mt)
395
+ end
396
+
397
+ # Create a Metric and associate it with a resource.
398
+ # @param [String] feed_id Id of the feed
399
+ # @param [String] metric_id Id of the metric
400
+ # @param [String] type_path Full path of the MetricType
401
+ # @param [String] resource_id Id of the resource to associate the metric with
402
+ # @param [String] metric_name a (display) name for the metric. If nil, #metric_id is used.
403
+ # @return [Metric] The metric created or if it already existed the version from the server
404
+ def create_metric_for_resource(feed_id, metric_id, type_path, resource_id, metric_name = nil)
405
+ the_feed = hawk_escape feed_id
406
+ the_resource = hawk_escape resource_id
407
+
408
+ m = {}
409
+ m['id'] = metric_id
410
+ m['name'] = (metric_name.nil?) ? metric_id : metric_name
411
+ m['metricTypePath'] = type_path
412
+
413
+ begin
414
+ http_post("/feeds/#{the_feed}/metrics", m)
415
+ rescue HawkularException => error
416
+ # 409 We already exist -> that is ok
417
+ raise unless error.status_code == 409
418
+ end
419
+
420
+ ret = http_get("/feeds/#{the_feed}/metrics/#{metric_id}")
421
+ the_metric = Metric.new(ret)
422
+
423
+ begin
424
+ http_post("/feeds/#{the_feed}/resources/#{the_resource}/metrics", [the_metric.path])
425
+ rescue HawkularException => error
426
+ # 409 We already exist -> that is ok
427
+ raise unless error.status_code == 409
428
+ end
429
+ the_metric
430
+ end
431
+
432
+ # Listen on inventory changes
433
+ # @param [String] type Type of entity for which we want the events.
434
+ # Allowed values: resource, metric, resourcetype, metrictype, feed, environment, operationtype, metadatapack
435
+ # @param [String] action What types of events are we interested in.
436
+ # Allowed values: created, updated, deleted, copied, registered
437
+ def events(type = 'resource', action = 'created')
438
+ tenant_id = get_tenant
439
+ url = "#{entrypoint.gsub(/https?/, 'ws')}/ws/events?tenantId=#{tenant_id}&type=#{type}&action=#{action}"
440
+ @ws = WebSocket::Client::Simple.connect url do |client|
441
+ client.on :message do |msg|
442
+ parsed_message = JSON.parse(msg.data)
443
+ entity = case type
444
+ when 'resource'
445
+ Resource.new(parsed_message)
446
+ when 'resourcetype'
447
+ ResourceType.new(parsed_message)
448
+ when 'metric'
449
+ Metric.new(parsed_message)
450
+ when 'metrictype'
451
+ MetricType.new(parsed_message)
452
+ else
453
+ BaseEntity.new(parsed_message)
454
+ end
455
+ yield entity
456
+ end
457
+ end
458
+ end
459
+
460
+ # Stop listening on inventory events.
461
+ # this method closes the web socket connection
462
+ def no_more_events!
463
+ @ws.close
201
464
  end
202
465
 
203
466
  private
204
467
 
468
+ # Creates a hash with the fields required by the Blueprint api in Hawkular-Inventory
469
+ def create_blueprint
470
+ res = {}
471
+ res[:properties] = {}
472
+ res[:id] = nil
473
+ res[:name] = nil
474
+ res[:outgoing] = {}
475
+ res[:incoming] = {}
476
+ res
477
+ end
478
+
479
+ def build_metric_type_hash(collection_interval, metric_kind, metric_type_id, unit)
480
+ mt = {}
481
+ mt['id'] = metric_type_id
482
+ mt['type'] = metric_kind
483
+ mt['unit'] = unit.nil? ? 'NONE' : unit.upcase
484
+ mt['collectionInterval'] = collection_interval.nil? ? 60 : collection_interval
485
+ mt
486
+ end
487
+
205
488
  def should_include?(metric_new, filter)
206
489
  found = true
207
490
  if filter.empty?
@@ -214,32 +497,31 @@ module Hawkular::Inventory
214
497
  end
215
498
  end
216
499
 
217
- # A ResourceType is like a class definition for {Resource}s
218
- # ResourceTypes are currently unique per feed, but one can assume
219
- # that a two types with the same name of two different feeds are
220
- # (more or less) the same.
221
- class ResourceType
222
- # @return [String] Full path of the type
500
+ # A Basic inventory entity with id, name, path and optional properties
501
+ class BaseEntity
502
+ # @return [String] Full path of the entity
223
503
  attr_reader :path
224
- # @return [String] Name of the type
504
+ # @return [String] Name of the entity
225
505
  attr_reader :name
226
- # @return [String] Name of the type
506
+ # @return [String] Name of the entity
227
507
  attr_reader :id
228
- # @return [String] Feed this type belongs to
508
+ # @return [String] Feed this entity belongs to (or nil in case of a feedless entity)
229
509
  attr_reader :feed
230
- # @return [String] Environment this Type belongs to - currently unused
510
+ # @return [String] Name of the environment for this entity
231
511
  attr_reader :env
232
- # @return [String] Properties of this type
512
+ # @return [String] Properties of this entity
233
513
  attr_reader :properties
234
514
 
235
- def initialize(rt_hash)
236
- @id = rt_hash['id']
237
- @path = rt_hash['path']
238
- @name = rt_hash['name'] || rt_hash['id']
239
- @properties = rt_hash['properties']
240
- @_hash = rt_hash.dup
515
+ def initialize(hash)
516
+ @id = hash['id']
517
+ @path = hash['path']
518
+ @name = hash['name'] || @id
519
+ @properties = hash['properties'] || {}
520
+ @_hash = hash.dup
241
521
 
242
- tmp = path.split('/')
522
+ return if @path.nil?
523
+
524
+ tmp = @path.split('/')
243
525
  tmp.each do |pair|
244
526
  (key, val) = pair.split(';')
245
527
  case key
@@ -258,89 +540,59 @@ module Hawkular::Inventory
258
540
  end
259
541
  end
260
542
 
543
+ # A ResourceType is like a class definition for {Resource}s
544
+ # ResourceTypes are currently unique per feed, but one can assume
545
+ # that a two types with the same name of two different feeds are
546
+ # (more or less) the same.
547
+ class ResourceType < BaseEntity
548
+ def initialize(rt_hash)
549
+ super(rt_hash)
550
+ end
551
+ end
552
+
261
553
  # A Resource is an instantiation of a {ResourceType}
262
- class Resource
263
- # @return [String] Full path of the resource including feed id
264
- attr_reader :path
265
- # @return [String] Name of the resource
266
- attr_reader :name
267
- # @return [String] Name of the resource
268
- attr_reader :id
269
- # @return [String] Name of the feed for this resource
270
- attr_reader :feed
271
- # @return [String] Name of the environment for this resource -- currently unused
272
- attr_reader :env
554
+ class Resource < BaseEntity
273
555
  # @return [String] Full path of the {ResourceType}
274
556
  attr_reader :type_path
275
- # @return [Hash<String,Object>] Hash with additional, resource specific properties
276
- attr_reader :properties
277
557
 
278
558
  def initialize(res_hash)
279
- @id = res_hash['id']
280
- @path = res_hash['path']
281
- @properties = res_hash['properties'] || {}
559
+ super(res_hash)
560
+ @type = res_hash['type']
282
561
  @type_path = res_hash['type']['path']
283
- @_hash = res_hash
284
-
285
- tmp = @path.split('/')
286
- tmp.each do |pair|
287
- (key, val) = pair.split(';')
288
- case key
289
- when 'f'
290
- @feed = val
291
- when 'e'
292
- @env = val
293
- when 'n'
294
- @name = val.nil? ? id : val
295
- end
296
- end
297
- self
298
562
  end
563
+ end
299
564
 
300
- def to_h
301
- @_hash.deep_dup
565
+ class MetricType < BaseEntity
566
+ # @return [String] GAUGE, COUNTER, etc.
567
+ attr_reader :type
568
+ # @return [String] metric unit such as NONE, BYTES, etc.
569
+ attr_reader :unit
570
+ # @return [Long] collection interval in seconds
571
+ attr_reader :collection_interval
572
+
573
+ def initialize(type_hash)
574
+ super(type_hash)
575
+ @type = type_hash['type']
576
+ @unit = type_hash['unit']
577
+ @collection_interval = type_hash['collectionInterval']
302
578
  end
303
579
  end
304
580
 
305
581
  # Definition of a Metric inside the inventory.
306
- class Metric
307
- # @return [String] Full path of the metric (definition)
308
- attr_reader :path
309
- # @return [String] Name of the metric
310
- attr_reader :name
311
- attr_reader :id
312
- attr_reader :feed
313
- attr_reader :env
582
+ class Metric < BaseEntity
583
+ # @return [String] GAUGE, COUNTER, etc.
314
584
  attr_reader :type
585
+ # @return [String] metric unit such as NONE, BYTES, etc.
315
586
  attr_reader :unit
316
587
  # @return [Long] collection interval in seconds
317
588
  attr_reader :collection_interval
318
589
 
319
590
  def initialize(metric_hash)
320
- @id = metric_hash['id']
321
- @path = metric_hash['path']
322
- @name = metric_hash['name'] || @id
323
- @_hash = metric_hash.dup
324
-
325
- tmp = path.split('/')
326
- tmp.each do |pair|
327
- (key, val) = pair.split(';')
328
- case key
329
- when 'f'
330
- @feed = val
331
- when 'e'
332
- @env = val
333
- when 'n'
334
- @name = val.nil? ? id : val
335
- end
336
- end
591
+ super(metric_hash)
337
592
  @type = metric_hash['type']['type']
593
+ @type_path = metric_hash['type']['path']
338
594
  @unit = metric_hash['type']['unit']
339
- @collection_interval = metric_hash['collectionInterval']
340
- end
341
-
342
- def to_h
343
- @_hash.dup
595
+ @collection_interval = metric_hash['type']['collectionInterval']
344
596
  end
345
597
  end
346
598
 
@@ -375,4 +627,81 @@ module Hawkular::Inventory
375
627
  hash
376
628
  end
377
629
  end
630
+
631
+ class CanonicalPath
632
+ attr_reader :tenant_id
633
+ attr_reader :feed_id
634
+ attr_reader :environment_id
635
+ attr_reader :resource_ids
636
+ attr_reader :metric_id
637
+ attr_reader :resource_type_id
638
+ attr_reader :metric_type_id
639
+
640
+ def initialize(hash)
641
+ fail 'At least tenant_id must be specified' if hash[:tenant_id].to_s.strip.length == 0
642
+ @tenant_id = hash[:tenant_id]
643
+ @feed_id = hash[:feed_id]
644
+ @environment_id = hash[:environment_id]
645
+ @resource_type_id = hash[:resource_type_id]
646
+ @metric_type_id = hash[:metric_type_id]
647
+ @resource_ids = hash[:resource_ids]
648
+ @metric_id = hash[:metric_id]
649
+ end
650
+
651
+ # rubocop:disable Metrics/CyclomaticComplexity
652
+ def self.parse(path)
653
+ fail 'CanonicalPath must not be nil or emtpy' if path.to_s.strip.length == 0
654
+ tmp = path.split('/')
655
+ hash = {}
656
+ tmp.each do |pair|
657
+ (key, val) = pair.split(';')
658
+ case key
659
+ when 't'
660
+ hash[:tenant_id] = val
661
+ when 'f'
662
+ hash[:feed_id] = val
663
+ when 'e'
664
+ hash[:environment_id] = val
665
+ when 'm'
666
+ hash[:metric_id] = val
667
+ when 'r'
668
+ hash[:resource_ids] = [] if hash[:resource_ids].nil?
669
+ hash[:resource_ids].push(val)
670
+ when 'mt'
671
+ hash[:metric_type_id] = val
672
+ when 'rt'
673
+ hash[:resource_type_id] = val
674
+ end
675
+ end
676
+ CanonicalPath.new(hash)
677
+ end
678
+ # rubocop:enable Metrics/CyclomaticComplexity
679
+
680
+ def ==(other)
681
+ self.eql?(other) || other.class == self.class && other.state == state
682
+ end
683
+
684
+ def to_s
685
+ ret = "/t;#{@tenant_id}"
686
+ ret += "/f;#{@feed_id}" unless @feed_id.nil?
687
+ ret += "/e;#{@environment_id}" unless @environment_id.nil?
688
+ ret += "/rt;#{@resource_type_id}" unless @resource_type_id.nil?
689
+ ret += "/mt;#{@metric_type_id}" unless @metric_type_id.nil?
690
+ ret += "/m;#{@metric_id}" unless @metric_id.nil?
691
+ ret += resources_chunk.to_s
692
+ ret
693
+ end
694
+
695
+ protected
696
+
697
+ def state
698
+ [@tenant_id, @feed_id, @environment_id, @resource_ids, @metric_id, @metric_type_id, @resource_type_id]
699
+ end
700
+
701
+ private
702
+
703
+ def resources_chunk
704
+ @resource_ids.map { |r| "/r;#{r}" }.join unless @resource_ids.nil?
705
+ end
706
+ end
378
707
  end