efigence-influxdb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.rubocop.yml +41 -0
  4. data/.travis.yml +11 -0
  5. data/Gemfile +11 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +362 -0
  8. data/Rakefile +22 -0
  9. data/efigence-influxdb.gemspec +28 -0
  10. data/lib/influxdb.rb +21 -0
  11. data/lib/influxdb/client.rb +77 -0
  12. data/lib/influxdb/client/http.rb +98 -0
  13. data/lib/influxdb/config.rb +60 -0
  14. data/lib/influxdb/errors.rb +40 -0
  15. data/lib/influxdb/logging.rb +22 -0
  16. data/lib/influxdb/max_queue.rb +18 -0
  17. data/lib/influxdb/point_value.rb +31 -0
  18. data/lib/influxdb/query/cluster.rb +17 -0
  19. data/lib/influxdb/query/continuous_query.rb +36 -0
  20. data/lib/influxdb/query/core.rb +109 -0
  21. data/lib/influxdb/query/database.rb +21 -0
  22. data/lib/influxdb/query/series.rb +13 -0
  23. data/lib/influxdb/query/shard.rb +14 -0
  24. data/lib/influxdb/query/shard_space.rb +60 -0
  25. data/lib/influxdb/query/user.rb +38 -0
  26. data/lib/influxdb/version.rb +3 -0
  27. data/lib/influxdb/writer/async.rb +115 -0
  28. data/lib/influxdb/writer/udp.rb +21 -0
  29. data/spec/influxdb/cases/async_client_spec.rb +33 -0
  30. data/spec/influxdb/cases/query_cluster_spec.rb +65 -0
  31. data/spec/influxdb/cases/query_database_spec.rb +58 -0
  32. data/spec/influxdb/cases/query_series_spec.rb +50 -0
  33. data/spec/influxdb/cases/query_shard_spec.rb +43 -0
  34. data/spec/influxdb/cases/query_user_spec.rb +127 -0
  35. data/spec/influxdb/cases/querying_spec.rb +159 -0
  36. data/spec/influxdb/cases/retry_requests_spec.rb +97 -0
  37. data/spec/influxdb/cases/udp_client_spec.rb +21 -0
  38. data/spec/influxdb/cases/write_points_spec.rb +141 -0
  39. data/spec/influxdb/client_spec.rb +58 -0
  40. data/spec/influxdb/config_spec.rb +118 -0
  41. data/spec/influxdb/logging_spec.rb +48 -0
  42. data/spec/influxdb/max_queue_spec.rb +29 -0
  43. data/spec/influxdb/point_value_spec.rb +66 -0
  44. data/spec/influxdb/worker_spec.rb +23 -0
  45. data/spec/spec_helper.rb +8 -0
  46. metadata +192 -0
@@ -0,0 +1,22 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ targeted_files = ARGV.drop(1)
4
+ file_pattern = targeted_files.empty? ? 'spec/**/*_spec.rb' : targeted_files
5
+
6
+ require 'rspec/core'
7
+ require 'rspec/core/rake_task'
8
+
9
+ RSpec::Core::RakeTask.new(:spec) do |t|
10
+ t.pattern = FileList[file_pattern]
11
+ end
12
+
13
+ RSpec.configure do |config|
14
+ config.color = true
15
+ config.formatter = :documentation
16
+ end
17
+
18
+ task default: :spec
19
+
20
+ task :console do
21
+ sh 'pry -r ./lib/influxdb.rb'
22
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'influxdb/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "efigence-influxdb"
8
+ spec.version = InfluxDB::VERSION
9
+ spec.authors = ["Jacek Grzybowski", "Todd Persen"]
10
+ spec.email = ["jgrzybowski@efigence.com"]
11
+ spec.description = %q{This is a fork of official Ruby library for InfluxDB meant for v0.9.x support.}
12
+ spec.summary = %q{Ruby library for InfluxDB.}
13
+ spec.homepage = "https://github.com/efigence/influxdb-ruby/tree/support_v0.9.x"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "json"
22
+ spec.add_runtime_dependency "cause"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.3"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "rspec", "~> 3.0.0"
27
+ spec.add_development_dependency "webmock"
28
+ end
@@ -0,0 +1,21 @@
1
+ require "influxdb/version"
2
+ require "influxdb/errors"
3
+ require "influxdb/logging"
4
+ require "influxdb/max_queue"
5
+ require "influxdb/point_value"
6
+ require "influxdb/config"
7
+
8
+ require "influxdb/writer/async"
9
+ require "influxdb/writer/udp"
10
+
11
+ require "influxdb/query/core"
12
+ require "influxdb/query/cluster"
13
+ require "influxdb/query/database"
14
+ require "influxdb/query/series"
15
+ require "influxdb/query/shard"
16
+ require "influxdb/query/user"
17
+ require "influxdb/query/continuous_query"
18
+ require "influxdb/query/shard_space"
19
+
20
+ require "influxdb/client/http"
21
+ require "influxdb/client"
@@ -0,0 +1,77 @@
1
+ require 'json'
2
+ require 'cause'
3
+
4
+ module InfluxDB
5
+ # rubocop:disable Metrics/MethodLength
6
+ # rubocop:disable Metrics/AbcSize
7
+
8
+ # InfluxDB client class
9
+ class Client
10
+ attr_reader :config, :writer
11
+
12
+ include InfluxDB::Logging
13
+ include InfluxDB::HTTP
14
+ include InfluxDB::Query::Core
15
+ include InfluxDB::Query::Cluster
16
+ include InfluxDB::Query::Database
17
+ include InfluxDB::Query::Shard
18
+ include InfluxDB::Query::Series
19
+ include InfluxDB::Query::User
20
+ include InfluxDB::Query::ContinuousQuery
21
+ include InfluxDB::Query::ShardSpace
22
+
23
+ # Initializes a new InfluxDB client
24
+ #
25
+ # === Examples:
26
+ #
27
+ # # connect to localhost using root/root
28
+ # # as the credentials and doesn't connect to a db
29
+ #
30
+ # InfluxDB::Client.new
31
+ #
32
+ # # connect to localhost using root/root
33
+ # # as the credentials and 'db' as the db name
34
+ #
35
+ # InfluxDB::Client.new 'db'
36
+ #
37
+ # # override username, other defaults remain unchanged
38
+ #
39
+ # InfluxDB::Client.new username: 'username'
40
+ #
41
+ # # override username, use 'db' as the db name
42
+ # Influxdb::Client.new 'db', username: 'username'
43
+ #
44
+ # === Valid options in hash
45
+ #
46
+ # +:host+:: the hostname to connect to
47
+ # +:port+:: the port to connect to
48
+ # +:path+:: the specified path prefix when building the url e.g.: /prefix/db/dbname...
49
+ # +:username+:: the username to use when executing commands
50
+ # +:password+:: the password associated with the username
51
+ # +:use_ssl+:: use ssl to connect
52
+ def initialize(*args)
53
+ opts = args.last.is_a?(Hash) ? args.last : {}
54
+ opts[:database] = args.first if args.first.is_a? String
55
+ @config = InfluxDB::Config.new(opts)
56
+ @stopped = false
57
+
58
+ @writer = self
59
+
60
+ if config.async?
61
+ @writer = InfluxDB::Writer::Async.new(self, config.async)
62
+ elsif config.udp?
63
+ @writer = InfluxDB::Writer::UDP.new(self, config.udp)
64
+ end
65
+
66
+ at_exit { stop! } if config.retry > 0
67
+ end
68
+
69
+ def stop!
70
+ @stopped = true
71
+ end
72
+
73
+ def stopped?
74
+ @stopped
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,98 @@
1
+ require 'uri'
2
+ require 'cgi'
3
+ require 'net/http'
4
+ require 'net/https'
5
+
6
+ module InfluxDB
7
+ # rubocop:disable Metrics/MethodLength
8
+ # rubocop:disable Metrics/AbcSize
9
+ module HTTP # :nodoc:
10
+ def get(url, options = {})
11
+ connect_with_retry do |http|
12
+ response = do_request http, Net::HTTP::Get.new(url)
13
+ if response.is_a? Net::HTTPSuccess
14
+ handle_successful_response(response, options)
15
+ elsif response.is_a? Net::HTTPUnauthorized
16
+ fail InfluxDB::AuthenticationError, response.body
17
+ else
18
+ resolve_error(response.body)
19
+ end
20
+ end
21
+ end
22
+
23
+ def post(url, data)
24
+ headers = {"Content-Type" => "application/octet-stream"}
25
+ connect_with_retry do |http|
26
+ response = do_request http, Net::HTTP::Post.new(url, headers), data
27
+ if response.is_a? Net::HTTPSuccess
28
+ return response
29
+ elsif response.is_a? Net::HTTPUnauthorized
30
+ fail InfluxDB::AuthenticationError, response.body
31
+ else
32
+ resolve_error(response.body)
33
+ end
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def connect_with_retry(&block)
40
+ hosts = config.hosts.dup
41
+ delay = config.initial_delay
42
+ retry_count = 0
43
+
44
+ begin
45
+ hosts.push(host = hosts.shift)
46
+ http = Net::HTTP.new(host, config.port)
47
+ http.open_timeout = config.open_timeout
48
+ http.read_timeout = config.read_timeout
49
+ http.use_ssl = config.use_ssl
50
+ block.call(http)
51
+
52
+ rescue Timeout::Error, *InfluxDB::NET_HTTP_EXCEPTIONS => e
53
+ retry_count += 1
54
+ if (config.retry == -1 || retry_count <= config.retry) && !stopped?
55
+ log :error, "Failed to contact host #{host}: #{e.inspect} - retrying in #{delay}s."
56
+ sleep delay
57
+ delay = [config.max_delay, delay * 2].min
58
+ retry
59
+ else
60
+ raise e, "Tried #{retry_count - 1} times to reconnect but failed."
61
+ end
62
+ ensure
63
+ http.finish if http.started?
64
+ end
65
+ end
66
+
67
+ def do_request(http, req, data = nil)
68
+ req.basic_auth config.username, config.password if basic_auth?
69
+ req.body = data if data
70
+ http.request(req)
71
+ end
72
+
73
+ def basic_auth?
74
+ config.auth_method == 'basic_auth'
75
+ end
76
+
77
+ def resolve_error(response)
78
+ if response =~ /Couldn\'t find series/
79
+ fail InfluxDB::SeriesNotFound, response
80
+ else
81
+ fail InfluxDB::Error, response
82
+ end
83
+ end
84
+
85
+ def handle_successful_response(response, options)
86
+ parsed_response = JSON.parse(response.body) if response.body
87
+ errors = errors_from_response(parsed_response) if parsed_response
88
+ raise InfluxDB::QueryError.new errors if errors
89
+ options.fetch(:parse, false) ? parsed_response : response
90
+ end
91
+
92
+ def errors_from_response(parsed_resp)
93
+ parsed_resp.is_a?(Hash) && parsed_resp.fetch('results', [])
94
+ .fetch(0, {})
95
+ .fetch('error', nil)
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,60 @@
1
+ module InfluxDB
2
+ # InfluxDB client configuration
3
+ class Config
4
+ AUTH_METHODS = %w(params basic_auth)
5
+
6
+ attr_accessor :hosts,
7
+ :port,
8
+ :username,
9
+ :password,
10
+ :database,
11
+ :time_precision,
12
+ :use_ssl,
13
+ :auth_method,
14
+ :initial_delay,
15
+ :max_delay,
16
+ :open_timeout,
17
+ :read_timeout,
18
+ :retry,
19
+ :denormalize
20
+
21
+ attr_reader :async, :udp
22
+
23
+ # rubocop:disable all
24
+ def initialize(opts = {})
25
+ @database = opts[:database]
26
+ @hosts = Array(opts[:hosts] || opts[:host] || ["localhost"])
27
+ @port = opts.fetch(:port, 8086)
28
+ @username = opts.fetch(:username, "root")
29
+ @password = opts.fetch(:password, "root")
30
+ @auth_method = AUTH_METHODS.include?(opts[:auth_method]) ? opts[:auth_method] : "params"
31
+ @use_ssl = opts.fetch(:use_ssl, false)
32
+ @time_precision = opts.fetch(:time_precision, "s")
33
+ @initial_delay = opts.fetch(:initial_delay, 0.01)
34
+ @max_delay = opts.fetch(:max_delay, 30)
35
+ @open_timeout = opts.fetch(:write_timeout, 5)
36
+ @read_timeout = opts.fetch(:read_timeout, 300)
37
+ @async = opts.fetch(:async, false)
38
+ @udp = opts.fetch(:udp, false)
39
+ @retry = opts.fetch(:retry, nil)
40
+ @denormalize = opts.fetch(:denormalize, true)
41
+ @retry =
42
+ case @retry
43
+ when Integer
44
+ @retry
45
+ when true, nil
46
+ -1
47
+ when false
48
+ 0
49
+ end
50
+ end
51
+
52
+ def udp?
53
+ !!udp
54
+ end
55
+
56
+ def async?
57
+ !!async
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,40 @@
1
+ require "net/http"
2
+ require "zlib"
3
+
4
+ module InfluxDB # :nodoc:
5
+ class Error < StandardError
6
+ end
7
+
8
+ class AuthenticationError < Error
9
+ end
10
+
11
+ class ConnectionError < Error
12
+ end
13
+
14
+ class SeriesNotFound < Error
15
+ end
16
+
17
+ class JSONParserError < Error
18
+ end
19
+
20
+ class QueryError < Error
21
+ end
22
+
23
+ # Taken from: https://github.com/lostisland/faraday/blob/master/lib/faraday/adapter/net_http.rb
24
+ NET_HTTP_EXCEPTIONS = [
25
+ EOFError,
26
+ Errno::ECONNABORTED,
27
+ Errno::ECONNREFUSED,
28
+ Errno::ECONNRESET,
29
+ Errno::EHOSTUNREACH,
30
+ Errno::EINVAL,
31
+ Errno::ENETUNREACH,
32
+ Net::HTTPBadResponse,
33
+ Net::HTTPHeaderSyntaxError,
34
+ Net::ProtocolError,
35
+ SocketError,
36
+ Zlib::GzipFile::Error
37
+ ]
38
+
39
+ NET_HTTP_EXCEPTIONS << OpenSSL::SSL::SSLError if defined?(OpenSSL)
40
+ end
@@ -0,0 +1,22 @@
1
+ require 'logger'
2
+
3
+ module InfluxDB
4
+ module Logging # :nodoc:
5
+ PREFIX = "[InfluxDB] "
6
+
7
+ class << self
8
+ attr_writer :logger
9
+ end
10
+
11
+ def self.logger
12
+ return false if @logger == false
13
+ @logger ||= ::Logger.new(STDERR).tap { |logger| logger.level = Logger::INFO }
14
+ end
15
+
16
+ private
17
+
18
+ def log(level, message)
19
+ InfluxDB::Logging.logger.send(level.to_sym, PREFIX + message) if InfluxDB::Logging.logger
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,18 @@
1
+ require "thread"
2
+
3
+ module InfluxDB
4
+ # Queue with max length limit
5
+ class MaxQueue < Queue
6
+ attr_reader :max
7
+
8
+ def initialize(max = 10_000)
9
+ fail ArgumentError, "queue size must be positive" unless max > 0
10
+ @max = max
11
+ super()
12
+ end
13
+
14
+ def push(obj)
15
+ super if length < @max
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,31 @@
1
+ module InfluxDB
2
+ class PointValue
3
+ attr_reader :series, :values, :tags, :timestamp
4
+
5
+ def initialize(data)
6
+ @series = data[:series].gsub(/\s/, '\ ')
7
+ @values = stringify(data[:values])
8
+ @tags = stringify(data[:tags])
9
+ @timestamp = data[:timestamp]
10
+ end
11
+
12
+ def dump
13
+ dump = "#{@series}"
14
+ dump << ",#{@tags}" if @tags
15
+ dump << " #{@values}"
16
+ dump << " #{@timestamp}" if @timestamp
17
+ dump
18
+ end
19
+
20
+ private
21
+
22
+ def stringify(hash)
23
+ return nil unless hash && !hash.empty?
24
+ hash.map do |k,v|
25
+ key = k.to_s.gsub(/\s/, '\ ')
26
+ val = v.is_a?(String) ? v.gsub(/\s/, '\ ') : v
27
+ "#{key}=#{val}"
28
+ end.join(',')
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ module InfluxDB
2
+ module Query
3
+ module Cluster # :nodoc:
4
+ def create_cluster_admin(username, password)
5
+ execute("CREATE USER #{username} WITH PASSWORD '#{password}' WITH ALL PRIVILEGES")
6
+ end
7
+
8
+ def list_cluster_admins
9
+ list_users.select{|u| u['admin']}.map {|u| u.delete_if {|k,_| k == 'admin'}}
10
+ end
11
+
12
+ def revoke_cluster_admin_privileges(username)
13
+ execute("REVOKE ALL PRIVILEGES FROM #{username}")
14
+ end
15
+ end
16
+ end
17
+ end