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.
- checksums.yaml +4 -4
- data/.coveralls.yml +1 -0
- data/.gitignore +2 -1
- data/.rubocop.yml +19 -6
- data/CHANGES.rdoc +13 -3
- data/README.rdoc +9 -3
- data/hawkularclient.gemspec +8 -4
- data/lib/alerts/alerts_api.rb +202 -32
- data/lib/hawkular.rb +35 -67
- data/lib/hawkular_all.rb +2 -0
- data/lib/inventory/inventory_api.rb +459 -130
- data/lib/metrics/metric_api.rb +14 -9
- data/lib/operations/operations_api.rb +247 -0
- data/lib/tokens/tokens_api.rb +33 -0
- data/lib/version.rb +1 -1
- data/spec/integration/alerts_spec.rb +272 -20
- data/spec/integration/hello-world-definitions.json +46 -0
- data/spec/integration/inventory_spec.rb +253 -89
- data/spec/integration/metric_spec.rb +36 -0
- data/spec/integration/operations_spec.rb +420 -0
- data/spec/integration/tokens_spec.rb +45 -0
- data/spec/resources/driver.jar +0 -0
- data/spec/resources/sample.war +0 -0
- data/spec/spec_helper.rb +30 -6
- data/spec/unit/base_spec.rb +22 -1
- data/spec/unit/canonical_path_spec.rb +92 -0
- data/spec/vcr/vcr_setup.rb +3 -4
- data/spec/vcr_cassettes/Alert/EndToEnd/Should_create_and_fire_a_trigger.yml +1187 -0
- data/spec/vcr_cassettes/Alert/Events/Should_list_events.yml +101 -0
- data/spec/vcr_cassettes/Alert/Events/Should_list_events_using_criteria.yml +79 -0
- data/spec/vcr_cassettes/Alert/Events/Should_not_list_events_using_criteria.yml +49 -0
- data/spec/vcr_cassettes/Alert/Triggers/Should_bulk_load_triggers.yml +225 -0
- data/spec/vcr_cassettes/Alert/Triggers/Should_create_a_basic_trigger_with_action.yml +355 -0
- data/spec/vcr_cassettes/Alert/Triggers/Should_create_an_action.yml +134 -0
- data/spec/vcr_cassettes/Alert/Triggers/Should_create_an_action_for_webhooks.yml +220 -0
- data/spec/vcr_cassettes/Alert/Triggers/Should_get_the_action_definitions.yml +218 -0
- data/spec/vcr_cassettes/Alert/Triggers/Should_not_create_an_action_for_unknown_plugin.yml +46 -0
- data/spec/vcr_cassettes/Alert/Triggers/Should_not_create_an_action_for_unknown_properties.yml +134 -0
- data/spec/vcr_cassettes/Counter_metrics/Should_get_metrics_with_limit_and_order.yml +270 -0
- data/spec/vcr_cassettes/Inventory/Client_should_listen_on_various_inventory_events.json +47 -0
- data/spec/vcr_cassettes/Inventory/Client_should_listen_on_various_inventory_events.yml +191 -0
- data/spec/vcr_cassettes/Inventory/Helpers/generate_some_events_for_websocket.yml +507 -0
- data/spec/vcr_cassettes/Inventory/Should_List_datasources_with_no_props.yml +74 -69
- data/spec/vcr_cassettes/Inventory/{Should_list_types_without_feed.yml → Should_create_a_feed.yml} +29 -32
- data/spec/vcr_cassettes/Inventory/Should_create_a_feed_again.yml +211 -0
- data/spec/vcr_cassettes/Inventory/Should_create_a_resource_.yml +355 -0
- data/spec/vcr_cassettes/Inventory/Should_create_a_resource_with_metric.yml +786 -0
- data/spec/vcr_cassettes/Inventory/Should_create_a_resourcetype.yml +205 -0
- data/spec/vcr_cassettes/Inventory/Should_create_and_delete_feed.yml +201 -0
- data/spec/vcr_cassettes/Inventory/Should_create_and_get_a_resource.yml +419 -0
- data/spec/vcr_cassettes/Inventory/Should_list_URLs.yml +39 -27
- data/spec/vcr_cassettes/Inventory/Should_list_WildFlys.yml +37 -29
- data/spec/vcr_cassettes/Inventory/Should_list_WildFlys_with_props.yml +53 -45
- data/spec/vcr_cassettes/Inventory/Should_list_all_the_resource_types.yml +177 -0
- data/spec/vcr_cassettes/Inventory/Should_list_children_of_WildFly.yml +230 -56
- data/spec/vcr_cassettes/Inventory/Should_list_feeds.yml +27 -24
- data/spec/vcr_cassettes/Inventory/Should_list_heap_metrics_for_WildFlys.yml +586 -119
- data/spec/vcr_cassettes/Inventory/Should_list_metrics_for_WildFlys.yml +220 -59
- data/spec/vcr_cassettes/Inventory/Should_list_metrics_of_given_metric_type.yml +613 -0
- data/spec/vcr_cassettes/Inventory/Should_list_metrics_of_given_resource_type.yml +333 -0
- data/spec/vcr_cassettes/Inventory/Should_list_recursive_children_of_WildFly.yml +2064 -0
- data/spec/vcr_cassettes/Inventory/Should_list_relationships_of_WildFly.yml +460 -0
- data/spec/vcr_cassettes/Inventory/Should_list_types_with_bad_feed.yml +26 -25
- data/spec/vcr_cassettes/Inventory/Should_list_types_with_feed.yml +102 -36
- data/spec/vcr_cassettes/Inventory/Should_not_find_an_unknown_resource.yml +54 -0
- data/spec/vcr_cassettes/Inventory/Tenants/Should_Get_Tenant_For_Explicit_Credentials.yml +50 -0
- data/spec/vcr_cassettes/Inventory/Tenants/Should_Get_Tenant_For_Implicit_Credentials.yml +50 -0
- data/spec/vcr_cassettes/Operation/Helpers/get_tenant.yml +50 -0
- data/spec/vcr_cassettes/Operation/Operation/Add_JDBC_driver_should_add_the_driver.json +26 -0
- data/spec/vcr_cassettes/Operation/Operation/Add_datasource_should_be_doable.json +26 -0
- data/spec/vcr_cassettes/Operation/Operation/Add_deployment_should_be_doable.json +26 -0
- data/spec/vcr_cassettes/Operation/Operation/Redeploy_can_be_run_multiple_times_in_parallel.json +40 -0
- data/spec/vcr_cassettes/Operation/Operation/Redeploy_should_be_performed_and_eventually_respond_with_success.json +26 -0
- data/spec/vcr_cassettes/Operation/Operation/Redeploy_should_not_be_performed_if_resource_path_is_wrong.json +26 -0
- data/spec/vcr_cassettes/Operation/Operation/Remove_JDBC_driver_should_be_performed_and_eventually_respond_with_success.json +26 -0
- data/spec/vcr_cassettes/Operation/Operation/Remove_datasource_should_be_performed_and_eventually_respond_with_success.json +26 -0
- data/spec/vcr_cassettes/Operation/Operation/Remove_deployment_should_be_performed_and_eventually_respond_with_success.json +26 -0
- data/spec/vcr_cassettes/Operation/Operation/Undeploy_should_be_performed_and_eventually_respond_with_success.json +26 -0
- data/spec/vcr_cassettes/Operation/Operation/should_not_be_possible_to_perform_on_closed_client.json +12 -0
- data/spec/vcr_cassettes/Operation/Websocket_connection/should_be_established.json +9 -0
- data/spec/vcr_cassettes/Tokens/Should_be_able_to_create_a_new_token_for_an_actual_user.yml +57 -0
- data/spec/vcr_cassettes/Tokens/Should_be_able_to_list_the_available_tokens.yml +49 -0
- data/spec/vcr_cassettes/Tokens/Should_get_a_401_when_attempting_to_create_a_token_with_a_wrong_password.yml +56 -0
- data/spec/vcr_cassettes/Tokens/Should_get_a_401_when_attempting_to_create_a_token_without_a_password.yml +55 -0
- metadata +175 -11
- data/lib/metrics/version.rb +0 -7
data/lib/metrics/metric_api.rb
CHANGED
|
@@ -43,12 +43,12 @@ module Hawkular::Metrics
|
|
|
43
43
|
# types (counters, gauges, availabilities).
|
|
44
44
|
class Metrics
|
|
45
45
|
# @param client [Client]
|
|
46
|
-
# @param
|
|
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,
|
|
49
|
+
def initialize(client, metric_type, resource)
|
|
50
50
|
@client = client
|
|
51
|
-
@type =
|
|
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
|
|
88
|
-
def update_tags(
|
|
89
|
-
@client.http_put("/#{@resource}/#{
|
|
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
|
-
|
|
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
|
|
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
|
@@ -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', :
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|