wavefront-sdk 1.6.2 → 2.0.0

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