wavefront-sdk 1.6.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -0
  3. data/HISTORY.md +39 -13
  4. data/README.md +75 -28
  5. data/Rakefile +1 -1
  6. data/lib/wavefront-sdk/alert.rb +113 -17
  7. data/lib/wavefront-sdk/cloudintegration.rb +8 -8
  8. data/lib/wavefront-sdk/core/api.rb +99 -0
  9. data/lib/wavefront-sdk/core/api_caller.rb +211 -0
  10. data/lib/wavefront-sdk/{exception.rb → core/exception.rb} +11 -6
  11. data/lib/wavefront-sdk/{logger.rb → core/logger.rb} +2 -3
  12. data/lib/wavefront-sdk/{response.rb → core/response.rb} +69 -52
  13. data/lib/wavefront-sdk/credentials.rb +6 -3
  14. data/lib/wavefront-sdk/dashboard.rb +14 -14
  15. data/lib/wavefront-sdk/{constants.rb → defs/constants.rb} +1 -0
  16. data/lib/wavefront-sdk/defs/version.rb +1 -0
  17. data/lib/wavefront-sdk/derivedmetric.rb +14 -14
  18. data/lib/wavefront-sdk/distribution.rb +75 -0
  19. data/lib/wavefront-sdk/event.rb +13 -13
  20. data/lib/wavefront-sdk/externallink.rb +8 -8
  21. data/lib/wavefront-sdk/integration.rb +9 -9
  22. data/lib/wavefront-sdk/maintenancewindow.rb +54 -8
  23. data/lib/wavefront-sdk/message.rb +4 -4
  24. data/lib/wavefront-sdk/metric.rb +3 -3
  25. data/lib/wavefront-sdk/notificant.rb +9 -9
  26. data/lib/wavefront-sdk/paginator/base.rb +148 -0
  27. data/lib/wavefront-sdk/paginator/delete.rb +11 -0
  28. data/lib/wavefront-sdk/paginator/get.rb +11 -0
  29. data/lib/wavefront-sdk/paginator/post.rb +64 -0
  30. data/lib/wavefront-sdk/paginator/put.rb +11 -0
  31. data/lib/wavefront-sdk/proxy.rb +7 -7
  32. data/lib/wavefront-sdk/query.rb +4 -4
  33. data/lib/wavefront-sdk/report.rb +9 -25
  34. data/lib/wavefront-sdk/savedsearch.rb +8 -8
  35. data/lib/wavefront-sdk/search.rb +16 -13
  36. data/lib/wavefront-sdk/source.rb +14 -14
  37. data/lib/wavefront-sdk/{mixins.rb → support/mixins.rb} +8 -2
  38. data/lib/wavefront-sdk/{parse_time.rb → support/parse_time.rb} +2 -0
  39. data/lib/wavefront-sdk/types/status.rb +52 -0
  40. data/lib/wavefront-sdk/user.rb +8 -8
  41. data/lib/wavefront-sdk/validators.rb +52 -3
  42. data/lib/wavefront-sdk/webhook.rb +8 -8
  43. data/lib/wavefront-sdk/write.rb +153 -52
  44. data/lib/wavefront-sdk/writers/api.rb +38 -0
  45. data/lib/wavefront-sdk/writers/core.rb +146 -0
  46. data/lib/wavefront-sdk/writers/http.rb +42 -0
  47. data/lib/wavefront-sdk/writers/socket.rb +66 -0
  48. data/lib/wavefront-sdk/writers/summary.rb +39 -0
  49. data/lib/wavefront_sdk.rb +9 -0
  50. data/spec/spec_helper.rb +3 -0
  51. data/spec/wavefront-sdk/alert_spec.rb +6 -0
  52. data/spec/wavefront-sdk/{base_spec.rb → core/api_caller_spec.rb} +28 -41
  53. data/spec/wavefront-sdk/core/api_spec.rb +31 -0
  54. data/spec/wavefront-sdk/{logger_spec.rb → core/logger_spec.rb} +3 -3
  55. data/spec/wavefront-sdk/core/response_spec.rb +77 -0
  56. data/spec/wavefront-sdk/credentials_spec.rb +15 -10
  57. data/spec/wavefront-sdk/distribution_spec.rb +78 -0
  58. data/spec/wavefront-sdk/paginator/base_spec.rb +67 -0
  59. data/spec/wavefront-sdk/paginator/post_spec.rb +53 -0
  60. data/spec/wavefront-sdk/report_spec.rb +3 -1
  61. data/spec/wavefront-sdk/search_spec.rb +25 -0
  62. data/spec/wavefront-sdk/stdlib/array_spec.rb +2 -1
  63. data/spec/wavefront-sdk/stdlib/hash_spec.rb +6 -1
  64. data/spec/wavefront-sdk/stdlib/string_spec.rb +2 -0
  65. data/spec/wavefront-sdk/{mixins_spec.rb → support/mixins_spec.rb} +2 -2
  66. data/spec/wavefront-sdk/{parse_time_spec.rb → support/parse_time_spec.rb} +2 -2
  67. data/spec/wavefront-sdk/validators_spec.rb +64 -1
  68. data/spec/wavefront-sdk/write_spec.rb +55 -77
  69. data/spec/wavefront-sdk/writers/api_spec.rb +45 -0
  70. data/spec/wavefront-sdk/writers/core_spec.rb +49 -0
  71. data/spec/wavefront-sdk/writers/http_spec.rb +69 -0
  72. data/spec/wavefront-sdk/writers/socket_spec.rb +104 -0
  73. data/spec/wavefront-sdk/writers/summary_spec.rb +38 -0
  74. data/wavefront-sdk.gemspec +1 -1
  75. metadata +52 -24
  76. data/lib/wavefront-sdk/base.rb +0 -264
  77. data/lib/wavefront-sdk/base_write.rb +0 -185
  78. data/lib/wavefront-sdk/stdlib.rb +0 -5
  79. data/lib/wavefront-sdk/version.rb +0 -1
  80. data/spec/wavefront-sdk/base_write_spec.rb +0 -82
  81. data/spec/wavefront-sdk/response_spec.rb +0 -39
@@ -0,0 +1,38 @@
1
+ require_relative 'core'
2
+ require_relative '../core/api_caller'
3
+
4
+ module Wavefront
5
+ module Writer
6
+ #
7
+ # Send points direct to Wavefront's API. This requires an
8
+ # endpoint, a token, and HTTPS egress.
9
+ #
10
+ class Api < Core
11
+ def open
12
+ @conn = Wavefront::ApiCaller.new(self, creds, opts)
13
+ end
14
+
15
+ def api_path
16
+ '/report'
17
+ end
18
+
19
+ def validate_credentials(creds)
20
+ unless creds.key?(:endpoint)
21
+ raise(Wavefront::Exception::CredentialError,
22
+ 'credentials must contain API endpoint')
23
+ end
24
+
25
+ return true if creds.key?(:token)
26
+
27
+ raise(Wavefront::Exception::CredentialError,
28
+ 'credentials must contain API token')
29
+ end
30
+
31
+ private
32
+
33
+ def _send_point(point)
34
+ conn.post('/?f=wavefront', point, 'application/octet-stream')
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,146 @@
1
+ require 'json'
2
+ require_relative 'summary'
3
+ require_relative '../core/response'
4
+ require_relative '../validators'
5
+
6
+ module Wavefront
7
+ module Writer
8
+ #
9
+ # Abstract class extended by the other writers. Methods
10
+ # required whatever mechanism actually sends the points.
11
+ #
12
+ # A point is defined as a hash with the following keys:
13
+ # path [String] metric path. (mandatory)
14
+ # value [Numeric] value of metric. Numeric. Mandatory.
15
+ # ts [Time, Integer] timestamp for point. Defaults to
16
+ # current UTC time.
17
+ # source [String] originating source of metric. Defaults to
18
+ # the local hostname.
19
+ # tags [Hash] key:value point tags which will be applied in
20
+ # addition to any tags defined in the #initialize()
21
+ # method.
22
+ #
23
+ class Core
24
+ attr_reader :creds, :opts, :logger, :summary, :conn, :calling_class
25
+
26
+ include Wavefront::Validators
27
+
28
+ def initialize(calling_class)
29
+ @calling_class = calling_class
30
+ @creds = calling_class.creds
31
+ @opts = calling_class.opts
32
+ @logger = calling_class.logger
33
+ @summary = Wavefront::Writer::Summary.new
34
+
35
+ validate_credentials(creds) if respond_to?(:validate_credentials)
36
+ post_initialize(creds, opts) if respond_to?(:post_initialize)
37
+ end
38
+
39
+ # Send multiple points to Wavefront.
40
+ #
41
+ # @param points [Array[Hash]] an array of points.
42
+ # @param openclose [Bool] if this is false, you must have
43
+ # already opened a connection to the proxy. If it is true, a
44
+ # connection will be opened for you, used, and closed.
45
+ # @param prefix [String] prefix all metrics with this string. No
46
+ # trailing dot is required.
47
+ # @raise any unhandled point validation error is passed through
48
+ # @return [Wavefront::Response]
49
+ #
50
+ def write(points = [], openclose = true, prefix = nil)
51
+ open if openclose && respond_to?(:open)
52
+
53
+ points = screen_points(points)
54
+ points = prefix_points(points, prefix)
55
+
56
+ begin
57
+ write_loop(points)
58
+ ensure
59
+ close if openclose && respond_to?(:close)
60
+ end
61
+
62
+ respond
63
+ end
64
+
65
+ def respond
66
+ Wavefront::Response.new(
67
+ { status: { result: summary.result, message: nil, code: nil },
68
+ response: summary.to_h }.to_json, nil, opts
69
+ )
70
+ end
71
+
72
+ # Call the inheriting class's #_send_point method, and handle
73
+ # the summary
74
+ #
75
+ def send_point(point)
76
+ _send_point(point)
77
+ summary.sent += 1
78
+ true
79
+ rescue StandardError => e
80
+ summary.unsent += 1
81
+ logger.log('WARNING: failed to send point.')
82
+ logger.log(e.to_s, :debug)
83
+ false
84
+ end
85
+
86
+ # Wrapper around calling_class's #_hash_to_wf to facilitate
87
+ # verbosity/debugging. (The actual work is done in the calling
88
+ # class because it is not always on the same data type.)
89
+ #
90
+ # @param point [Hash] a hash describing a point. See #write() for
91
+ # the format.
92
+ # @return [String]
93
+ #
94
+ def hash_to_wf(point)
95
+ wf_point = calling_class.hash_to_wf(point)
96
+ logger.log(wf_point, :info)
97
+ wf_point
98
+ end
99
+
100
+ # Prefix points with a given string
101
+ # @param points [Array,Hash] one or more points
102
+ # @param prefix [String] prefix to apply to every point
103
+ # @return [Array] of points
104
+ #
105
+ def prefix_points(points, prefix = nil)
106
+ ret = [points].flatten
107
+ return ret unless prefix
108
+
109
+ ret.map { |pt| pt.tap { |p| p[:path] = prefix + '.' + p[:path] } }
110
+ end
111
+
112
+ # Filter invalid points out of an array of points
113
+ # @param points [Array,Hash] one or more points
114
+ # @return [Array] of points
115
+ #
116
+ def screen_points(points)
117
+ return points if opts[:novalidate]
118
+
119
+ [points].flatten.select { |p| valid_point?(p) }
120
+ end
121
+
122
+ def valid_point?(point)
123
+ send(calling_class.validation, point)
124
+ rescue Wavefront::Exception::InvalidMetricName,
125
+ Wavefront::Exception::InvalidMetricValue,
126
+ Wavefront::Exception::InvalidTimestamp,
127
+ Wavefront::Exception::InvalidSourceId,
128
+ Wavefront::Exception::InvalidTag => e
129
+ logger.log('Invalid point, skipping.', :info)
130
+ logger.log(e.class, :info)
131
+ logger.log(format('Invalid point: %s (%s)', point, e.to_s), :debug)
132
+ summary.rejected += 1
133
+ false
134
+ end
135
+
136
+ private
137
+
138
+ def write_loop(points)
139
+ points.each do |p|
140
+ p[:ts] = p[:ts].to_i if p[:ts].is_a?(Time)
141
+ send_point(hash_to_wf(p))
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,42 @@
1
+ require_relative 'core'
2
+ require_relative '../core/api_caller'
3
+
4
+ module Wavefront
5
+ module Writer
6
+ #
7
+ # HTTP POST points to a local proxy. This method does not
8
+ # support any authentication or authorization, as these are not
9
+ # supported by the proxy at the time of writing. When the proxy
10
+ # acquires these functions, a new writer will be made.
11
+ #
12
+ class Http < Core
13
+ def open
14
+ creds[:endpoint] = format('%s:%s', creds[:proxy],
15
+ creds[:port] || default_port)
16
+ opts[:scheme] = 'http'
17
+ @conn = Wavefront::ApiCaller.new(self, creds, opts)
18
+ end
19
+
20
+ def api_path
21
+ nil
22
+ end
23
+
24
+ def default_port
25
+ 2878
26
+ end
27
+
28
+ def validate_credentials(creds)
29
+ return true if creds.key?(:proxy)
30
+
31
+ raise(Wavefront::Exception::CredentialError,
32
+ 'credentials must contain proxy')
33
+ end
34
+
35
+ private
36
+
37
+ def _send_point(point)
38
+ conn.post(nil, point).ok?
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,66 @@
1
+ require_relative 'core'
2
+
3
+ module Wavefront
4
+ module Writer
5
+ #
6
+ # Everything specific to writing points to a Wavefront proxy, in
7
+ # native Wavefront format, to a socket. (The original and,
8
+ # once, only way to send points.)
9
+ #
10
+ class Socket < Core
11
+ # Open a socket to a Wavefront proxy, putting the descriptor
12
+ # in instance variable @conn.
13
+ # @return [TCPSocket]
14
+ #
15
+ # rubocop:disable Metrics/AbcSize
16
+ def open
17
+ if opts[:noop]
18
+ logger.log('No-op requested. Not opening connection to proxy.')
19
+ return true
20
+ end
21
+
22
+ port = creds[:port] || default_port
23
+ logger.log("Connecting to #{creds[:proxy]}:#{port}.", :debug)
24
+
25
+ begin
26
+ @conn = TCPSocket.new(creds[:proxy], port)
27
+ rescue StandardError => e
28
+ logger.log(e, :error)
29
+ raise Wavefront::Exception::InvalidEndpoint
30
+ end
31
+ end
32
+ # rubocop:enable Metrics/AbcSize
33
+
34
+ # Close the connection described by the @conn instance variable.
35
+ #
36
+ def close
37
+ return if opts[:noop]
38
+
39
+ logger.log('Closing connection to proxy.', :debug)
40
+ conn.close
41
+ end
42
+
43
+ def validate_credentials(creds)
44
+ return true if creds.key?(:proxy)
45
+
46
+ raise(Wavefront::Exception::CredentialError,
47
+ 'creds must contain proxy address')
48
+ end
49
+
50
+ private
51
+
52
+ # @param point [String] point or points in native Wavefront
53
+ # format.
54
+ #
55
+ def _send_point(point)
56
+ conn.puts(point)
57
+ end
58
+
59
+ # return [Integer] the port to connect to, if none is supplied
60
+ #
61
+ def default_port
62
+ 2878
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,39 @@
1
+ module Wavefront
2
+ module Writer
3
+ #
4
+ # Count and report on points we attempt to send to Wavefront.
5
+ #
6
+ class Summary
7
+ attr_accessor :sent, :rejected, :unsent
8
+
9
+ def initialize
10
+ @sent = 0
11
+ @rejected = 0
12
+ @unsent = 0
13
+ end
14
+
15
+ # @return [String] OK if all points were sent, ERROR if not
16
+ #
17
+ def result
18
+ ok? ? 'OK' : 'ERROR'
19
+ end
20
+
21
+ # Were all points sent successfully? (This does not
22
+ # necessarily mean they were received -- it depends on the
23
+ # writer class. Sockets are dumb, HTTP is smart.)
24
+ # @return [Bool]
25
+ #
26
+ def ok?
27
+ unsent.zero? && rejected.zero?
28
+ end
29
+
30
+ # Representation of summary as it used to be when it was built
31
+ # into the Write class
32
+ # @return [Hash]
33
+ #
34
+ def to_h
35
+ { sent: sent, rejected: rejected, unsent: unsent }
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,9 @@
1
+ # Automatically load all SDK classes.
2
+ #
3
+ require 'pathname'
4
+
5
+ libdir = Pathname.new(__FILE__).dirname + 'wavefront-sdk'
6
+
7
+ libdir.children.select { |f| f.extname == '.rb' }.each do |f|
8
+ require_relative f
9
+ end
data/spec/spec_helper.rb CHANGED
@@ -26,6 +26,9 @@ DUMMY_RESPONSE = '{"status":{"result":"OK","message":"","code":200},' \
26
26
  '"response":{"items":[{"name":"test data"}],"offset":0,' \
27
27
  '"limit":100,"totalItems":3,"moreItems":false}}'.freeze
28
28
 
29
+ RESOURCE_DIR = (Pathname.new(__FILE__).dirname +
30
+ 'wavefront-sdk' + 'resources').freeze
31
+
29
32
  # Common testing code
30
33
  class WavefrontTestBase < MiniTest::Test
31
34
  attr_reader :wf, :wf_noop, :headers
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require_relative '../spec_helper'
4
+ require 'spy/integration'
4
5
 
5
6
  ALERT = '1481553823153'.freeze
6
7
  ALERT_BODY = {
@@ -20,6 +21,11 @@ class WavefrontAlertTest < WavefrontTestBase
20
21
  should_work(:list, 10, '?offset=10&limit=100')
21
22
  end
22
23
 
24
+ def test_list_all
25
+ should_work(:list, [0, :all], '?limit=999&offset=0')
26
+ should_work(:list, [20, :all], '?limit=20&offset=0')
27
+ end
28
+
23
29
  def test_update_keys
24
30
  assert_instance_of(Array, wf.update_keys)
25
31
  wf.update_keys.each { |k| assert_instance_of(Symbol, k) }
@@ -1,70 +1,65 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require_relative '../spec_helper'
4
- require_relative '../../lib/wavefront-sdk/base'
3
+ require_relative '../../spec_helper'
4
+ require_relative '../../../lib/wavefront-sdk/core/api_caller'
5
5
 
6
+ # Wavefront::ApiCaller needs a class which responds to #api_base as
7
+ # its first argument
6
8
  #
7
- # Test SDK base class
9
+ class Dummy
10
+ def api_base
11
+ '/base'
12
+ end
13
+
14
+ def api_path
15
+ ['', 'api', 'v2', api_base].uri_concat
16
+ end
17
+ end
18
+
19
+ # Test Wavefront API caller
8
20
  #
9
- class WavefrontBaseTest < MiniTest::Test
21
+ class WavefrontApiCallerTest < MiniTest::Test
10
22
  attr_reader :wf, :wf_noop, :uri_base, :headers
11
23
 
12
24
  def setup
13
- @wf = Wavefront::Base.new(CREDS)
14
- @wf_noop = Wavefront::Base.new(CREDS, noop: true)
25
+ @wf = Wavefront::ApiCaller.new(Dummy.new, CREDS)
26
+ @wf_noop = Wavefront::ApiCaller.new(Dummy.new, CREDS, noop: true)
15
27
  @uri_base = "https://#{CREDS[:endpoint]}/api/v2/base"
16
28
  @headers = { 'Authorization' => "Bearer #{CREDS[:token]}" }
17
29
  @update_keys = []
18
30
  end
19
31
 
20
- def test_time_to_ms
21
- now_ms = Time.now.to_i * 1000
22
- assert_equal wf.time_to_ms(now_ms), now_ms
23
- assert_equal wf.time_to_ms(1_469_711_187), 1_469_711_187_000
24
- refute wf.time_to_ms([])
25
- refute wf.time_to_ms('1469711187')
26
- end
27
-
28
- def test_uri_concat
29
- assert_equal %w[a b].uri_concat, 'a/b'
30
- assert_equal ['', 'a', 'b'].uri_concat, '/a/b'
31
- assert_equal %w[a /b].uri_concat, 'a/b'
32
- assert_equal ['', 'a', 'b/'].uri_concat, '/a/b'
33
- assert_equal %w[/a /b/ /c].uri_concat, '/a/b/c'
34
- assert_equal ['/a', '/b c'].uri_concat, '/a/b c'
35
- end
36
-
37
- def test_api_get
32
+ def test_get
38
33
  uri = "#{uri_base}/path?key1=val1"
39
34
  stub_request(:get, uri).to_return(body: DUMMY_RESPONSE, status: 200)
40
- wf.api_get('/path', key1: 'val1')
35
+ wf.get('/path', key1: 'val1')
41
36
  assert_requested(:get, uri, headers: headers)
42
37
  end
43
38
 
44
- def test_api_post
39
+ def test_post
45
40
  uri = "#{uri_base}/path"
46
41
  obj = { key: 'value' }
47
42
  stub_request(:post, uri).to_return(body: DUMMY_RESPONSE, status: 200)
48
- wf.api_post('/path', 'string')
43
+ wf.post('/path', 'string')
49
44
  assert_requested(:post, uri, body: 'string',
50
45
  headers: headers.merge(POST_HEADERS))
51
- wf.api_post('/path', obj)
46
+ wf.post('/path', obj)
52
47
  assert_requested(:post, uri, body: obj.to_json,
53
48
  headers: headers.merge(POST_HEADERS))
54
49
  end
55
50
 
56
- def test_api_put
51
+ def test_put
57
52
  uri = "#{uri_base}/path"
58
53
  stub_request(:put, uri).to_return(body: DUMMY_RESPONSE, status: 200)
59
- wf.api_put('/path', 'body')
54
+ wf.put('/path', 'body')
60
55
  assert_requested(:put, uri, body: 'body'.to_json,
61
56
  headers: headers.merge(JSON_POST_HEADERS))
62
57
  end
63
58
 
64
- def test_api_delete
59
+ def test_delete
65
60
  uri = "#{uri_base}/path"
66
61
  stub_request(:delete, uri).to_return(body: DUMMY_RESPONSE, status: 200)
67
- wf.api_delete('/path')
62
+ wf.delete('/path')
68
63
  assert_requested(:delete, uri, headers: headers)
69
64
  end
70
65
 
@@ -73,16 +68,8 @@ class WavefrontBaseTest < MiniTest::Test
73
68
 
74
69
  %w[get post put delete].each do |call|
75
70
  stub_request(call.to_sym, uri)
76
- wf_noop.send("api_#{call}", '/path')
71
+ wf_noop.send(call, '/path')
77
72
  refute_requested(call.to_sym, uri)
78
73
  end
79
74
  end
80
-
81
- def test_hash_for_update
82
- wf.instance_variable_set('@update_keys', %i[k1 k2])
83
- body = { k1: 'ov1', k2: 'ov2', k3: 'ov3' }
84
- upd = { k2: 'nv1' }
85
-
86
- assert_equal(wf.hash_for_update(body, upd), k1: 'ov1', k2: 'nv1')
87
- end
88
75
  end