hawkular-client 0.2.0 → 0.2.1

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.
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