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
@@ -43,12 +43,12 @@ module Hawkular::Metrics
43
43
  # types (counters, gauges, availabilities).
44
44
  class Metrics
45
45
  # @param client [Client]
46
- # @param metricType [String] metric type (one of "counter", "gauge", "availability")
46
+ # @param metric_type [String] metric type (one of "counter", "gauge", "availability")
47
47
  # @param resource [String] REST resource name for accessing metrics
48
48
  # of given type (one of "counters", "gauges", "availability")
49
- def initialize(client, metricType, resource)
49
+ def initialize(client, metric_type, resource)
50
50
  @client = client
51
- @type = metricType
51
+ @type = metric_type
52
52
  @resource = resource
53
53
  end
54
54
 
@@ -84,9 +84,9 @@ module Hawkular::Metrics
84
84
  end
85
85
 
86
86
  # update tags for given metric definition
87
- # @param metricDefinition [MetricDefinition]
88
- def update_tags(metricDefinition)
89
- @client.http_put("/#{@resource}/#{metricDefinition.id}/tags", metricDefinition.hash[:tags])
87
+ # @param metric_definition [MetricDefinition]
88
+ def update_tags(metric_definition)
89
+ @client.http_put("/#{@resource}/#{metric_definition.id}/tags", metric_definition.hash[:tags])
90
90
  end
91
91
 
92
92
  # Push metric data
@@ -114,10 +114,15 @@ module Hawkular::Metrics
114
114
  # @param starts [Integer] optional timestamp (default now - 8h)
115
115
  # @param ends [Integer] optional timestamp (default now)
116
116
  # @param bucketDuration [String] optional interval (default no aggregation)
117
+ # @param percentiles [String] optional percentiles to calculate
118
+ # @param limit [Integer] optional limit the number of data points returned
119
+ # @param order [String] optional Data point sort order, based on timestamp (ASC, DESC)
117
120
  # @return [Array[Hash]] datapoints
118
121
  # @see #push_data #push_data for datapoint detail
119
- def get_data(id, starts: nil, ends: nil, bucketDuration: nil, buckets: nil)
120
- params = { start: starts, end: ends, bucketDuration: bucketDuration, buckets: buckets }
122
+ def get_data(id, starts: nil, ends: nil, bucketDuration: nil, buckets: nil, percentiles: nil, limit: nil,
123
+ order: nil)
124
+ params = { start: starts, end: ends, bucketDuration: bucketDuration, buckets: buckets,
125
+ percentiles: percentiles, limit: limit, order: order }
121
126
  resp = @client.http_get("/#{@resource}/#{ERB::Util.url_encode(id)}/data/?" +
122
127
  encode_params(params))
123
128
  resp.is_a?(Array) ? resp : [] # API returns no content (empty Hash) instead of empty array
@@ -146,7 +151,7 @@ module Hawkular::Metrics
146
151
  end
147
152
  end
148
153
 
149
- # Class that interracts with "gauge" metric types
154
+ # Class that interacts with "gauge" metric types
150
155
  class Gauges < Metrics
151
156
  # @param client [Client]
152
157
  def initialize(client)
@@ -0,0 +1,247 @@
1
+ require 'hawkular'
2
+ require 'websocket-client-simple'
3
+ require 'json'
4
+
5
+ # Adding a method `perform` for each block so that we can write nice callbacks for this client
6
+ class Proc
7
+ def perform(callable, result)
8
+ call(Class.new do
9
+ method_name = callable.to_sym
10
+ define_method(method_name) { |&block| block.nil? ? true : block.call(result) }
11
+ define_method("#{method_name}?") { true }
12
+ # method_missing is here because we are not forcing the client to provide both success and error callbacks
13
+ # rubocop:disable Lint/NestedMethodDefinition
14
+ # https://github.com/bbatsov/rubocop/issues/2704
15
+ def method_missing(_method_name, *_args, &_block)
16
+ false
17
+ end
18
+ # rubocop:enable Lint/NestedMethodDefinition
19
+ end.new)
20
+ end
21
+ end
22
+
23
+ # Operations module allows invoking operation on the wildfly agent.
24
+ module Hawkular::Operations
25
+ # Client class to interact with the agent via websockets
26
+ class OperationsClient < Hawkular::BaseClient
27
+ include WebSocket::Client
28
+
29
+ attr_accessor :ws
30
+
31
+ # helper for parsing the "OperationName=json_payload" messages
32
+ class WebSocket::Frame::Data
33
+ def to_msg_hash
34
+ chunks = split('=', 2)
35
+ {
36
+ operationName: chunks[0],
37
+ data: JSON.parse(chunks[1])
38
+ }
39
+ rescue
40
+ {}
41
+ end
42
+ end
43
+
44
+ # Initialize new OperationsClient
45
+ #
46
+ # @param [Hash] args Arguments for client
47
+ #
48
+ # @option args [String] :host base url of Hawkular - e.g http://localhost:8080
49
+ # @option args [Hash{String=>String}] :credentials Hash of {username, password} or token
50
+ # @option args [Fixnum] :wait_time Time in seconds describing how long the constructor should block - handshake
51
+ #
52
+ # @example
53
+ # Hawkular::Operations::OperationsClient.new(credentials: {username: 'jdoe', password: 'password'})
54
+ def initialize(args)
55
+ args[:host] ||= 'localhost:8080'
56
+ args[:credentials] ||= {}
57
+ args[:wait_time] ||= 0.5
58
+ super(args[:host], args[:credentials])
59
+ # note: if we start using the secured WS, change the protocol to wss://
60
+ url = "ws://#{entrypoint}/hawkular/command-gateway/ui/ws"
61
+ @ws = Simple.connect url do |client|
62
+ client.on(:message, once: true) do |msg|
63
+ parsed_message = msg.data.to_msg_hash
64
+ puts parsed_message if ENV['HAWKULARCLIENT_LOG_RESPONSE']
65
+ case parsed_message[:operationName]
66
+ when 'WelcomeResponse'
67
+ @session_id = parsed_message[:data]['sessionId']
68
+ end
69
+ end
70
+ end
71
+ sleep args[:wait_time]
72
+ end
73
+
74
+ # Closes the WebSocket connection
75
+ def close_connection!
76
+ @ws.close
77
+ end
78
+
79
+ # Invokes a generic operation on the Wildfly agent
80
+ # (the operation name must be specified in the hash)
81
+ # Note: if success and failure callbacks are omitted, the client will not wait for the Response message
82
+ # @param hash [Hash{String=>Object}] a hash containing: resourcePath [String] denoting the resource on
83
+ # which the operation is about to run, operationName [String]
84
+ # @param callback [Block] callback that is run after the operation is done
85
+ def invoke_generic_operation(hash, &callback)
86
+ required = [:resourcePath, :operationName]
87
+ check_pre_conditions hash, required, &callback
88
+
89
+ invoke_operation_helper(hash, &callback)
90
+ end
91
+
92
+ # Invokes operation on the wildfly agent that has it's own message type
93
+ # @param operation_payload [Hash{String=>Object}] a hash containing: resourcePath [String] denoting
94
+ # the resource on which the operation is about to run
95
+ # @param operation_name [String] the name of the operation. This must correspond with the message type, they can be
96
+ # found here https://git.io/v2h1a (Use only the first part of the name without the Request/Response suffix), e.g.
97
+ # RemoveDatasource (and not RemoveDatasourceRequest)
98
+ # @param callback [Block] callback that is run after the operation is done
99
+ def invoke_specific_operation(operation_payload, operation_name, &callback)
100
+ fail 'Operation must be specified' if operation_name.nil?
101
+ required = [:resourcePath]
102
+ check_pre_conditions operation_payload, required, &callback
103
+
104
+ invoke_operation_helper(operation_payload, operation_name, &callback)
105
+ end
106
+
107
+ # Deploys a war file into WildFly
108
+ #
109
+ # @param [Hash] hash Arguments for deployment
110
+ # @option hash [String] :resource_path canonical path of the WildFly server into which we deploy
111
+ # @option hash [String] :destination_file_name resulting file name
112
+ # @option hash [String] :binary_content binary content representing the war file
113
+ # @option hash [String] :enabled whether the deployment should be enabled or not
114
+ #
115
+ # @param callback [Block] callback that is run after the operation is done
116
+ def add_deployment(hash, &callback)
117
+ hash[:enabled] ||= true
118
+ required = [:resource_path, :destination_file_name, :binary_content]
119
+ check_pre_conditions hash, required, &callback
120
+
121
+ operation_payload = prepare_payload_hash([:binary_content], hash)
122
+ invoke_operation_helper(operation_payload, 'DeployApplication', hash[:binary_content], &callback)
123
+ end
124
+
125
+ # Adds a new datasource
126
+ #
127
+ # @param [Hash] hash Arguments for the datasource
128
+ # @option hash [String] :resourcePath canonical path of the WildFly server into which we add datasource
129
+ # @option hash [String] :xaDatasource XA DS or normal
130
+ # @option hash [String] :datasourceName name of the datasource
131
+ # @option hash [String] :jndiName JNDI name
132
+ # @option hash [String] :driverName this is internal name of the driver in Hawkular
133
+ # @option hash [String] :driverClass class of driver
134
+ # @option hash [String] :connectionUrl jdbc connection string
135
+ # @option hash [String] :datasourceProperties optional properties
136
+ # @option hash [String] :username username to DB
137
+ # @option hash [String] :password password to DB
138
+ #
139
+ # @param callback [Block] callback that is run after the operation is done
140
+ def add_datasource(hash, &callback)
141
+ required = [:resourcePath, :xaDatasource, :datasourceName, :jndiName, :driverName, :driverClass, :connectionUrl]
142
+ check_pre_conditions hash, required, &callback
143
+
144
+ invoke_specific_operation(hash, 'AddDatasource', &callback)
145
+ end
146
+
147
+ # Adds a new datasource
148
+ #
149
+ # @param [Hash] hash Arguments for the datasource
150
+ # @option hash [String] :resource_path canonical path of the WildFly server into which we add driver
151
+ # @option hash [String] :driver_jar_name name of the jar file
152
+ # @option hash [String] :driver_name name of the jdbc driver (when adding datasource, this is the driverName)
153
+ # @option hash [String] :module_name name of the JBoss module into which the driver will be installed - 'foo.bar'
154
+ # @option hash [String] :driver_class fully specified java class of the driver - e.q. 'com.mysql.jdbc.Driver'
155
+ # @option hash [String] :binary_content driver jar file bits
156
+ #
157
+ # @param callback [Block] callback that is run after the operation is done
158
+ def add_jdbc_driver(hash, &callback)
159
+ required = [:resource_path, :driver_jar_name, :driver_name, :module_name, :driver_class, :binary_content]
160
+ check_pre_conditions hash, required, &callback
161
+
162
+ operation_payload = prepare_payload_hash([:binary_content], hash)
163
+ invoke_operation_helper(operation_payload, 'AddJdbcDriver', hash[:binary_content], &callback)
164
+ end
165
+
166
+ # Exports the JDR report
167
+ #
168
+ # @param [String] resource_path canonical path of the WildFly server
169
+ # @param callback [Block] callback that is run after the operation is done
170
+ def export_jdr(resource_path, &callback)
171
+ fail 'resource_path must be specified' if resource_path.nil?
172
+ check_pre_conditions(&callback)
173
+
174
+ invoke_specific_operation({ resourcePath: resource_path }, 'ExportJdr', &callback)
175
+ end
176
+
177
+ private
178
+
179
+ def invoke_operation_helper(operation_payload, operation_name = nil, binary_content = nil, &callback)
180
+ # fallback to generic 'ExecuteOperation' if nothing is specified
181
+ operation_name ||= 'ExecuteOperation'
182
+ add_credentials! operation_payload
183
+
184
+ handle_message(operation_name, operation_payload, &callback) unless callback.nil?
185
+
186
+ # sends a message that will actually run the operation
187
+ payload = "#{operation_name}Request=#{operation_payload.to_json}"
188
+ payload += binary_content unless binary_content.nil?
189
+ @ws.send payload, type: binary_content.nil? ? :text : :binary
190
+ end
191
+
192
+ def check_pre_conditions(hash = {}, params = [], &callback)
193
+ fail 'Handshake with server has not been done.' unless @ws.open?
194
+ fail 'Hash cannot be nil.' if hash.nil?
195
+ fail 'callback must have the perform method defined. include Hawkular::Operations' unless
196
+ callback.nil? || callback.respond_to?('perform')
197
+ params.each do |property|
198
+ fail "Hash property #{property} must be specified" if hash[property].nil?
199
+ end
200
+ end
201
+
202
+ def add_credentials!(hash)
203
+ hash[:authentication] = @credentials.delete_if { |_, v| v.nil? }
204
+ end
205
+
206
+ def handle_message(operation_name, operation_payload, &callback)
207
+ client = @ws
208
+ # register a callback handler
209
+ @ws.on :message do |msg|
210
+ parsed = msg.data.to_msg_hash
211
+ OperationsClient.log_message(parsed)
212
+ case parsed[:operationName]
213
+ when "#{operation_name}Response"
214
+ same_path = parsed[:data]['resourcePath'] == operation_payload[:resourcePath]
215
+ # failed operations don't return the operation name from some strange reason
216
+ same_name = parsed[:data]['operationName'] == operation_payload[:operationName]
217
+ if same_path # using the resource path as a correlation id
218
+ success = same_name && parsed[:data]['status'] == 'OK'
219
+ success ? callback.perform(:success, parsed[:data]) : callback.perform(:failure, parsed[:data]['message'])
220
+ client.remove_listener :message
221
+ end
222
+ when 'GenericErrorResponse'
223
+ OperationsClient.handle_error parsed, &callback
224
+ client.remove_listener :message
225
+ end
226
+ end
227
+ end
228
+
229
+ def self.handle_error(parsed_message, &callback)
230
+ callback.perform(:failure, parsed_message == {} ? 'error' : parsed_message[:data]['errorMessage'])
231
+ end
232
+
233
+ def self.log_message(message)
234
+ puts "\nreceived WebSocket msg: #{message}\n" if ENV['HAWKULARCLIENT_LOG_RESPONSE']
235
+ end
236
+
237
+ def prepare_payload_hash(ignored_params, hash)
238
+ # it filters out ignored params and convert keys from snake_case to camelCase
239
+ Hash[hash.select { |k, _| !ignored_params.include? k }.map { |k, v| [to_camel_case(k.to_s).to_sym, v] }]
240
+ end
241
+
242
+ def to_camel_case(str)
243
+ ret = str.split('_').collect(&:capitalize).join
244
+ ret[0, 1].downcase + ret[1..-1]
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,33 @@
1
+ require 'hawkular'
2
+
3
+ # Token module provides access to the Secret Store REST API.
4
+ module Hawkular::Token
5
+ # Client class to interact with the Secret Store
6
+ class TokenClient < Hawkular::BaseClient
7
+ # Create a new Secret Store client
8
+ # @param entrypoint [String] base url of Hawkular - e.g http://localhost:8080
9
+ # @param credentials [Hash{String=>String}] Hash of username, password
10
+ def initialize(entrypoint = 'http://localhost:8080', credentials = {})
11
+ super(entrypoint, credentials)
12
+ end
13
+
14
+ # Retrieve the tenant id for the passed credentials.
15
+ # If no credentials are passed, the ones from the constructor are used
16
+ # @param credentials [Hash{String=>String}] Hash of username, password, token(optional)
17
+ # @return [String] tenant id
18
+ def get_tokens(credentials = {})
19
+ creds = credentials.empty? ? @credentials : credentials
20
+ auth_header = { Authorization: base_64_credentials(creds) }
21
+ http_get('/secret-store/v1/tokens', auth_header)
22
+ end
23
+
24
+ def create_token(credentials = {}, persona = nil, name = 'Token created via Hawkular Ruby Client', expires_at = nil)
25
+ creds = credentials.empty? ? @credentials : credentials
26
+ auth_header = { Authorization: base_64_credentials(creds) }
27
+ auth_header['Hawkular-Persona'] = persona if persona
28
+
29
+ token_attributes = { expiresAt: expires_at, attributes: { name: name } }
30
+ http_post('/secret-store/v1/tokens/create', token_attributes, auth_header)
31
+ end
32
+ end
33
+ end
data/lib/version.rb CHANGED
@@ -4,5 +4,5 @@
4
4
  # @see https://github.com/hawkular
5
5
  module Hawkular
6
6
  # Version of the Hawkular Ruby Gem
7
- VERSION = '0.2.0'
7
+ VERSION = '0.2.1'
8
8
  end
@@ -5,57 +5,165 @@ module Hawkular::Alerts::RSpec
5
5
  ALERTS_BASE = 'http://localhost:8080/hawkular/alerts'
6
6
  creds = { username: 'jdoe', password: 'password' }
7
7
 
8
- describe 'Alert/Triggers', :vcr do
9
- it 'Should List Triggers' do
10
- client = Hawkular::Alerts::AlertsClient.new(ALERTS_BASE, creds)
8
+ describe 'Alert/Triggers', vcr: { decode_compressed_response: true } do
9
+ before(:each) do
10
+ @client = Hawkular::Alerts::AlertsClient.new(ALERTS_BASE, creds)
11
+ end
11
12
 
12
- triggers = client.list_triggers
13
+ it 'Should List Triggers' do
14
+ triggers = @client.list_triggers
13
15
 
14
16
  expect(triggers.size).to be(3)
15
17
  end
16
18
 
17
19
  it 'Should List Triggers for Tag' do
18
- client = Hawkular::Alerts::AlertsClient.new(ALERTS_BASE, creds)
19
-
20
- triggers = client.list_triggers [], ['resourceId|75bfdd05-d03d-481e-bf32-c724c7719d8b~Local']
20
+ triggers = @client.list_triggers [],
21
+ ['resourceId|75bfdd05-d03d-481e-bf32-c724c7719d8b~Local']
21
22
 
22
23
  expect(triggers.size).to be(7)
23
24
  end
24
25
 
25
26
  it 'Should List Triggers for Tags' do
26
- client = Hawkular::Alerts::AlertsClient.new(ALERTS_BASE, creds)
27
-
28
- triggers = client.list_triggers [], ['resourceId|75bfdd05-d03d-481e-bf32-c724c7719d8b~Local',
29
- 'app|MyShop']
27
+ triggers = @client.list_triggers [],
28
+ ['resourceId|75bfdd05-d03d-481e-bf32-c724c7719d8b~Local',
29
+ 'app|MyShop']
30
30
 
31
31
  expect(triggers.size).to be(7)
32
32
  end
33
33
 
34
34
  it 'Should List Triggers for ID' do
35
- client = Hawkular::Alerts::AlertsClient.new(ALERTS_BASE, creds)
36
-
37
- triggers = client.list_triggers ['75bfdd05-d03d-481e-bf32-c724c7719d8b~Local_jvm_pheap']
35
+ triggers = @client.list_triggers ['75bfdd05-d03d-481e-bf32-c724c7719d8b~Local_jvm_pheap']
38
36
 
39
37
  expect(triggers.size).to be(1)
40
38
  end
41
39
 
42
40
  it 'Should get a single metric Trigger' do
43
- client = Hawkular::Alerts::AlertsClient.new(ALERTS_BASE, creds)
44
-
45
- trigger = client.get_single_trigger('snert~Local_jvm_nheap')
41
+ trigger = @client.get_single_trigger('snert~Local_jvm_nheap')
46
42
 
47
43
  expect(trigger).not_to be_nil
48
44
  end
49
45
 
50
46
  it 'Should get a single Trigger with conditions' do
51
- client = Hawkular::Alerts::AlertsClient.new(ALERTS_BASE, creds)
52
-
53
- trigger = client.get_single_trigger 'snert~Local_jvm_nheap', true
47
+ trigger = @client.get_single_trigger 'snert~Local_jvm_nheap', true
54
48
 
55
49
  expect(trigger).not_to be_nil
56
50
  expect(trigger.conditions.size).to be(1)
57
51
  expect(trigger.dampenings.size).to be(1)
58
52
  end
53
+
54
+ it 'Should bulk load triggers' do
55
+ json = IO.read('spec/integration/hello-world-definitions.json')
56
+ trigger_hash = JSON.parse(json)
57
+
58
+ @client.bulk_import_triggers trigger_hash
59
+
60
+ trigger = @client.get_single_trigger 'hello-world-trigger', true
61
+ expect(trigger).not_to be_nil
62
+ expect(trigger.conditions.size).to be(2)
63
+ expect(trigger.dampenings.size).to be(0)
64
+
65
+ @client.delete_trigger(trigger.id)
66
+ end
67
+
68
+ it 'Should create a basic trigger with action' do
69
+ @client.create_action :email, 'send-via-email', 'notify-to-admins' => 'joe@acme.org'
70
+
71
+ # Create the trigger
72
+ t = Hawkular::Alerts::Trigger.new({})
73
+ t.enabled = true
74
+ t.id = 'my-cool-trigger'
75
+ t.name = 'Just a trigger'
76
+ t.severity = :HIGH
77
+ t.description = 'Just a test trigger'
78
+
79
+ # Create a condition
80
+ c = Hawkular::Alerts::Trigger::Condition.new({})
81
+ c.trigger_mode = :FIRING
82
+ c.type = :THRESHOLD
83
+ c.data_id = 'my-metric-id'
84
+ c.operator = :LT
85
+ c.threshold = 5
86
+
87
+ # Reference an action definition
88
+ a = Hawkular::Alerts::Trigger::Action.new({})
89
+ a.action_plugin = :email
90
+ a.action_id = 'send-via-email'
91
+ t.actions.push a
92
+
93
+ begin
94
+ ft = @client.create_trigger t, [c], nil
95
+
96
+ expect(ft).not_to be_nil
97
+
98
+ trigger = @client.get_single_trigger t.id, true
99
+ expect(trigger).not_to be_nil
100
+ expect(trigger.conditions.size).to be(1)
101
+ expect(trigger.dampenings.size).to be(0)
102
+ ensure
103
+ # rubocop:disable Lint/HandleExceptions
104
+ begin
105
+ @client.delete_trigger(t.id)
106
+ rescue
107
+ # I am not interested
108
+ end
109
+ begin
110
+ @client.delete_action(a.action_id, a.action_plugin)
111
+ rescue
112
+ # I am not interested
113
+ end
114
+ # rubocop:enable Lint/HandleExceptions
115
+ end
116
+ end
117
+
118
+ it 'Should get the action definitions' do
119
+ ret = @client.get_action_definition
120
+ expect(ret.size).to be(2)
121
+ expect(ret.key? 'email').to be_truthy
122
+
123
+ ret = @client.get_action_definition 'email'
124
+ expect(ret.size).to be(1)
125
+ expect(ret['email'].size).to be(7)
126
+
127
+ expect { @client.get_action_definition '-does-not-exist-' }
128
+ .to raise_error(Hawkular::BaseClient::HawkularException)
129
+ end
130
+
131
+ it 'Should create an action' do
132
+ @client.create_action 'email', 'my-id1', 'notify-to-admins' => 'joe@acme.org'
133
+ @client.delete_action 'email', 'my-id1'
134
+ end
135
+
136
+ it 'Should not create an action for unknown plugin' do
137
+ expect do
138
+ @client.create_action '-does-not-exist',
139
+ 'my-id2',
140
+ 'notify-to-admins' => 'joe@acme.org'
141
+ end.to raise_error(Hawkular::BaseClient::HawkularException)
142
+ end
143
+
144
+ it 'Should not create an action for unknown properties' do
145
+ begin
146
+ @client.create_action :email, 'my-id3', foo: 'bar'
147
+ ensure
148
+ @client.delete_action :email, 'my-id3'
149
+ end
150
+ end
151
+
152
+ it 'Should create an action for webhooks' do
153
+ begin
154
+ @client.get_action_definition 'webhook'
155
+
156
+ webhook_props = { 'url' => 'http://localhost:8080/bla', 'method' => 'POST' }
157
+ @client.create_action 'webhook', 'my-id1',
158
+ webhook_props
159
+ ret = @client.get_action 'webhook', 'my-id1'
160
+ expect(ret.action_plugin).to eq('webhook')
161
+ expect(ret.action_id).to eq('my-id1')
162
+
163
+ ensure
164
+ @client.delete_action 'webhook', 'my-id1'
165
+ end
166
+ end
59
167
  end
60
168
 
61
169
  describe 'Alert/Alerts', :vcr do
@@ -140,4 +248,148 @@ module Hawkular::Alerts::RSpec
140
248
  # expect(data).not_to be_nil
141
249
  # end
142
250
  # end
251
+
252
+ describe 'Alert/Events', :vcr do
253
+ VCR.configure do |c|
254
+ c.default_cassette_options = {
255
+ match_requests_on: [:method, VCR.request_matchers.uri_without_params(:startTime, :endTime)]
256
+ }
257
+ end
258
+
259
+ it 'Should list events' do
260
+ client = Hawkular::Alerts::AlertsClient.new(ALERTS_BASE, creds)
261
+
262
+ events = client.list_events('thin' => true)
263
+
264
+ expect(events).to_not be_nil
265
+ expect(events.size).to be(12)
266
+ end
267
+
268
+ it 'Should list events using criteria' do
269
+ now = Time.new.to_i
270
+ start_time = (now - 7_200) * 1000
271
+ end_time = now * 1000
272
+
273
+ client = Hawkular::Alerts::AlertsClient.new(ALERTS_BASE, creds)
274
+
275
+ events = client.list_events('startTime' => start_time, 'endTime' => end_time)
276
+
277
+ expect(events).to_not be_nil
278
+ expect(events.size).to be(1)
279
+ end
280
+
281
+ it 'Should not list events using criteria' do
282
+ client = Hawkular::Alerts::AlertsClient.new(ALERTS_BASE, creds)
283
+
284
+ events = client.list_events('startTime' => 0, 'endTime' => 1000)
285
+
286
+ expect(events).to_not be_nil
287
+ expect(events.size).to be(0)
288
+ end
289
+ end
290
+
291
+ describe 'Alert/EndToEnd', vcr: { decode_compressed_response: true } do
292
+ before(:each) do
293
+ @client = Hawkular::Alerts::AlertsClient.new(ALERTS_BASE, creds)
294
+ end
295
+
296
+ it 'Should create and fire a trigger' do
297
+ email_props = { to: 'joe@acme.org',
298
+ from: 'admin@acme.org' }
299
+ begin
300
+ @client.create_action 'email', 'send-via-email',
301
+ email_props
302
+ rescue
303
+ @client.delete_action 'email', 'send-via-email'
304
+ @client.create_action 'email', 'send-via-email',
305
+ email_props
306
+ end
307
+
308
+ webhook_props = { url: 'http://172.31.7.177/',
309
+ method: 'POST' }
310
+ begin
311
+ @client.create_action 'webhook', 'send-via-webhook',
312
+ webhook_props
313
+ rescue
314
+ @client.delete_action 'webhook', 'send-via-webhook'
315
+ @client.create_action 'webhook', 'send-via-webhook',
316
+ webhook_props
317
+ end
318
+
319
+ # Create the trigger
320
+ t = Hawkular::Alerts::Trigger.new({})
321
+ t.enabled = true
322
+ t.id = 'my-cool-email-trigger'
323
+ t.name = 'Just a trigger'
324
+ t.severity = :HIGH
325
+ t.description = 'Just a test trigger'
326
+
327
+ # Create a condition
328
+ c = Hawkular::Alerts::Trigger::Condition.new({})
329
+ c.trigger_mode = :FIRING
330
+ c.type = :THRESHOLD
331
+ c.data_id = 'my-metric-id1'
332
+ c.operator = :GT
333
+ c.threshold = 5
334
+
335
+ # Reference an action definition for email
336
+ a = Hawkular::Alerts::Trigger::Action.new({})
337
+ a.action_plugin = 'email'
338
+ a.action_id = 'send-via-email'
339
+ t.actions.push a
340
+
341
+ # Reference an action definition for webhook
342
+ a = Hawkular::Alerts::Trigger::Action.new({})
343
+ a.action_plugin = 'webhook'
344
+ a.action_id = 'send-via-webhook'
345
+ t.actions.push a
346
+
347
+ begin
348
+ ft = @client.create_trigger t, [c], nil
349
+
350
+ expect(ft).not_to be_nil
351
+
352
+ trigger = @client.get_single_trigger t.id, true
353
+ expect(trigger).not_to be_nil
354
+ expect(trigger.conditions.size).to be(1)
355
+ expect(trigger.dampenings.size).to be(0)
356
+
357
+ # Trigger is set up - send a metric value to trigger it.
358
+ metric_client = Hawkular::Metrics::Client.new('http://localhost:8080/hawkular/metrics',
359
+ creds)
360
+
361
+ data_point = { timestamp: Time.now.to_i * 1000, value: 42 }
362
+ data = [{ id: 'my-metric-id1', data: [data_point] }]
363
+
364
+ metric_client.push_data(gauges: data)
365
+
366
+ # wait 2s for the alert engine to work if we are live
367
+ sleep 2 if VCR.current_cassette.recording?
368
+
369
+ # see if alert has fired
370
+ alerts = @client.get_alerts_for_trigger 'my-cool-email-trigger'
371
+ expect(alerts).to_not be(nil)
372
+ alerts.each { |al| @client.resolve_alert(al.id, 'Heiko', 'Hello Ruby World :-)') }
373
+
374
+ ensure
375
+ # rubocop:disable Lint/HandleExceptions
376
+ begin
377
+ @client.delete_trigger(t.id)
378
+ rescue
379
+ # I am not interested
380
+ end
381
+ begin
382
+ @client.delete_action('webhook', 'send-via-webhook')
383
+ rescue
384
+ # I am not interested
385
+ end
386
+ begin
387
+ @client.delete_action('email', 'send-via-email')
388
+ rescue
389
+ # I am not interested
390
+ end
391
+ # rubocop:enable Lint/HandleExceptions
392
+ end
393
+ end
394
+ end
143
395
  end