hawkular-client 3.0.2 → 5.0.0

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.
@@ -1,16 +1,8 @@
1
1
  require 'hawkular/base_client'
2
- require 'websocket-client-simple'
3
- require 'json'
4
- require 'zlib'
5
- require 'stringio'
6
2
 
7
3
  require 'hawkular/inventory/entities'
8
4
 
9
5
  # Inventory module provides access to the Hawkular Inventory REST API.
10
- # @see http://www.hawkular.org/docs/rest/rest-inventory.html
11
- #
12
- # @note While Inventory supports 'environments', they are not used currently
13
- # and thus set to 'test' as default value.
14
6
  module Hawkular::Inventory
15
7
  # Client class to interact with Hawkular Inventory
16
8
  class Client < Hawkular::BaseClient
@@ -21,12 +13,13 @@ module Hawkular::Inventory
21
13
  # http://localhost:8080/hawkular/inventory
22
14
  # @param credentials [Hash{String=>String}] Hash of username, password, token(optional)
23
15
  # @param options [Hash{String=>String}] Additional rest client options
24
- def initialize(entrypoint = nil, credentials = {}, options = {})
25
- entrypoint = normalize_entrypoint_url entrypoint, 'hawkular/metrics'
16
+ def initialize(entrypoint = nil, credentials = {}, options = {}, page_size = nil)
17
+ entrypoint = normalize_entrypoint_url entrypoint, 'hawkular/inventory'
26
18
  @entrypoint = entrypoint
27
19
  super(entrypoint, credentials, options)
28
20
  version = fetch_version_and_status['Implementation-Version']
29
21
  @version = version.scan(/\d+/).map(&:to_i)
22
+ @page_size = page_size || 100
30
23
  end
31
24
 
32
25
  # Creates a new Inventory Client
@@ -34,261 +27,71 @@ module Hawkular::Inventory
34
27
  # entrypoint: http://localhost:8080/hawkular/inventory
35
28
  # and another sub-hash containing the hash with username[String], password[String], token(optional)
36
29
  def self.create(hash)
37
- fail 'no parameter ":entrypoint" given' unless hash[:entrypoint]
30
+ fail Hawkular::ArgumentError, 'no parameter ":entrypoint" given' unless hash[:entrypoint]
31
+
38
32
  hash[:credentials] ||= {}
39
33
  hash[:options] ||= {}
40
- Client.new(hash[:entrypoint], hash[:credentials], hash[:options])
34
+ Client.new(hash[:entrypoint], hash[:credentials], hash[:options], hash[:page_size])
41
35
  end
42
36
 
43
- # List feeds in the system
44
- # @return [Array<String>] List of feed ids
45
- def list_feeds
46
- ret = http_get('/strings/tags/module:inventory,feed:*')
47
- return [] unless ret.key? 'feed'
48
- ret['feed']
37
+ # Get single resource by id
38
+ # @return Resource the resource
39
+ def resource(id)
40
+ hash = http_get(url('/resources/%s', id))
41
+ Resource.new(hash)
42
+ rescue ::Hawkular::Exception => e
43
+ return if e.cause.is_a?(::RestClient::NotFound)
44
+ raise
49
45
  end
50
46
 
51
- # List resource types for the given feed
52
- # @param [String] feed_id The id of the feed the type lives under
53
- # @return [Array<ResourceType>] List of types, that can be empty
54
- def list_resource_types(feed_id)
55
- fail 'Feed id must be given' unless feed_id
56
- feed_path = feed_cp(feed_id)
57
- response = http_post(
58
- '/strings/raw/query',
59
- fromEarliest: true,
60
- order: 'DESC',
61
- tags: "#{feed_path.to_tags},type:rt")
62
- structures = extract_structures_from_body(response)
63
- structures.map do |rt|
64
- root_hash = entity_json_to_hash(-> (id) { feed_path.resource_type(id) }, rt['inventoryStructure'], false)
65
- ResourceType.new(root_hash)
66
- end
47
+ # Get resource by id with its complete subtree
48
+ # @return Resource the resource
49
+ def resource_tree(id)
50
+ hash = http_get(url('/resources/%s/tree', id))
51
+ Resource.new(hash)
67
52
  end
68
53
 
69
- # List metric types for the given feed
70
- # @param [String] feed_id The id of the feed the type lives under
71
- # @return [Array<MetricType>] List of types, that can be empty
72
- def list_metric_types(feed_id)
73
- fail 'Feed id must be given' unless feed_id
74
- feed_path = feed_cp(feed_id)
75
- response = http_post(
76
- '/strings/raw/query',
77
- fromEarliest: true,
78
- order: 'DESC',
79
- tags: "#{feed_path.to_tags},type:mt")
80
- structures = extract_structures_from_body(response)
81
- structures.map do |mt|
82
- root_hash = entity_json_to_hash(-> (id) { feed_path.metric_type(id) }, mt['inventoryStructure'], false)
83
- MetricType.new(root_hash)
84
- end
54
+ # Get childrens of a resource
55
+ # @return Children of a resource
56
+ def children_resources(parent_id)
57
+ http_get(url('/resources/%s/children', parent_id))['results'].map { |r| Resource.new(r) }
85
58
  end
86
59
 
87
- # Return all resources for a feed
88
- # @param [String] feed_id Id of the feed that hosts the resources
89
- # @param [Boolean] fetch_properties Should the config data be fetched too
90
- # @return [Array<Resource>] List of resources, which can be empty.
91
- def list_resources_for_feed(feed_id, fetch_properties = false, filter = {})
92
- fail 'Feed id must be given' unless feed_id
93
- feed_path = feed_cp(feed_id)
94
- response = http_post(
95
- '/strings/raw/query',
96
- fromEarliest: true,
97
- order: 'DESC',
98
- tags: "#{feed_path.to_tags},type:r")
99
- structures = extract_structures_from_body(response)
100
- to_filter = structures.map do |r|
101
- root_hash = entity_json_to_hash(-> (id) { feed_path.down(id) }, r['inventoryStructure'], fetch_properties)
102
- Resource.new(root_hash)
103
- end
104
- filter_entities(to_filter, filter)
60
+ # Get parent of a resource
61
+ # @return Resource the parent resource, or nil if the provided ID referred to a root resource
62
+ def parent(id)
63
+ hash = http_get(url('/resources/%s/parent', id))
64
+ Resource.new(hash) if hash
105
65
  end
106
66
 
107
- # List the resources for the passed resource type. The representation for
108
- # resources under a feed are sparse and additional data must be retrieved separately.
109
- # It is possible though to also obtain runtime properties by setting #fetch_properties to true.
110
- # @param [String] resource_type_path Canonical path of the resource type. Can be obtained from {ResourceType}.path.
111
- # Must not be nil. The tenant_id in the canonical path doesn't have to be there.
112
- # @param [Boolean] fetch_properties Shall additional runtime properties be fetched?
113
- # @return [Array<Resource>] List of resources. Can be empty
114
- def list_resources_for_type(resource_type_path, fetch_properties = false)
115
- path = CanonicalPath.parse_if_string(resource_type_path)
116
- fail 'Feed id must be given' unless path.feed_id
117
- fail 'Resource type must be given' unless path.resource_type_id
118
-
119
- # Fetch metrics by tag
120
- feed_path = feed_cp(URI.unescape(path.feed_id))
121
- resource_type_id = URI.unescape(path.resource_type_id)
122
- escaped_for_regex = Regexp.quote("|#{resource_type_id}|")
123
- response = http_post(
124
- '/strings/raw/query',
125
- fromEarliest: true,
126
- order: 'DESC',
127
- tags: "#{feed_path.to_tags},type:r,restypes:.*#{escaped_for_regex}.*")
128
- structures = extract_structures_from_body(response)
129
- return [] if structures.empty?
130
-
131
- # Now find each collected resource path in their belonging InventoryStructure
132
- extract_resources_for_type(structures, feed_path, resource_type_id, fetch_properties)
67
+ # List root resources
68
+ # @return [Enumeration<Resource>] Lazy-loaded Enumeration of resources
69
+ def root_resources
70
+ resources root: 'true'
133
71
  end
134
72
 
135
- # Retrieve runtime properties for the passed resource
136
- # @param [String] resource_path Canonical path of the resource to read properties from.
137
- # @return [Hash<String,Object] Hash with additional data
138
- def get_config_data_for_resource(resource_path)
139
- path = CanonicalPath.parse_if_string(resource_path)
140
- raw_hash = get_raw_entity_hash(path)
141
- return {} unless raw_hash
142
- { 'value' => fetch_properties(raw_hash) }
143
- end
73
+ # List resources
74
+ # @param [Hash] filter options to filter the resource list
75
+ # @option filter :root If truthy, only get root resources
76
+ # @option filter :feedId Filter by feed id
77
+ # @option filter :typeId Filter by type id
78
+ # @return [Enumeration<Resource>] Lazy-loaded Enumeration of resources
79
+ def resources(filter = {})
80
+ filter[:root] = !filter[:root].nil? if filter.key? :root
81
+ filter[:maxResults] = @page_size
144
82
 
145
- # Obtain the child resources of the passed resource. In case of a WildFly server,
146
- # those would be Datasources, Deployments and so on.
147
- # @param [String] parent_res_path Canonical path of the resource to obtain children from.
148
- # @param [Boolean] recursive Whether to fetch also all the children of children of ...
149
- # @return [Array<Resource>] List of resources that are children of the given parent resource.
150
- # Can be empty
151
- def list_child_resources(parent_res_path, recursive = false)
152
- path = CanonicalPath.parse_if_string(parent_res_path)
153
- feed_id = path.feed_id
154
- fail 'Feed id must be given' unless feed_id
155
- entity_hash = get_raw_entity_hash(path)
156
- extract_child_resources([], path.to_s, entity_hash, recursive) if entity_hash
157
- end
158
-
159
- # List the metrics for the passed metric type. If feed is not passed in the path,
160
- # all the metrics across all the feeds of a given type will be retrieved
161
- # @param [String] metric_type_path Canonical path of the resource type to look for. Can be obtained from
162
- # {MetricType}.path. Must not be nil. The tenant_id in the canonical path doesn't have to be there.
163
- # @return [Array<Metric>] List of metrics. Can be empty
164
- def list_metrics_for_metric_type(metric_type_path)
165
- path = CanonicalPath.parse_if_string(metric_type_path)
166
- fail 'Feed id must be given' unless path.feed_id
167
- fail 'Metric type id must be given' unless path.metric_type_id
168
- feed_id = URI.unescape(path.feed_id)
169
- metric_type_id = URI.unescape(path.metric_type_id)
170
-
171
- feed_path = feed_cp(feed_id)
172
- escaped_for_regex = Regexp.quote("|#{metric_type_id}|")
173
- response = http_post(
174
- '/strings/raw/query',
175
- fromEarliest: true,
176
- order: 'DESC',
177
- tags: "#{feed_path.to_tags},type:r,mtypes:.*#{escaped_for_regex}.*")
178
- structures = extract_structures_from_body(response)
179
- return [] if structures.empty?
180
-
181
- # Now find each collected resource path in their belonging InventoryStructure
182
- metric_type = get_metric_type(path)
183
- extract_metrics_for_type(structures, feed_path, metric_type)
184
- end
185
-
186
- # List metric (definitions) for the passed resource. It is possible to filter down the
187
- # result by a filter to only return a subset. The
188
- # @param [String] resource_path Canonical path of the resource.
189
- # @param [Hash{Symbol=>String}] filter for 'type' and 'match'
190
- # Metric type can be one of 'GAUGE', 'COUNTER', 'AVAILABILITY'. If a key is missing
191
- # it will not be used for filtering
192
- # @return [Array<Metric>] List of metrics that can be empty.
193
- # @example
194
- # # Filter by type and match on metrics id
195
- # client.list_metrics_for_resource(wild_fly, type: 'GAUGE', match: 'Metrics~Heap')
196
- # # Filter by type only
197
- # client.list_metrics_for_resource(wild_fly, type: 'COUNTER')
198
- # # Don't filter, return all metric definitions
199
- # client.list_metrics_for_resource(wild_fly)
200
- def list_metrics_for_resource(resource_path, filter = {})
201
- path = CanonicalPath.parse_if_string(resource_path)
202
- raw_hash = get_raw_entity_hash(path)
203
- return [] unless raw_hash
204
- to_filter = []
205
- if (raw_hash.key? 'children') && (raw_hash['children'].key? 'metric') && !raw_hash['children']['metric'].empty?
206
- # Need to merge metric type info that we must grab from another place
207
- metric_types = list_metric_types(URI.unescape(path.feed_id))
208
- metric_types_index = {}
209
- metric_types.each { |mt| metric_types_index[mt.path] = mt }
210
- to_filter = raw_hash['children']['metric'].map do |m|
211
- metric_data = m['data']
212
- metric_data['path'] = "#{path}/m;#{metric_data['id']}"
213
- metric_type = metric_types_index[metric_data['metricTypePath']]
214
- Metric.new(metric_data, metric_type) if metric_type
215
- end
216
- to_filter = to_filter.select { |m| m }
83
+ fetch_func = lambda do |offset|
84
+ filter[:startOffSet] = offset
85
+ filter_query = '?' + filter.keys.join('=%s&') + '=%s' unless filter.empty?
86
+ http_get(url("/resources#{filter_query}", *filter.values))
217
87
  end
218
- filter_entities(to_filter, filter)
88
+ ResultFetcher.new(fetch_func).map { |r| Resource.new(r) }
219
89
  end
220
90
 
221
- # Return the resource object for the passed path
222
- # @param [String] resource_path Canonical path of the resource to fetch.
223
- # @param [Boolean] fetch_properties Should the resource config data be fetched?
224
- def get_resource(resource_path, fetch_properties = true)
225
- path = CanonicalPath.parse_if_string(resource_path)
226
- raw_hash = get_raw_entity_hash(path)
227
- unless raw_hash
228
- exception = HawkularException.new("Resource not found: #{resource_path}")
229
- fail exception
230
- end
231
- entity_hash = entity_json_to_hash(-> (_) { path }, raw_hash, fetch_properties)
232
- Resource.new(entity_hash)
233
- end
234
-
235
- # Return the resource type object for the passed path
236
- # @param [String] resource_type_path Canonical path of the resource type to fetch.
237
- def get_resource_type(resource_type_path)
238
- path = CanonicalPath.parse_if_string(resource_type_path)
239
- raw_hash = get_raw_entity_hash(path)
240
- unless raw_hash
241
- exception = HawkularException.new("Resource type not found: #{resource_type_path}")
242
- fail exception
243
- end
244
- entity_hash = entity_json_to_hash(-> (_) { path }, raw_hash, false)
245
- ResourceType.new(entity_hash)
246
- end
247
-
248
- # Return the metric type object for the passed path
249
- # @param [String] metric_type_path Canonical path of the metric type to fetch.
250
- def get_metric_type(metric_type_path)
251
- path = CanonicalPath.parse_if_string(metric_type_path)
252
- raw_hash = get_raw_entity_hash(path)
253
- unless raw_hash
254
- exception = HawkularException.new("Metric type not found: #{metric_type_path}")
255
- fail exception
256
- end
257
- entity_hash = entity_json_to_hash(-> (_) { path }, raw_hash, false)
258
- MetricType.new(entity_hash)
259
- end
260
-
261
- # List operation definitions (types) for a given resource type
262
- # @param [String] resource_type_path canonical path of the resource type entity
263
- # @return [Array<String>] List of operation type ids
264
- def list_operation_definitions(resource_type_path)
265
- path = CanonicalPath.parse_if_string(resource_type_path)
266
- fail 'Missing feed_id in resource_type_path' unless path.feed_id
267
- fail 'Missing resource_type_id in resource_type_path' unless path.resource_type_id
268
- response = http_post(
269
- '/strings/raw/query',
270
- fromEarliest: true,
271
- order: 'DESC',
272
- tags: path.to_tags)
273
- structures = extract_structures_from_body(response)
274
- res = {}
275
- structures.map { |rt| rt['inventoryStructure'] }
276
- .select { |rt| rt['children'] && rt['children']['operationType'] }
277
- .flat_map { |rt| rt['children']['operationType'] }
278
- .each do |ot|
279
- hash = optype_json_to_hash(ot)
280
- od = OperationDefinition.new hash
281
- res[od.name] = od
282
- end
283
- res
284
- end
285
-
286
- # List operation definitions (types) for a given resource
287
- # @param [String] resource_path canonical path of the resource entity
288
- # @return [Array<String>] List of operation type ids
289
- def list_operation_definitions_for_resource(resource_path)
290
- resource = get_resource(resource_path, false)
291
- list_operation_definitions(resource.type_path)
91
+ # List resources for type
92
+ # @return [Enumeration<Resource>] Lazy-loaded Enumeration of resources
93
+ def resources_for_type(type)
94
+ resources typeId: type
292
95
  end
293
96
 
294
97
  # Return version and status information for the used version of Hawkular-Inventory
@@ -297,176 +100,25 @@ module Hawkular::Inventory
297
100
  def fetch_version_and_status
298
101
  http_get('/status')
299
102
  end
103
+ end
300
104
 
301
- def feed_cp(feed_id)
302
- CanonicalPath.new(tenant_id: @tenant, feed_id: hawk_escape_id(feed_id))
303
- end
304
-
305
- private
306
-
307
- def filter_entities(entities, filter)
308
- entities.select do |entity|
309
- found = true
310
- if filter.empty?
311
- found = true
312
- else
313
- found = false unless filter[:type] == (entity.type) || filter[:type].nil?
314
- found = false unless filter[:match].nil? || entity.id.include?(filter[:match])
315
- end
316
- found
317
- end
318
- end
319
-
320
- def entity_json_to_hash(path_getter, json, fetch_properties)
321
- data = json['data']
322
- data['path'] = path_getter.call(data['id']).to_s
323
- if fetch_properties
324
- props = fetch_properties(json)
325
- data['properties'].merge! props if props
326
- end
327
- data
328
- end
329
-
330
- def fetch_properties(json)
331
- return unless (json.key? 'children') && (json['children'].key? 'dataEntity')
332
- config = json['children']['dataEntity'].find { |d| d['data']['id'] == 'configuration' }
333
- config['data']['value'] if config
334
- end
335
-
336
- def optype_json_to_hash(json)
337
- data = json['data']
338
- # Fetch parameterTypes
339
- if (json.key? 'children') && (json['children'].key? 'dataEntity')
340
- param_types = json['children']['dataEntity'].find { |d| d['data']['id'] == 'parameterTypes' }
341
- data['parameters'] = param_types['data']['value'] if param_types
342
- end
343
- data
344
- end
345
-
346
- def get_raw_entity_hash(path)
347
- c_path = CanonicalPath.parse_if_string(path)
348
- raw = http_post(
349
- '/strings/raw/query',
350
- fromEarliest: true,
351
- order: 'DESC',
352
- tags: c_path.to_tags
353
- )
354
- structure = extract_structure_from_body(raw)
355
- find_entity_in_tree(c_path, structure)
356
- end
357
-
358
- def find_entity_in_tree(fullpath, inventory_structure)
359
- entity = inventory_structure
360
- if fullpath.resource_ids
361
- relative = fullpath.resource_ids.drop(1)
362
- relative.each do |child|
363
- if (entity.key? 'children') && (entity['children'].key? 'resource')
364
- unescaped = URI.unescape(child)
365
- entity = entity['children']['resource'].find { |r| r['data']['id'] == unescaped }
366
- else
367
- entity = nil
368
- break
369
- end
370
- end
371
- end
372
- if fullpath.metric_id
373
- if (entity.key? 'children') && (entity['children'].key? 'metric')
374
- unescaped = URI.unescape(fullpath.metric_id)
375
- entity = entity['children']['metric'].find { |r| r['data']['id'] == unescaped }
376
- else
377
- entity = nil
378
- end
379
- end
380
- entity
381
- end
382
-
383
- def extract_child_resources(arr, path, parent_hash, recursive)
384
- c_path = CanonicalPath.parse_if_string(path)
385
- if (parent_hash.key? 'children') && (parent_hash['children'].key? 'resource')
386
- parent_hash['children']['resource'].each do |r|
387
- entity = entity_json_to_hash(-> (id) { c_path.down(id) }, r, false)
388
- arr.push(Resource.new(entity))
389
- extract_child_resources(arr, entity['path'], r, true) if recursive
390
- end
391
- end
392
- arr
393
- end
394
-
395
- def extract_resources_for_type(structures, feed_path, resource_type_id, fetch_properties)
396
- matching_resources = []
397
- structures.each do |full_struct|
398
- next unless full_struct.key? 'typesIndex'
399
- next unless full_struct['typesIndex'].key? resource_type_id
400
- inventory_structure = full_struct['inventoryStructure']
401
- root_path = feed_path.down(inventory_structure['data']['id'])
402
- full_struct['typesIndex'][resource_type_id].each do |relative_path|
403
- if relative_path.empty?
404
- # Root resource
405
- resource = entity_json_to_hash(-> (id) { feed_path.down(id) }, inventory_structure, fetch_properties)
406
- matching_resources.push(Resource.new(resource))
407
- else
408
- # Search for child
409
- fullpath = CanonicalPath.parse("#{root_path}/#{relative_path}")
410
- resource_json = find_entity_in_tree(fullpath, inventory_structure)
411
- if resource_json
412
- resource = entity_json_to_hash(-> (_) { fullpath }, resource_json, fetch_properties)
413
- matching_resources.push(Resource.new(resource))
414
- end
415
- end
416
- end
417
- end
418
- matching_resources
419
- end
420
-
421
- def extract_metrics_for_type(structures, feed_path, metric_type)
422
- matching_metrics = []
423
- structures.each do |full_struct|
424
- next unless full_struct.key? 'metricTypesIndex'
425
- next unless full_struct['metricTypesIndex'].key? metric_type.id
426
- inventory_structure = full_struct['inventoryStructure']
427
- root_path = feed_path.down(inventory_structure['data']['id'])
428
- full_struct['metricTypesIndex'][metric_type.id].each do |relative_path|
429
- # Search for child
430
- fullpath = CanonicalPath.parse("#{root_path}/#{relative_path}")
431
- metric_json = find_entity_in_tree(fullpath, inventory_structure)
432
- if metric_json
433
- metric_hash = entity_json_to_hash(-> (_) { fullpath }, metric_json, false)
434
- matching_metrics.push(Metric.new(metric_hash, metric_type))
435
- end
436
- end
437
- end
438
- matching_metrics
439
- end
105
+ # Lazy fetching results, based on Inventory "ResultSet" model
106
+ class ResultFetcher
107
+ include Enumerable
440
108
 
441
- def extract_structure_from_body(response_body_array)
442
- # Expecting only 1 structure (but may have several chunks)
443
- structures = extract_structures_from_body(response_body_array)
444
- structures[0]['inventoryStructure'] unless structures.empty?
109
+ def initialize(fetcher)
110
+ @fetcher = fetcher
445
111
  end
446
112
 
447
- def extract_structures_from_body(response_body_array)
448
- response_body_array.map { |element| rebuild_from_chunks(element['data']) }
449
- .select { |full| full } # evict nil
450
- .map { |full| decompress(full) }
451
- end
452
-
453
- def rebuild_from_chunks(data_node)
454
- return if data_node.empty?
455
- master_data = data_node[0]
456
- return Base64.decode64(master_data['value']) unless (master_data.key? 'tags') &&
457
- (master_data['tags'].key? 'chunks')
458
- last_chunk = master_data['tags']['chunks'].to_i - 1
459
- all = Base64.decode64(master_data['value'])
460
- return if all.empty?
461
- (1..last_chunk).inject(all) do |full, chunk_id|
462
- slave_data = data_node[chunk_id]
463
- full.concat(Base64.decode64(slave_data['value']))
113
+ def each
114
+ offset = 0
115
+ loop do
116
+ result_set = @fetcher.call(offset)
117
+ results = result_set['results']
118
+ results.each { |r| yield(r) }
119
+ offset += results.length
120
+ break if offset >= result_set['resultSize']
464
121
  end
465
122
  end
466
-
467
- def decompress(raw)
468
- gz = Zlib::GzipReader.new(StringIO.new(raw))
469
- JSON.parse(gz.read)
470
- end
471
123
  end
472
124
  end