flydata 0.3.8 → 0.3.9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 652af727f40585a7cb70d5b1f71fcdd9beda4a7e
4
- data.tar.gz: f8e94b1dd631c553c787175910695065bcedee65
3
+ metadata.gz: abffe0873e3fe1143a5053d9f02b5c61eb1e90a3
4
+ data.tar.gz: c7f54222297282bcc2c13ffa1ca18ddc45bb10ef
5
5
  SHA512:
6
- metadata.gz: 2e815f1b604d0ba454e82bbaae84ac13b6173507bc4066a138af3221fdaa3327aed5f636e9ebfc2c061b38eda6e0410e3ea6b57dcfcdca592b07f0cc64a6fc01
7
- data.tar.gz: bdb92aee00edac39d02d4a374ca4dbf555c4b80672bcaade05801db0a0d3612715b0d19036c65162f8911401d9c7d31a5c9cc7c30730855cf0778eb53e808816
6
+ metadata.gz: 3b81782a6f4ac5bf2c28485b56ea1912e6f8490d375cb6ffd5f70e070776691fe4d20f74cfb444672192a0440a651b73835765054fc9da617e36182e4c021655
7
+ data.tar.gz: 7ef6babb2f76e4c1e75ff700a377c1b8a9504f022fe1bbe0fe09dc30f54e629277dafe2d55cfd905967edeac55d70d0019181fc1a90000c77172f9f7325961b8
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.8
1
+ 0.3.9
data/flydata.gemspec CHANGED
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: flydata 0.3.8 ruby lib
5
+ # stub: flydata 0.3.9 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "flydata"
9
- s.version = "0.3.8"
9
+ s.version = "0.3.9"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
13
  s.authors = ["Koichi Fujikawa", "Masashi Miyazaki", "Matthew Luu", "Mak Inada", "Sriram NS"]
14
- s.date = "2015-03-09"
14
+ s.date = "2015-03-21"
15
15
  s.description = "FlyData Agent"
16
16
  s.email = "sysadmin@flydata.com"
17
17
  s.executables = ["fdmysqldump", "flydata", "serverinfo"]
@@ -167,7 +167,7 @@ Gem::Specification.new do |s|
167
167
  ]
168
168
  s.homepage = "http://flydata.com/"
169
169
  s.licenses = ["All right reserved."]
170
- s.rubygems_version = "2.4.3"
170
+ s.rubygems_version = "2.2.2"
171
171
  s.summary = "FlyData Agent"
172
172
 
173
173
  if s.respond_to? :specification_version then
@@ -289,9 +289,9 @@ EOS
289
289
  unless Flydata::Preference::DataEntryPreference.conf_exists?(de)
290
290
  Flydata::Command::Conf.new.copy_templates
291
291
  end
292
- generate_mysqldump(de, sync_fm, !opts.dump_stream?) do |mysqldump_io, db_bytesize|
292
+ generate_mysqldump(de, sync_fm, !opts.dump_stream?) do |mysqldump_io, binlog_pos, db_bytesize|
293
293
  sync_fm.save_sync_info(@full_initial_sync, de['mysql_data_entry_preference']['tables'])
294
- parse_mysqldump_and_send(mysqldump_io, dp, de, sync_fm, db_bytesize)
294
+ parse_mysqldump_and_send(mysqldump_io, dp, de, sync_fm, binlog_pos, db_bytesize)
295
295
  end
296
296
  wait_for_mysqldump_processed(dp, de, sync_fm)
297
297
  end
@@ -362,13 +362,13 @@ EOM
362
362
  log_info_stdout("Exporting data from database.")
363
363
  log_info_stdout("This process can take hours depending on data size and load on your database. Please be patient...")
364
364
  if file_dump
365
- Flydata::Parser::Mysql::MysqlDumpGeneratorNoMasterData.
365
+ binlog_pos = Flydata::Parser::Mysql::MysqlDumpGeneratorNoMasterData.
366
366
  new(de['mysql_data_entry_preference'].merge('tables' => target_tables)).dump(fp)
367
367
  log_info_stdout(" -> Done")
368
- call_block_or_return_io(fp, &block)
368
+ call_block_or_return_io(fp, binlog_pos, &block)
369
369
  else
370
370
  Flydata::Parser::Mysql::MysqlDumpGeneratorNoMasterData.
371
- new(de['mysql_data_entry_preference'].merge('tables' => target_tables)).dump {|io| block.call(io, db_bytesize)}
371
+ new(de['mysql_data_entry_preference'].merge('tables' => target_tables)).dump {|io, binlog_pos| block.call(io, binlog_pos, db_bytesize)}
372
372
  end
373
373
  else
374
374
  exit 1
@@ -381,11 +381,11 @@ EOM
381
381
  stat.block_size * stat.blocks_available
382
382
  end
383
383
 
384
- def call_block_or_return_io(fp, &block)
384
+ def call_block_or_return_io(fp, binlog_pos = nil, &block)
385
385
  if block
386
386
  f_io = open_file_io(fp)
387
387
  begin
388
- block.call(f_io)
388
+ block.call(f_io, binlog_pos)
389
389
  return nil
390
390
  ensure
391
391
  f_io.close rescue nil
@@ -418,7 +418,7 @@ EOM
418
418
  # <- checkpoint
419
419
  #...
420
420
  #CREATE TABLE ...
421
- def parse_mysqldump_and_send(mysqldump_io, dp, de, sync_fm, db_bytesize = nil)
421
+ def parse_mysqldump_and_send(mysqldump_io, dp, de, sync_fm, binlog_pos, db_bytesize = nil)
422
422
  # Prepare forwarder
423
423
  de_tag_name = de["tag_name#{env_suffix}"]
424
424
  server_port = dp['server_port']
@@ -435,8 +435,10 @@ EOM
435
435
  dump_pos_info = sync_fm.load_dump_pos
436
436
  option = dump_pos_info || {}
437
437
  if option[:table_name] && option[:last_pos].to_i != -1
438
+ binlog_pos = option[:binlog_pos]
438
439
  log_info_stdout("Resuming... Last processed table: #{option[:table_name]}")
439
440
  else
441
+ option[:binlog_pos] = binlog_pos
440
442
  #If its a new sync, ensure server side resources are clean
441
443
  cleanup_sync_server(de, target_tables_for_api) unless opts.skip_cleanup?
442
444
  end
@@ -448,45 +450,53 @@ EOM
448
450
  tmp_num_inserted_record = 0
449
451
  dump_fp = sync_fm.dump_file_path
450
452
  dump_file_size = File.exists?(dump_fp) ? File.size(dump_fp) : 1
451
- binlog_pos = Flydata::Parser::Mysql::MysqlDumpParser.new(option).parse(
452
- mysqldump_io,
453
- # create table
454
- Proc.new { |mysql_table|
455
- tmp_num_inserted_record = 0
456
- # dump mysql_table for resume
457
- #TODO: make it option
458
- sync_fm.save_mysql_table_marshal_dump(mysql_table)
459
- },
460
- # insert record
461
- Proc.new { |mysql_table, values_set|
462
- mysql_table_name = mysql_table.table_name
463
- records = values_set.collect do |values|
464
- json = generate_json(mysql_table, values)
465
- {table_name: mysql_table_name, log: json}
466
- end
467
- ret = forwarder.emit(records)
468
- tmp_num_inserted_record += 1
469
- ret
470
- },
471
- # checkpoint
472
- Proc.new { |mysql_table, last_pos, bytesize, binlog_pos, state, substate|
473
- # flush if buffer records exist
474
- if tmp_num_inserted_record > 0 && forwarder.buffer_record_count > 0
475
- forwarder.flush # send buffer data to the server before checkpoint
476
- end
477
453
 
478
- # show the current progress
479
- if last_pos.to_i == -1 # stream dump
480
- log_info_stdout(" -> #{as_size(bytesize)} (#{bytesize} byte) completed...")
481
- else
482
- log_info_stdout(" -> #{(last_pos.to_f/dump_file_size * 100).round(1)}% (#{last_pos}/#{dump_file_size}) completed...")
483
- end
454
+ begin
455
+ Flydata::Parser::Mysql::MysqlDumpParser.new(option).parse(
456
+ mysqldump_io,
457
+ # create table
458
+ Proc.new { |mysql_table|
459
+ tmp_num_inserted_record = 0
460
+ # dump mysql_table for resume
461
+ #TODO: make it option
462
+ sync_fm.save_mysql_table_marshal_dump(mysql_table)
463
+ },
464
+ # insert record
465
+ Proc.new { |mysql_table, values_set|
466
+ mysql_table_name = mysql_table.table_name
467
+ records = values_set.collect do |values|
468
+ json = generate_json(mysql_table, values)
469
+ {table_name: mysql_table_name, log: json}
470
+ end
471
+ ret = forwarder.emit(records)
472
+ tmp_num_inserted_record += 1
473
+ ret
474
+ },
475
+ # checkpoint
476
+ Proc.new { |mysql_table, last_pos, bytesize, binlog_pos, state, substate|
477
+ # flush if buffer records exist
478
+ if tmp_num_inserted_record > 0 && forwarder.buffer_record_count > 0
479
+ forwarder.flush # send buffer data to the server before checkpoint
480
+ end
481
+
482
+ # show the current progress
483
+ if last_pos.to_i == -1 # stream dump
484
+ log_info_stdout(" -> #{as_size(bytesize)} (#{bytesize} byte) completed...")
485
+ else
486
+ log_info_stdout(" -> #{(last_pos.to_f/dump_file_size * 100).round(1)}% (#{last_pos}/#{dump_file_size}) completed...")
487
+ end
484
488
 
485
- # save check point
486
- table_name = mysql_table.nil? ? '' : mysql_table.table_name
487
- sync_fm.save_dump_pos(STATUS_PARSING, table_name, last_pos, binlog_pos, state, substate)
488
- }
489
- )
489
+ # save check point
490
+ table_name = mysql_table.nil? ? '' : mysql_table.table_name
491
+ sync_fm.save_dump_pos(STATUS_PARSING, table_name, last_pos, binlog_pos, state, substate)
492
+ }
493
+ )
494
+ rescue DumpParseError =>e
495
+ ee = DumpParseError.new("ERROR: We encountered an error parsing this chunk:\n #{e.message}")
496
+ ee.description = " Please contact support@flydata.com to report the issue."
497
+ ee.set_backtrace e.backtrace
498
+ raise ee
499
+ end
490
500
  forwarder.close
491
501
  log_info_stdout(" -> Done")
492
502
  sync_fm.save_dump_pos(STATUS_WAITING, '', dump_file_size, binlog_pos)
@@ -31,4 +31,7 @@ end
31
31
  class ServerDataProcessingTimeout < AgentError
32
32
  end
33
33
 
34
+ class DumpParseError < AgentError
35
+ end
36
+
34
37
  end
@@ -4,8 +4,8 @@ require 'fluent/plugin/in_mysql_binlog'
4
4
  require 'binlog'
5
5
  require 'kodama'
6
6
 
7
- # Load client library(flydata/cli/lib)
8
- lib = File.expand_path(File.dirname(__FILE__), '/../..')
7
+ # Load client library(flydata-agent/lib)
8
+ lib = File.expand_path(File.join(File.dirname(__FILE__), '../../'))
9
9
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
10
10
  require 'flydata'
11
11
  require 'flydata/sync_file_manager'
@@ -41,14 +41,11 @@ module Flydata
41
41
 
42
42
  class MysqlDumpGenerator
43
43
  def initialize(conf)
44
- @dump_cmd = generate_dump_cmd(conf)
44
+ @conf = conf
45
45
  @db_opts = [:host, :port, :username, :password, :database].inject({}) {|h, sym| h[sym] = conf[sym.to_s]; h}
46
46
  end
47
- def dump(file_path)
48
- raise "subclass must implement the method"
49
- end
50
47
 
51
- def generate_dump_cmd(conf)
48
+ def dump(file_path)
52
49
  raise "subclass must implement the method"
53
50
  end
54
51
  end
@@ -68,6 +65,8 @@ EOS
68
65
  raise ArgumentError.new("file_path or block must be given.")
69
66
  end
70
67
 
68
+ dump_cmd = generate_dump_cmd(@conf, file_path)
69
+
71
70
  # RDS doesn't allow obtaining binlog position using mysqldump. Get it separately and insert it into the dump file.
72
71
  table_locker = create_table_locker
73
72
  table_locker.resume # Lock tables
@@ -78,14 +77,16 @@ EOS
78
77
  wr_io.sync = true
79
78
  wr_io.set_encoding("utf-8")
80
79
  rd_io.extend(DumpStreamIO)
80
+ binlog_pos = nil
81
81
 
82
82
  # start mysqldump
83
- Open3.popen3 @dump_cmd do |cmd_in, cmd_out, cmd_err, wait_thr|
83
+ Open3.popen3 dump_cmd do |cmd_in, cmd_out, cmd_err, wait_thr|
84
84
  cmd_in.close_write
85
85
  cmd_out.set_encoding("utf-8") # mysqldump output must be in UTF-8
86
86
 
87
87
  first_line = cmd_out.gets # wait until first line comes
88
- binlog_file, binlog_pos = table_locker.resume
88
+ binfile, pos = table_locker.resume
89
+ binlog_pos = {binfile: binfile, pos: pos}
89
90
 
90
91
  threads = []
91
92
 
@@ -93,7 +94,7 @@ EOS
93
94
  threads << Thread.new do
94
95
  begin
95
96
  wr_io.print(first_line) # write a first line
96
- filter_dump_stream(cmd_out, wr_io, binlog_file, binlog_pos)
97
+ filter_dump_stream(cmd_out, wr_io)
97
98
  ensure
98
99
  wr_io.close rescue nil
99
100
  end
@@ -109,10 +110,7 @@ EOS
109
110
 
110
111
  if block
111
112
  # call callback function with io if block is given
112
- block.call(rd_io)
113
- elsif file_path
114
- # store data to file
115
- open_file_stream(file_path) {|f| rd_io.each_line{|l| f.print(l)}}
113
+ block.call(rd_io, binlog_pos)
116
114
  end
117
115
 
118
116
  threads.each(&:join)
@@ -123,7 +121,7 @@ EOS
123
121
  end
124
122
  raise errors unless errors.empty?
125
123
  end
126
- true
124
+ binlog_pos
127
125
  rescue
128
126
  # Cleanup
129
127
  FileUtils.rm(file_path) if file_path && File.exists?(file_path)
@@ -136,8 +134,8 @@ EOS
136
134
  end
137
135
  end
138
136
 
139
- def generate_dump_cmd(conf)
140
- Util::MysqlUtil.generate_mysqldump_without_master_data_cmd(conf)
137
+ def generate_dump_cmd(conf, file_path = nil)
138
+ Util::MysqlUtil.generate_mysqldump_without_master_data_cmd(conf.merge(result_file: file_path))
141
139
  end
142
140
 
143
141
  private
@@ -201,25 +199,13 @@ EOS
201
199
  result.first['Value']
202
200
  end
203
201
 
204
- def filter_dump_stream(cmd_out, w_io, binlog_file, binlog_pos)
205
- find_insert_pos = :not_started
202
+ def filter_dump_stream(cmd_out, w_io)
206
203
  cmd_out.each_line do |line|
207
- if find_insert_pos == :not_started && /^-- Server version/ === line
208
- find_insert_pos = :finding
209
- elsif find_insert_pos == :finding && /^--/ === line
210
- # wait before writing the first database queries
211
- # insert binlog pos
212
- change_master = CHANGE_MASTER_TEMPLATE % [binlog_file, binlog_pos]
213
- w_io.print change_master
214
-
215
- find_insert_pos = :found
216
- end
217
204
  w_io.print line
218
205
  w_io.puts unless line.end_with?("\n")
219
206
  w_io.flush
220
207
  end
221
208
  end
222
-
223
209
  end
224
210
 
225
211
  # Custom mysql client that sets config params (eg:-read_timeout) uniformly for all
@@ -258,9 +244,10 @@ EOS
258
244
  end
259
245
 
260
246
  attr_accessor :binlog_pos
247
+ BINLOG_INV_ERROR_CHUNK_SIZE = 250
261
248
 
262
249
  def initialize(option = {})
263
- @binlog_pos = option[:binlog_pos]
250
+ @binlog_pos = option[:binlog_pos] or raise ArgumentError.new("binlog position is required")
264
251
  @option = option
265
252
  end
266
253
 
@@ -291,10 +278,8 @@ EOS
291
278
  state_start = Proc.new do
292
279
  line = readline_proc.call
293
280
 
294
- # -- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000002', MASTER_LOG_POS=120;
295
- m = /^\-\- CHANGE MASTER TO MASTER_LOG_FILE='(?<binfile>[^']+)', MASTER_LOG_POS=(?<pos>\d+)/.match(line)
296
- if m
297
- @binlog_pos = {binfile: m[:binfile], pos: m[:pos].to_i}
281
+ # -- Server version 5.6.21-log
282
+ if line.start_with?('-- Server version')
298
283
  current_state = State::CREATE_TABLE
299
284
  check_point_block.call(nil, dump_io.pos, bytesize, @binlog_pos, current_state)
300
285
  end
@@ -478,8 +463,26 @@ EOS
478
463
  #
479
464
  # We are using the C native method that is like 'split', 'start_with?', 'regexp'
480
465
  # instead of 'String#each_char' and string comparision for the performance.
481
- # 'String#each_char' is twice as slow as the current storategy.
482
- items = target_line.split(',')
466
+ # 'String#each_char' is twice as slow as the current strategy.
467
+ begin
468
+ items = target_line.split(',')
469
+ rescue ArgumentError => e
470
+ # If there are any non-UTF8 characters, split will fail
471
+ # This will go through the chunk of data and cut the data up into chunks to make it
472
+ # displayable to user.
473
+ target_chunk=""
474
+ chunk_size = BINLOG_INV_ERROR_CHUNK_SIZE
475
+ chunk_repeat_count = target_line.length/chunk_size
476
+ for i in 0..chunk_repeat_count
477
+ line_chunk=target_line[(0+(i*chunk_size)..chunk_size+((i*chunk_size)-1))]
478
+ unless line_chunk.valid_encoding?
479
+ target_chunk=line_chunk
480
+ break
481
+ end
482
+ end
483
+ hex_string = target_chunk.bytes.map { |b| sprintf(", 0x%02X",b) }.join
484
+ raise DumpParseError, "raw_text: #{target_chunk}\n hex_dump: #{hex_string}"
485
+ end
483
486
  index = 0
484
487
  cur_state = :next_values
485
488
 
@@ -54,6 +54,9 @@ module Flydata
54
54
  opt = option.dup
55
55
  opt[:command] = 'mysqldump'
56
56
  opt[:custom_option] = '--skip-lock-tables --single-transaction --hex-blob'
57
+ if opt[:result_file]
58
+ opt[:custom_option] << " --result-file=#{opt[:result_file]}"
59
+ end
57
60
  generate_mysql_cmd(opt)
58
61
  end
59
62
 
@@ -1,8 +1,9 @@
1
1
  # coding: utf-8
2
2
  require 'spec_helper'
3
- require 'flydata/parser/mysql/dump_parser'
4
3
  require 'open3'
5
4
  require 'mysql2'
5
+ require 'flydata/command/sync'
6
+ require 'flydata/parser/mysql/dump_parser'
6
7
 
7
8
  module Flydata
8
9
  module Parser
@@ -33,6 +34,7 @@ module Flydata
33
34
  let(:wait_thr) { double(:wait_thr) }
34
35
 
35
36
  let(:default_dump_generator) { MysqlDumpGeneratorNoMasterData.new(default_conf) }
37
+ let(:binlog_pos) { { binfile: 'mysql-bin.000451', pos: 89872 } }
36
38
  let(:mysql_client) do
37
39
  m = double(:mysql_client)
38
40
  allow(m).to receive(:query).with(/TABLES/)
@@ -44,12 +46,6 @@ module Flydata
44
46
  m
45
47
  end
46
48
 
47
- describe '#initialize' do
48
- subject { default_dump_generator.instance_variable_get(:@dump_cmd) }
49
- it { is_expected.to eq('mysqldump -h localhost -P 3306 -uadmin -p"pass" --default-character-set=utf8 --protocol=tcp --skip-lock-tables ' +
50
- '--single-transaction --hex-blob dev users groups') }
51
- end
52
-
53
49
  describe '#dump' do
54
50
  before do
55
51
  expect(Mysql2::Client).to receive(:new).and_return(mysql_client)
@@ -59,8 +55,7 @@ module Flydata
59
55
  it do
60
56
  expect(wait_thr).to receive(:value).and_return(0)
61
57
  expect(stderr).to receive(:each_line).and_yield("")
62
- expect(default_dump_generator.dump(file_path)).to be_truthy
63
- expect(File.exists?(file_path)).to be_truthy
58
+ expect(default_dump_generator.dump(file_path)).to eq(binlog_pos)
64
59
  end
65
60
  end
66
61
  context "when mysqldump exits with status 1" do
@@ -115,7 +110,7 @@ module Flydata
115
110
  describe MysqlDumpParser do
116
111
  let(:file_path) { File.join('/tmp', "flydata_sync_spec_mysqldump_parse_#{Time.now.to_i}") }
117
112
  let(:dump_io) { File.open(file_path, 'r', encoding: "utf-8") }
118
- let(:default_parser) { MysqlDumpParser.new }
113
+ let(:default_parser) { MysqlDumpParser.new(binlog_pos: default_binlog_pos) }
119
114
 
120
115
  def generate_dump_file(content)
121
116
  File.open(file_path, 'w') {|f| f.write(content)}
@@ -155,9 +150,9 @@ EOT
155
150
  content.index(string) + string.bytesize + 1
156
151
  end
157
152
 
158
- let(:default_parser) { MysqlDumpParser.new }
153
+ let(:default_parser) { MysqlDumpParser.new(binlog_pos: default_binlog_pos) }
159
154
  let(:default_binlog_pos) { {binfile: 'mysql-bin.000267', pos: 120 } }
160
- let(:dump_pos_after_binlog_pos) { index_after(DUMP_HEADER, 'MASTER_LOG_POS=120;') }
155
+ let(:dump_pos_after_binlog_pos) { index_after(DUMP_HEADER, '5.6.13-log') }
161
156
 
162
157
  let(:create_table_block) { double('create_table_block') }
163
158
  let(:insert_record_block) { double('insert_record_block') }
@@ -167,19 +162,6 @@ EOT
167
162
  generate_dump_file('')
168
163
  end
169
164
 
170
- context 'when dump does not contain binlog pos' do
171
- before { generate_dump_file('dummy content') }
172
- it do
173
- expect(create_table_block).to receive(:call).never
174
- expect(insert_record_block).to receive(:call).never
175
- expect(check_point_block).to receive(:call).never
176
- binlog_pos = default_parser.parse(
177
- dump_io, create_table_block, insert_record_block, check_point_block
178
- )
179
- expect(binlog_pos).to be_nil
180
- end
181
- end
182
-
183
165
  context 'when dump contains only binlog pos' do
184
166
  before { generate_dump_file(<<EOT
185
167
  #{DUMP_HEADER}
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flydata
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.8
4
+ version: 0.3.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Koichi Fujikawa
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2015-03-09 00:00:00.000000000 Z
15
+ date: 2015-03-21 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: rest-client
@@ -607,7 +607,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
607
607
  version: '0'
608
608
  requirements: []
609
609
  rubyforge_project:
610
- rubygems_version: 2.4.3
610
+ rubygems_version: 2.2.2
611
611
  signing_key:
612
612
  specification_version: 4
613
613
  summary: FlyData Agent