hawkular-client 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +2 -1
  4. data/.rubocop.yml +19 -6
  5. data/CHANGES.rdoc +13 -3
  6. data/README.rdoc +9 -3
  7. data/hawkularclient.gemspec +8 -4
  8. data/lib/alerts/alerts_api.rb +202 -32
  9. data/lib/hawkular.rb +35 -67
  10. data/lib/hawkular_all.rb +2 -0
  11. data/lib/inventory/inventory_api.rb +459 -130
  12. data/lib/metrics/metric_api.rb +14 -9
  13. data/lib/operations/operations_api.rb +247 -0
  14. data/lib/tokens/tokens_api.rb +33 -0
  15. data/lib/version.rb +1 -1
  16. data/spec/integration/alerts_spec.rb +272 -20
  17. data/spec/integration/hello-world-definitions.json +46 -0
  18. data/spec/integration/inventory_spec.rb +253 -89
  19. data/spec/integration/metric_spec.rb +36 -0
  20. data/spec/integration/operations_spec.rb +420 -0
  21. data/spec/integration/tokens_spec.rb +45 -0
  22. data/spec/resources/driver.jar +0 -0
  23. data/spec/resources/sample.war +0 -0
  24. data/spec/spec_helper.rb +30 -6
  25. data/spec/unit/base_spec.rb +22 -1
  26. data/spec/unit/canonical_path_spec.rb +92 -0
  27. data/spec/vcr/vcr_setup.rb +3 -4
  28. data/spec/vcr_cassettes/Alert/EndToEnd/Should_create_and_fire_a_trigger.yml +1187 -0
  29. data/spec/vcr_cassettes/Alert/Events/Should_list_events.yml +101 -0
  30. data/spec/vcr_cassettes/Alert/Events/Should_list_events_using_criteria.yml +79 -0
  31. data/spec/vcr_cassettes/Alert/Events/Should_not_list_events_using_criteria.yml +49 -0
  32. data/spec/vcr_cassettes/Alert/Triggers/Should_bulk_load_triggers.yml +225 -0
  33. data/spec/vcr_cassettes/Alert/Triggers/Should_create_a_basic_trigger_with_action.yml +355 -0
  34. data/spec/vcr_cassettes/Alert/Triggers/Should_create_an_action.yml +134 -0
  35. data/spec/vcr_cassettes/Alert/Triggers/Should_create_an_action_for_webhooks.yml +220 -0
  36. data/spec/vcr_cassettes/Alert/Triggers/Should_get_the_action_definitions.yml +218 -0
  37. data/spec/vcr_cassettes/Alert/Triggers/Should_not_create_an_action_for_unknown_plugin.yml +46 -0
  38. data/spec/vcr_cassettes/Alert/Triggers/Should_not_create_an_action_for_unknown_properties.yml +134 -0
  39. data/spec/vcr_cassettes/Counter_metrics/Should_get_metrics_with_limit_and_order.yml +270 -0
  40. data/spec/vcr_cassettes/Inventory/Client_should_listen_on_various_inventory_events.json +47 -0
  41. data/spec/vcr_cassettes/Inventory/Client_should_listen_on_various_inventory_events.yml +191 -0
  42. data/spec/vcr_cassettes/Inventory/Helpers/generate_some_events_for_websocket.yml +507 -0
  43. data/spec/vcr_cassettes/Inventory/Should_List_datasources_with_no_props.yml +74 -69
  44. data/spec/vcr_cassettes/Inventory/{Should_list_types_without_feed.yml → Should_create_a_feed.yml} +29 -32
  45. data/spec/vcr_cassettes/Inventory/Should_create_a_feed_again.yml +211 -0
  46. data/spec/vcr_cassettes/Inventory/Should_create_a_resource_.yml +355 -0
  47. data/spec/vcr_cassettes/Inventory/Should_create_a_resource_with_metric.yml +786 -0
  48. data/spec/vcr_cassettes/Inventory/Should_create_a_resourcetype.yml +205 -0
  49. data/spec/vcr_cassettes/Inventory/Should_create_and_delete_feed.yml +201 -0
  50. data/spec/vcr_cassettes/Inventory/Should_create_and_get_a_resource.yml +419 -0
  51. data/spec/vcr_cassettes/Inventory/Should_list_URLs.yml +39 -27
  52. data/spec/vcr_cassettes/Inventory/Should_list_WildFlys.yml +37 -29
  53. data/spec/vcr_cassettes/Inventory/Should_list_WildFlys_with_props.yml +53 -45
  54. data/spec/vcr_cassettes/Inventory/Should_list_all_the_resource_types.yml +177 -0
  55. data/spec/vcr_cassettes/Inventory/Should_list_children_of_WildFly.yml +230 -56
  56. data/spec/vcr_cassettes/Inventory/Should_list_feeds.yml +27 -24
  57. data/spec/vcr_cassettes/Inventory/Should_list_heap_metrics_for_WildFlys.yml +586 -119
  58. data/spec/vcr_cassettes/Inventory/Should_list_metrics_for_WildFlys.yml +220 -59
  59. data/spec/vcr_cassettes/Inventory/Should_list_metrics_of_given_metric_type.yml +613 -0
  60. data/spec/vcr_cassettes/Inventory/Should_list_metrics_of_given_resource_type.yml +333 -0
  61. data/spec/vcr_cassettes/Inventory/Should_list_recursive_children_of_WildFly.yml +2064 -0
  62. data/spec/vcr_cassettes/Inventory/Should_list_relationships_of_WildFly.yml +460 -0
  63. data/spec/vcr_cassettes/Inventory/Should_list_types_with_bad_feed.yml +26 -25
  64. data/spec/vcr_cassettes/Inventory/Should_list_types_with_feed.yml +102 -36
  65. data/spec/vcr_cassettes/Inventory/Should_not_find_an_unknown_resource.yml +54 -0
  66. data/spec/vcr_cassettes/Inventory/Tenants/Should_Get_Tenant_For_Explicit_Credentials.yml +50 -0
  67. data/spec/vcr_cassettes/Inventory/Tenants/Should_Get_Tenant_For_Implicit_Credentials.yml +50 -0
  68. data/spec/vcr_cassettes/Operation/Helpers/get_tenant.yml +50 -0
  69. data/spec/vcr_cassettes/Operation/Operation/Add_JDBC_driver_should_add_the_driver.json +26 -0
  70. data/spec/vcr_cassettes/Operation/Operation/Add_datasource_should_be_doable.json +26 -0
  71. data/spec/vcr_cassettes/Operation/Operation/Add_deployment_should_be_doable.json +26 -0
  72. data/spec/vcr_cassettes/Operation/Operation/Redeploy_can_be_run_multiple_times_in_parallel.json +40 -0
  73. data/spec/vcr_cassettes/Operation/Operation/Redeploy_should_be_performed_and_eventually_respond_with_success.json +26 -0
  74. data/spec/vcr_cassettes/Operation/Operation/Redeploy_should_not_be_performed_if_resource_path_is_wrong.json +26 -0
  75. data/spec/vcr_cassettes/Operation/Operation/Remove_JDBC_driver_should_be_performed_and_eventually_respond_with_success.json +26 -0
  76. data/spec/vcr_cassettes/Operation/Operation/Remove_datasource_should_be_performed_and_eventually_respond_with_success.json +26 -0
  77. data/spec/vcr_cassettes/Operation/Operation/Remove_deployment_should_be_performed_and_eventually_respond_with_success.json +26 -0
  78. data/spec/vcr_cassettes/Operation/Operation/Undeploy_should_be_performed_and_eventually_respond_with_success.json +26 -0
  79. data/spec/vcr_cassettes/Operation/Operation/should_not_be_possible_to_perform_on_closed_client.json +12 -0
  80. data/spec/vcr_cassettes/Operation/Websocket_connection/should_be_established.json +9 -0
  81. data/spec/vcr_cassettes/Tokens/Should_be_able_to_create_a_new_token_for_an_actual_user.yml +57 -0
  82. data/spec/vcr_cassettes/Tokens/Should_be_able_to_list_the_available_tokens.yml +49 -0
  83. data/spec/vcr_cassettes/Tokens/Should_get_a_401_when_attempting_to_create_a_token_with_a_wrong_password.yml +56 -0
  84. data/spec/vcr_cassettes/Tokens/Should_get_a_401_when_attempting_to_create_a_token_without_a_password.yml +55 -0
  85. metadata +175 -11
  86. data/lib/metrics/version.rb +0 -7
@@ -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