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
@@ -1,7 +1,9 @@
1
1
  require 'date'
2
- require_relative 'exception'
3
2
  require_relative 'parse_time'
4
- require_relative 'stdlib'
3
+ require_relative '../core/exception'
4
+ require_relative '../stdlib/string'
5
+ require_relative '../stdlib/array'
6
+ require_relative '../stdlib/hash'
5
7
 
6
8
  module Wavefront
7
9
  #
@@ -85,5 +87,9 @@ module Wavefront
85
87
  return u[suffix.to_sym] if u.key?(suffix.to_sym)
86
88
  raise Wavefront::Exception::InvalidTimeUnit
87
89
  end
90
+
91
+ def log(message, severity = :info)
92
+ logger.log(message, severity)
93
+ end
88
94
  end
89
95
  end
@@ -1,3 +1,5 @@
1
+ require 'time'
2
+
1
3
  module Wavefront
2
4
  #
3
5
  # Parse various times into integers. This class is not for direct
@@ -0,0 +1,52 @@
1
+ module Wavefront
2
+ #
3
+ # Status types are used by the Wavefront::Response class. They
4
+ # represent the success or failure of an API call.
5
+ #
6
+ #
7
+ module Type
8
+ #
9
+ # An object which provides information about whether the request
10
+ # was successful or not. Ordinarily this is easy to construct
11
+ # from the API's JSON response, but some classes, for instance
12
+ # Wavefront::Write fake it by constructing their own.
13
+ #
14
+ # @!attribute [r] result
15
+ # @return [OK, ERROR] a string telling us how the request went
16
+ # @!attribute [r] message
17
+ # @return [String] Any informational message from the API
18
+ # @!attribute [r] code
19
+ # @return [Integer] the HTTP response code from the API
20
+ # request
21
+ #
22
+ class Status
23
+ attr_reader :obj, :status
24
+
25
+ # @param response [Hash] the API response, turned into a hash
26
+ # @param status [Integer] HTTP status code
27
+ #
28
+ def initialize(response, status)
29
+ @obj = response.fetch(:status, response)
30
+ @status = status
31
+ end
32
+
33
+ def to_s
34
+ obj.inspect.to_s
35
+ end
36
+
37
+ def message
38
+ obj[:message] || nil
39
+ end
40
+
41
+ def code
42
+ obj[:code] || status
43
+ end
44
+
45
+ def result
46
+ return obj[:result] if obj[:result]
47
+ return 'OK' if status.between?(200, 299)
48
+ 'ERROR'
49
+ end
50
+ end
51
+ end
52
+ end
@@ -1,15 +1,15 @@
1
- require_relative 'base'
1
+ require_relative 'core/api'
2
2
 
3
3
  module Wavefront
4
4
  #
5
5
  # Manage and query Wavefront users
6
6
  #
7
- class User < Base
7
+ class User < CoreApi
8
8
  # GET /api/v2/user
9
9
  # Get all users.
10
10
  #
11
11
  def list
12
- api_get('')
12
+ api.get('')
13
13
  end
14
14
 
15
15
  # POST /api/v2/user
@@ -22,7 +22,7 @@ module Wavefront
22
22
  #
23
23
  def create(body, send_email = false)
24
24
  raise ArgumentError unless body.is_a?(Hash)
25
- api_post("?sendEmail=#{send_email}", body, 'application/json')
25
+ api.post("?sendEmail=#{send_email}", body, 'application/json')
26
26
  end
27
27
 
28
28
  # DELETE /api/v2/user/id
@@ -33,7 +33,7 @@ module Wavefront
33
33
  #
34
34
  def delete(id)
35
35
  wf_user_id?(id)
36
- api_delete(id)
36
+ api.delete(id)
37
37
  end
38
38
 
39
39
  # GET /api/v2/user/id
@@ -44,7 +44,7 @@ module Wavefront
44
44
  #
45
45
  def describe(id)
46
46
  wf_user_id?(id)
47
- api_get(id)
47
+ api.get(id)
48
48
  end
49
49
 
50
50
  # PUT /api/v2/user/id/grant
@@ -63,7 +63,7 @@ module Wavefront
63
63
  def grant(id, group)
64
64
  wf_user_id?(id)
65
65
  raise ArgumentError unless group.is_a?(String)
66
- api_post([id, 'grant'].uri_concat, "group=#{group}",
66
+ api.post([id, 'grant'].uri_concat, "group=#{group}",
67
67
  'application/x-www-form-urlencoded')
68
68
  end
69
69
 
@@ -79,7 +79,7 @@ module Wavefront
79
79
  def revoke(id, group)
80
80
  wf_user_id?(id)
81
81
  raise ArgumentError unless group.is_a?(String)
82
- api_post([id, 'revoke'].uri_concat, "group=#{group}",
82
+ api.post([id, 'revoke'].uri_concat, "group=#{group}",
83
83
  'application/x-www-form-urlencoded')
84
84
  end
85
85
 
@@ -1,5 +1,5 @@
1
- require_relative 'constants'
2
- require_relative 'exception'
1
+ require_relative 'defs/constants'
2
+ require_relative 'core/exception'
3
3
 
4
4
  module Wavefront
5
5
  #
@@ -409,7 +409,7 @@ module Wavefront
409
409
  # https://community.wavefront.com/docs/DOC-1031
410
410
  #
411
411
  # @param point [Hash] description of point
412
- # @return true if valie
412
+ # @return true if valid
413
413
  # @raise whichever exception is thrown first when validating
414
414
  # each component of the point.
415
415
  #
@@ -422,6 +422,35 @@ module Wavefront
422
422
  true
423
423
  end
424
424
 
425
+ # Validate a distribution description
426
+ # @param dist [Hash] description of distribution
427
+ # @return true if valid
428
+ # @raise whichever exception is thrown first when validating
429
+ # each component of the distribution.
430
+ #
431
+ def wf_distribution?(dist)
432
+ wf_metric_name?(dist[:path])
433
+ wf_distribution_values?(dist[:value])
434
+ wf_epoch?(dist[:ts]) if dist[:ts]
435
+ wf_source_id?(dist[:source]) if dist[:source]
436
+ wf_point_tags?(dist[:tags]) if dist[:tags]
437
+ true
438
+ end
439
+
440
+ # Validate an array of distribution values
441
+ # @param vals [Array[Array]] [count, value]
442
+ # @return true if valid
443
+ # @raise whichever exception is thrown first when validating
444
+ # each component of the distribution.
445
+ #
446
+ def wf_distribution_values?(vals)
447
+ vals.each do |times, val|
448
+ wf_distribution_count?(times)
449
+ wf_value?(val)
450
+ end
451
+ true
452
+ end
453
+
425
454
  # Ensure the given argument is a valid Wavefront notificant ID.
426
455
  #
427
456
  # @param id [String] the notificant ID to validate
@@ -446,6 +475,26 @@ module Wavefront
446
475
  return true if id.is_a?(String) && id =~ /^[a-z0-9]+$/
447
476
  raise Wavefront::Exception::InvalidIntegrationId
448
477
  end
478
+
479
+ # Ensure the given argument is a valid distribution interval.
480
+ # @param interval [Symbol]
481
+ # @raise Wavefront::Exception::InvalidDistributionInterval if the
482
+ # interval is not valid
483
+ #
484
+ def wf_distribution_interval?(interval)
485
+ return true if %i[m h d].include?(interval)
486
+ raise Wavefront::Exception::InvalidDistributionInterval
487
+ end
488
+
489
+ # Ensure the given argument is a valid distribution count.
490
+ # @param count [Numeric]
491
+ # @raise Wavefront::Exception::InvalidDistributionCount if the
492
+ # count is not valid
493
+ #
494
+ def wf_distribution_count?(count)
495
+ return true if count.is_a?(Integer) && count > 0
496
+ raise Wavefront::Exception::InvalidDistributionCount
497
+ end
449
498
  end
450
499
  # rubocop:enable Metrics/ModuleLength
451
500
  end
@@ -1,10 +1,10 @@
1
- require_relative 'base'
1
+ require_relative 'core/api'
2
2
 
3
3
  module Wavefront
4
4
  #
5
5
  # Manage and query Wavefront webhooks
6
6
  #
7
- class Webhook < Base
7
+ class Webhook < CoreApi
8
8
  def update_keys
9
9
  %i[title description template title triggers recipient]
10
10
  end
@@ -16,7 +16,7 @@ module Wavefront
16
16
  # @param limit [Integer] the number of webhooks to return
17
17
  #
18
18
  def list(offset = 0, limit = 100)
19
- api_get('', offset: offset, limit: limit)
19
+ api.get('', offset: offset, limit: limit)
20
20
  end
21
21
 
22
22
  # POST /api/v2/webhook
@@ -29,7 +29,7 @@ module Wavefront
29
29
  #
30
30
  def create(body)
31
31
  raise ArgumentError unless body.is_a?(Hash)
32
- api_post('', body, 'application/json')
32
+ api.post('', body, 'application/json')
33
33
  end
34
34
 
35
35
  # DELETE /api/v2/webhook/id
@@ -40,7 +40,7 @@ module Wavefront
40
40
  #
41
41
  def delete(id)
42
42
  wf_webhook_id?(id)
43
- api_delete(id)
43
+ api.delete(id)
44
44
  end
45
45
 
46
46
  # GET /api/v2/webhook/id
@@ -51,7 +51,7 @@ module Wavefront
51
51
  #
52
52
  def describe(id)
53
53
  wf_webhook_id?(id)
54
- api_get(id)
54
+ api.get(id)
55
55
  end
56
56
 
57
57
  # PUT /api/v2/webhook/id
@@ -69,9 +69,9 @@ module Wavefront
69
69
  wf_webhook_id?(id)
70
70
  raise ArgumentError unless body.is_a?(Hash)
71
71
 
72
- return api_put(id, body, 'application/json') unless modify
72
+ return api.put(id, body, 'application/json') unless modify
73
73
 
74
- api_put(id, hash_for_update(describe(id).response, body),
74
+ api.put(id, hash_for_update(describe(id).response, body),
75
75
  'application/json')
76
76
  end
77
77
  end
@@ -1,66 +1,127 @@
1
- require_relative 'base_write'
1
+ require 'socket'
2
+ require_relative 'core/exception'
3
+ require_relative 'core/logger'
4
+ require_relative 'defs/constants'
5
+ require_relative 'validators'
6
+ require 'spy'
7
+ require 'spy/integration'
8
+
9
+ HOSTNAME = Socket.gethostname.freeze
2
10
 
3
11
  module Wavefront
4
12
  #
5
- # This class helps you send points to a Wavefront proxy in native
6
- # format. Usually this is done on port 2878.
7
- #
8
- # The points are prepped in the BaseWrite class, which this
9
- # extends. This class provides the transport mechanism.
13
+ # This class helps you send points to Wavefront. It is extended by
14
+ # the Write and Report classes, which respectively handle point
15
+ # ingestion by a proxy and directly to the API.
10
16
  #
11
- class Write < BaseWrite
12
- def really_send_point(point)
13
- begin
14
- sock.puts(point)
15
- rescue StandardError => e
16
- summary[:unsent] += 1
17
- log('WARNING: failed to send point.')
18
- log(e.to_s, :debug)
19
- return false
20
- end
17
+ class Write
18
+ attr_reader :creds, :opts, :writer, :logger
19
+
20
+ include Wavefront::Validators
21
+
22
+ # Construct an object which gives the user an interface for
23
+ # writing points to Wavefront. The actual writing is handled by
24
+ # a Wavefront::Writer:: subclass.
25
+ #
26
+ # @param creds [Hash] credentials
27
+ # signature.
28
+ # @param options [Hash] can contain the following keys:
29
+ # proxy [String] the address of the Wavefront proxy. ('wavefront')
30
+ # port [Integer] the port of the Wavefront proxy
31
+ # tags [Hash] point tags which will be applied to every point
32
+ # noop [Bool] if true, no proxy connection will be made, and
33
+ # instead of sending the points, they will be printed in
34
+ # Wavefront wire format.
35
+ # novalidate [Bool] if true, points will not be validated.
36
+ # This might make things go marginally quicker if you have
37
+ # done point validation higher up in the chain. Invalid
38
+ # points are dropped, logged, and reported in the summary.
39
+ # verbose [Bool]
40
+ # debug [Bool]
41
+ # writer [Symbol, String] the name of the writer class to use.
42
+ # Defaults to :socket
43
+ #
44
+ def initialize(creds = {}, opts = {})
45
+ defaults = { tags: nil,
46
+ writer: :socket,
47
+ noop: false,
48
+ novalidate: false,
49
+ verbose: false,
50
+ debug: false }
51
+
52
+ @opts = setup_options(opts, defaults)
53
+ @creds = creds
54
+ wf_point_tags?(opts[:tags]) if opts[:tags]
55
+ @logger = Wavefront::Logger.new(opts)
56
+ @writer = setup_writer
57
+ end
21
58
 
22
- summary[:sent] += 1
23
- true
59
+ def setup_options(user, defaults)
60
+ defaults.merge(user)
24
61
  end
25
62
 
26
- # Open a socket to a Wavefront proxy, putting the descriptor
27
- # in instance variable @sock.
63
+ # Wrapper to the writer class's #open method. Using this you can
64
+ # manually open a connection and re-use it.
28
65
  #
29
66
  def open
30
- if opts[:noop]
31
- log('No-op requested. Not opening connection to proxy.')
32
- return true
33
- end
67
+ writer.open
68
+ end
34
69
 
35
- port = net[:port] || 2878
36
- log("Connecting to #{net[:proxy]}:#{port}.", :debug)
70
+ # Wrapper to the writer class's #close method.
71
+ #
72
+ def close
73
+ writer.close
74
+ end
37
75
 
38
- begin
39
- @sock = TCPSocket.new(net[:proxy], port)
40
- rescue StandardError => e
41
- log(e, :error)
42
- raise Wavefront::Exception::InvalidEndpoint
43
- end
76
+ # A wrapper to the writer class's #write method.
77
+ # Writers implement this method differently, Check the
78
+ # appropriate class documentation for @return information etc.
79
+ # The signature is always the same.
80
+ #
81
+ def write(points = [], openclose = true, prefix = nil)
82
+ writer.write(points, openclose, prefix)
44
83
  end
45
84
 
46
- # Close the socket described by the @sock instance variable.
85
+ # A wrapper method around #write() which guarantees all points
86
+ # will be sent as deltas. You can still manually prefix any
87
+ # metric with a delta symbol and use #write(), but depending on
88
+ # your use-case, this method may be safer. It's easy to forget
89
+ # the delta.
47
90
  #
48
- def close
49
- return if opts[:noop]
50
- log('Closing connection to proxy.', :debug)
51
- sock.close
91
+ # @param points [Array[Hash]] see #write()
92
+ # @param openclose [Bool] see #write()
93
+ #
94
+ def write_delta(points, openclose = true)
95
+ write(paths_to_deltas(points), openclose)
96
+ end
97
+
98
+ # Prefix all paths in a points array (as passed to
99
+ # #write_delta() with a delta symbol
100
+ #
101
+ # @param points [Array[Hash]] see #write()
102
+ # @return [Array[Hash]]
103
+ #
104
+ def paths_to_deltas(points)
105
+ [points].flatten.map { |p| p.tap { p[:path] = DELTA + p[:path] } }
52
106
  end
53
107
 
54
- # Overload the method which sets an API endpoint. A proxy
55
- # endpoint has an address and a port, rather than an address and
56
- # a token.
108
+ # Wrapper for the writer class's #send_point method
109
+ # @param point [String] a point description, probably from
110
+ # #hash_to_wf()
57
111
  #
58
- def setup_endpoint(creds)
59
- @net = creds
112
+ def send_point(point)
113
+ if opts[:noop]
114
+ logger.log "Would send: #{point}"
115
+ return
116
+ end
117
+
118
+ logger.log("Sending: #{point}", :debug)
119
+ writer.send_point(point)
60
120
  end
61
121
 
62
- # Send raw data to a Wavefront proxy, automatically opening and
63
- # closing a socket.
122
+ # Send raw data to a Wavefront proxy, optionally automatically
123
+ # opening and closing the connection. (Or not, if that does not
124
+ # make sense in the context of the writer.)
64
125
  #
65
126
  # @param points [Array[String]] an array of points in native
66
127
  # Wavefront wire format, as described in
@@ -71,22 +132,62 @@ module Wavefront
71
132
  # afterwards, close it.
72
133
  #
73
134
  def raw(points, openclose = true)
74
- open if openclose
135
+ writer.open if openclose && writer.respond_to?(:open)
75
136
 
76
137
  begin
77
- [points].flatten.each { |p| send_point(p) }
138
+ [points].flatten.each { |p| writer.send_point(p) }
78
139
  ensure
79
- close if openclose
140
+ writer.close if openclose && writer.respond_to?(:close)
80
141
  end
81
142
  end
82
143
 
144
+ # The method used to validate the data we wish to write.
145
+ #
146
+ def validation
147
+ :wf_point?
148
+ end
149
+
150
+ # Convert a validated point to a string conforming to
151
+ # https://community.wavefront.com/docs/DOC-1031. No validation
152
+ # is done here.
153
+ #
154
+ # @param point [Hash] a hash describing a point. See #write() for
155
+ # the format.
156
+ #
157
+ def hash_to_wf(point)
158
+ format('%s %s %s source=%s %s %s',
159
+ *point_array(point)).squeeze(' ').strip
160
+ rescue StandardError
161
+ raise Wavefront::Exception::InvalidPoint
162
+ end
163
+
164
+ # Make an array which can be used by #hash_to_wf.
165
+ # @param point [Hash] a hash describing a point. See #write() for
166
+ # the format.
167
+ # @raise
168
+ #
169
+ def point_array(point)
170
+ [point[:path] || raise,
171
+ point[:value] || raise,
172
+ point.fetch(:ts, nil),
173
+ point.fetch(:source, HOSTNAME),
174
+ point[:tags] && point[:tags].to_wf_tag,
175
+ opts[:tags] && opts[:tags].to_wf_tag]
176
+ end
177
+
83
178
  private
84
179
 
85
- def _write_loop(points)
86
- points.each do |p|
87
- p[:ts] = p[:ts].to_i if p[:ts].is_a?(Time)
88
- send_point(hash_to_wf(p))
89
- end
180
+ # @return [Object] appropriate subclass of Wavefront::Writer
181
+ # @raise [Wavefront::Exception::UnsupportedWriter] if requested
182
+ # writer cannot be loaded
183
+ #
184
+ def setup_writer
185
+ writer = opts[:writer].to_s
186
+ require_relative File.join('writers', writer)
187
+ Object.const_get(format('Wavefront::Writer::%s',
188
+ writer.capitalize)).new(self)
189
+ rescue LoadError
190
+ raise(Wavefront::Exception::UnsupportedWriter, writer)
90
191
  end
91
192
  end
92
193
  end