flydata 0.2.1 → 0.2.2

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: c9c27e5aaf80b5e422b45d86afd09ce77e50e89a
4
- data.tar.gz: cf978a752a26f2700aefc8882c9240278a75f051
3
+ metadata.gz: 0a3fbdc5a0e42708b1dd6f173c366810e4f58892
4
+ data.tar.gz: 85a647490961e9b85baea5524b9c0f4f395a9d64
5
5
  SHA512:
6
- metadata.gz: bfc17189f1057dc79b2e1e969d005bcc587f28bff69340e444d2e6a11176688a7b26ef3074045b8df212a7b652a471b626d1a36a0e470064a9fdc5f2515e2170
7
- data.tar.gz: 4578998bd6fb444acc86acb82ae141396f279661d4c5aacb4592c67540951eb5651212566ba3fd61f6259c4b0201ca9e16f4c9c64d98ab981557bd46e25389d6
6
+ metadata.gz: 200c29e2a64610cd4561ae127365f934b9cef0ef9dad264fed54d191bc3ec779eb22fe507d4d55c6373928a560c807dafa0cc0883243b0865723a62649349d40
7
+ data.tar.gz: 2a5b4a1e246b448e6fffe76c6fff8416298d22c4e46719109d6d9c2fca99c3f2ddb1cc3e70cb5b5bf9afbac388c3048b1b840c05952bcc0ab9e14a53c89b901f
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.1
1
+ 0.2.2
data/bin/serverinfo ADDED
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env bash
2
+ detect_system() {
3
+ # refer to https://github.com/wayneeseguin/rvm/blob/master/scripts/functions/utility_system
4
+ unset _system_info _system_lib_type
5
+ export _system_info _system_lib_type
6
+ _system_info="$(command uname -a)"
7
+ _system_lib_type="unknown"
8
+ case "$(command uname)" in
9
+ (Linux|GNU*)
10
+ if [[ -f /etc/lsb-release ]]; then
11
+ _system_lib_type="debian"
12
+ elif [[ -f /etc/debian_version ]]; then
13
+ _system_lib_type="debian"
14
+ elif [[ -f /etc/os-release ]]; then
15
+ _system_lib_type="debian"
16
+ elif [[ -f /etc/system-release ]]; then
17
+ _system_lib_type="centos"
18
+ elif [[ -f /etc/centos-release ]]; then
19
+ _system_lib_type="centos"
20
+ elif [[ -f /etc/redhat-release ]]; then
21
+ _system_lib_type="centos"
22
+ fi
23
+ ;;
24
+ #(Darwin)
25
+ # _system_lib_type="osx"
26
+ # ;;
27
+ (*)
28
+ ;;
29
+ esac
30
+ }
31
+
32
+ log_server_details()
33
+ {
34
+ echo "Logging ip address"
35
+ ip addr show
36
+
37
+ echo "Logging ulimit and uname"
38
+ ulimit -a
39
+ uname -a
40
+
41
+ echo "Logging repo details"
42
+ detect_system
43
+ if [ "${_system_lib_type}" = "debian" ]; then
44
+ grep -RoPish --include="*.list" "(?<=^deb\s).*?(?=#|$)" /etc/apt
45
+ else
46
+ yum repolist all
47
+ fi
48
+
49
+ echo "Logging release details"
50
+ cat /etc/*-release
51
+ }
52
+
53
+ log_server_details
data/flydata.gemspec CHANGED
@@ -2,19 +2,19 @@
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.2.1 ruby lib
5
+ # stub: flydata 0.2.2 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "flydata"
9
- s.version = "0.2.1"
9
+ s.version = "0.2.2"
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"]
14
- s.date = "2014-08-20"
14
+ s.date = "2014-09-12"
15
15
  s.description = "FlyData Command Line Interface"
16
16
  s.email = "sysadmin@flydata.co"
17
- s.executables = ["fdmysqldump", "flydata"]
17
+ s.executables = ["fdmysqldump", "flydata", "serverinfo"]
18
18
  s.files = [
19
19
  ".gitignore",
20
20
  ".rspec",
@@ -24,6 +24,7 @@ Gem::Specification.new do |s|
24
24
  "VERSION",
25
25
  "bin/fdmysqldump",
26
26
  "bin/flydata",
27
+ "bin/serverinfo",
27
28
  "flydata.gemspec",
28
29
  "lib/fly_data_model.rb",
29
30
  "lib/flydata.rb",
data/lib/flydata/cli.rb CHANGED
@@ -33,6 +33,7 @@ module Flydata
33
33
  puts
34
34
  print_usage
35
35
  raise e if FLYDATA_DEBUG
36
+ exit 1
36
37
  end
37
38
  end
38
39
 
@@ -6,7 +6,7 @@ module Flydata
6
6
  on 'n', 'no-daemon', 'Start FlyData agent as a regular program'
7
7
  end
8
8
  end
9
- def start(options_or_show_final_message = {show_final_message: true}) # For backward compatibility. Use only as options going forward
9
+ def start(options_or_show_final_message = {show_final_message: true}) # For backward compatibility. Use only as options going forward
10
10
  if options_or_show_final_message.kind_of? Hash
11
11
  options = options_or_show_final_message
12
12
  else
@@ -23,6 +23,7 @@ module Flydata
23
23
  # Start sender(fluentd) process
24
24
  say('Starting sender process.') unless options[:quiet]
25
25
  Dir.chdir(FLYDATA_HOME){
26
+ Kernel.system("bash #{File.dirname(__FILE__)}/../../../bin/serverinfo", :out => ["#{FLYDATA_HOME}/flydata.log",'a'], :err => ["#{FLYDATA_HOME}/flydata.log",'a'])
26
27
  daemon_option = opts.no_daemon? ? "" : "-d #{FLYDATA_HOME}/flydata.pid"
27
28
  Kernel.system("fluentd #{daemon_option} -l #{FLYDATA_HOME}/flydata.log -c #{FLYDATA_HOME}/flydata.conf -p #{File.dirname(__FILE__)}/../fluent-plugins")
28
29
  }
@@ -35,16 +35,10 @@ module Flydata
35
35
  end
36
36
  exit 1
37
37
  end
38
- de = retrieve_data_entries.first
39
- raise "There are no data entry." unless de
40
- case de['type']
41
- when 'RedshiftMysqlDataEntry'
42
- de = load_sync_info(override_tables(de, tables))
43
- flush_buffer_and_stop unless de['mysql_data_entry_preference']['initial_sync']
44
- sync_mysql_to_redshift(de)
45
- else
46
- raise "No supported data entry. Only mysql-redshift sync is supported."
47
- end
38
+ de = retrieve_data_entry
39
+ de = load_sync_info(override_tables(de, tables))
40
+ flush_buffer_and_stop unless de['mysql_data_entry_preference']['initial_sync']
41
+ sync_mysql_to_redshift(de)
48
42
  end
49
43
 
50
44
  def flush
@@ -65,7 +59,7 @@ module Flydata
65
59
  sender.flush_client_buffer # TODO We should rather delete buffer files
66
60
  sender.stop
67
61
 
68
- de = retrieve_data_entries.first
62
+ de = retrieve_data_entry
69
63
  wait_for_server_buffer
70
64
  cleanup_sync_server(de, tables) unless opts.client?
71
65
  sync_fm = Flydata::FileUtil::SyncFileManager.new(de)
@@ -114,7 +108,7 @@ module Flydata
114
108
  end
115
109
 
116
110
  def check
117
- de = retrieve_data_entries.first
111
+ de = retrieve_data_entry
118
112
  retry_on(RestClient::Exception) do
119
113
  status = do_check(de)
120
114
  if status['complete']
@@ -127,7 +121,7 @@ module Flydata
127
121
 
128
122
  # skip initial sync
129
123
  def skip
130
- de = retrieve_data_entries.first
124
+ de = retrieve_data_entry
131
125
  sync_fm = Flydata::FileUtil::SyncFileManager.new(de)
132
126
  binlog_path = sync_fm.binlog_path
133
127
  `touch #{binlog_path}`
@@ -137,20 +131,28 @@ module Flydata
137
131
  end
138
132
 
139
133
  def generate_table_ddl(*tables)
140
- de = retrieve_data_entries.first
134
+ de = retrieve_data_entry
141
135
  Flydata::Mysql::CompatibilityCheck.new(de['mysql_data_entry_preference']).check
136
+ do_generate_table_ddl(override_tables(de, tables))
137
+ end
142
138
 
139
+ private
140
+
141
+ def retrieve_data_entry
142
+ de = retrieve_data_entries.first
143
143
  raise "There are no data entry." unless de
144
144
  case de['type']
145
145
  when 'RedshiftMysqlDataEntry'
146
- do_generate_table_ddl(override_tables(de, tables))
146
+ mp = de['mysql_data_entry_preference']
147
+ if mp['tables_append_only']
148
+ mp['tables'] = (mp['tables'].split(",") + mp['tables_append_only'].split(",")).uniq.join(",")
149
+ end
147
150
  else
148
151
  raise "No supported data entry. Only mysql-redshift sync is supported."
149
152
  end
153
+ de
150
154
  end
151
155
 
152
- private
153
-
154
156
  def cleanup_sync_server(de, tables = [])
155
157
  puts "Cleaning the server"
156
158
  flydata.data_entry.cleanup_sync(de['id'], tables)
@@ -412,7 +414,7 @@ What's next?
412
414
  Thank you for using FlyData!
413
415
  EOM
414
416
  def complete
415
- de = load_sync_info(retrieve_data_entries.first)
417
+ de = load_sync_info(retrieve_data_entry)
416
418
  sync_fm = Flydata::FileUtil::SyncFileManager.new(de)
417
419
  info = sync_fm.load_dump_pos
418
420
  if info[:status] == STATUS_COMPLETE
@@ -47,6 +47,7 @@ class MysqlBinlogFlydataInput < MysqlBinlogInput
47
47
 
48
48
  config_param :database, :string
49
49
  config_param :tables, :string
50
+ config_param :tables_append_only, :string
50
51
 
51
52
  def configure(conf)
52
53
  super
@@ -55,13 +56,18 @@ class MysqlBinlogFlydataInput < MysqlBinlogInput
55
56
  raise "No position file(#{@position_file}). Initial synchronization is required before starting."
56
57
  end
57
58
  load_custom_conf
58
- $log.info "mysql host:\"#{@host}\" username:\"#{@username}\" database:\"#{@database}\" tables:\"#{@tables}\""
59
+ $log.info "mysql host:\"#{@host}\" username:\"#{@username}\" database:\"#{@database}\" tables:\"#{@tables}\" tables_append_only:\"#{tables_append_only}\""
59
60
  @tables = @tables.split(/,\s*/)
61
+ @omit_events = Hash.new
62
+ @tables_append_only.split(/,\s*/).each do |table|
63
+ @tables << table unless @tables.include?(table)
64
+ @omit_events[table] = [:delete]
65
+ end
60
66
  sync_fm = Flydata::FileUtil::SyncFileManager.new(nil) # Passing nil for data_entry as this class does not use methods which require data_entry
61
67
 
62
68
  @context = Mysql::Context.new(
63
69
  database: @database, tables: @tables,
64
- tag: @tag, sync_fm: sync_fm
70
+ tag: @tag, sync_fm: sync_fm, omit_events: @omit_events
65
71
  )
66
72
  @record_dispatcher = Mysql::FlydataBinlogRecordDispatcher.new(@context)
67
73
  end
@@ -50,6 +50,10 @@ module Mysql
50
50
  acceptable
51
51
  end
52
52
 
53
+ def acceptable_event?(type, table)
54
+ @context.omit_events[table].nil? || !@context.omit_events[table].include?(type)
55
+ end
56
+
53
57
  def emit_record(type, record, opt = {})
54
58
  return unless acceptable_db?(record)
55
59
  return unless record["table_name"].nil? or acceptable_table?(record, record["table_name"])
@@ -62,7 +66,7 @@ module Mysql
62
66
 
63
67
  table = records.first[TABLE_NAME] || record['table_name']
64
68
  raise "Missing table name. #{record}" if table.to_s.empty?
65
- return unless acceptable_table?(record, table)
69
+ return unless acceptable_table?(record, table) && acceptable_event?(type, table)
66
70
 
67
71
  table_rev = @context.sync_fm.table_rev(table)
68
72
  position = record['next_position'] - record['event_length']
@@ -1,10 +1,10 @@
1
1
  module Mysql
2
2
  class Context
3
3
  MANDATORY_OPTS = [
4
- :database, :tables, :tag, :sync_fm,
4
+ :database, :tables, :tag, :sync_fm, :omit_events
5
5
  ]
6
6
  OPTIONAL_OPTS = [
7
- :current_binlog_file,
7
+ :current_binlog_file
8
8
  ]
9
9
 
10
10
  (MANDATORY_OPTS + OPTIONAL_OPTS).each do |opt|
@@ -72,6 +72,7 @@ module Fluent
72
72
  mysql_data_entry_preference: {
73
73
  database: {},
74
74
  tables: {},
75
+ tables_append_only: {},
75
76
  host: {},
76
77
  username: {},
77
78
  password: {encrypted: true},
@@ -20,7 +20,7 @@ module Flydata
20
20
  expect(a.to_hash).to be_empty
21
21
  }.and_return(command_obj)
22
22
  expect(command_obj).to receive(:run)
23
-
23
+
24
24
  subject.run
25
25
  end
26
26
  end
@@ -30,7 +30,7 @@ module Flydata
30
30
  expect(subject).to receive(:puts).with('! Unknown options -a, --host-name').once
31
31
  allow(subject).to receive(:puts)
32
32
 
33
- subject.run
33
+ expect{ subject.run }.to terminate.with_code(1)
34
34
  end
35
35
  end
36
36
  context 'with arguments' do
@@ -65,7 +65,7 @@ module Flydata
65
65
  expect(subject).to receive(:puts).with('! Unknown options -a, --host-name').once
66
66
  allow(subject).to receive(:puts)
67
67
 
68
- subject.run
68
+ expect{ subject.run }.to terminate.with_code(1)
69
69
  end
70
70
  end
71
71
  end
@@ -21,6 +21,8 @@ module Flydata
21
21
  let(:args) { [] }
22
22
  it "starts fluend with daemon option" do
23
23
  expect(Kernel).to receive(:system).with( Regexp.new(
24
+ "bash .+/../../../bin/serverinfo"), :out => [Regexp.new(".+/flydata.log"),'a'], :err => [Regexp.new(".+/flydata.log"),'a'])
25
+ expect(Kernel).to receive(:system).with( Regexp.new(
24
26
  "fluentd -d .+/flydata.pid -l .+/flydata.log -c .+/flydata.conf -p .+/\.\./fluent-plugins"))
25
27
  subject.start(false)
26
28
  end
@@ -28,6 +30,8 @@ module Flydata
28
30
  context "as regular process" do
29
31
  let(:args) { ["-n"] }
30
32
  it "starts fluentd with no daemon option" do
33
+ expect(Kernel).to receive(:system).with( Regexp.new(
34
+ "bash .+/../../../bin/serverinfo"), :out => [Regexp.new(".+/flydata.log"),'a'], :err => [Regexp.new(".+/flydata.log"),'a'])
31
35
  expect(Kernel).to receive(:system).with(Regexp.new(
32
36
  "fluentd -l .+/flydata.log -c .+/flydata.conf -p .+/\.\./fluent-plugins"))
33
37
  subject.start(false)
@@ -36,6 +40,8 @@ module Flydata
36
40
  context "as regular process with long option" do
37
41
  let(:args) { ["--no-daemon"] }
38
42
  it "starts fluentd with no daemon option" do
43
+ expect(Kernel).to receive(:system).with( Regexp.new(
44
+ "bash .+/../../../bin/serverinfo"), :out => [Regexp.new(".+/flydata.log"),'a'], :err => [Regexp.new(".+/flydata.log"),'a'])
39
45
  expect(Kernel).to receive(:system).with(Regexp.new(
40
46
  "fluentd -l .+/flydata.log -c .+/flydata.conf -p .+/\.\./fluent-plugins"))
41
47
  subject.start(false)
@@ -18,10 +18,27 @@ module Fluent
18
18
  TEST_POSITION_FILE = "test_position.log"
19
19
  TEST_REVISION_FILE = File.join(FLYDATA_HOME, "positions/#{TEST_TABLE}.rev")
20
20
  TEST_TIMESTAMP = 1389214083
21
+ TEST_TABLE_APPEND_ONLY = "test_table_4"
22
+ TEST_SEQUENCE_FILE_1 = File.join(FLYDATA_HOME, "positions/#{TEST_TABLE_APPEND_ONLY}.pos")
21
23
  TEST_CONFIG = <<EOT
22
24
  tag #{TEST_TAG}
23
25
  database #{TEST_DB}
24
26
  tables #{TEST_TABLES}
27
+ tables_append_only
28
+ position_file #{TEST_POSITION_FILE}
29
+ EOT
30
+ TEST_TABLES_APPEND_ONLY_CONFIG = <<EOT
31
+ tag #{TEST_TAG}
32
+ database #{TEST_DB}
33
+ tables #{TEST_TABLES}
34
+ tables_append_only #{TEST_TABLE_APPEND_ONLY}
35
+ position_file #{TEST_POSITION_FILE}
36
+ EOT
37
+ TEST_TABLES_DUPLICATE_CONFIG = <<EOT
38
+ tag #{TEST_TAG}
39
+ database #{TEST_DB}
40
+ tables #{TEST_TABLES},#{TEST_TABLE_APPEND_ONLY}
41
+ tables_append_only #{TEST_TABLE_APPEND_ONLY}
25
42
  position_file #{TEST_POSITION_FILE}
26
43
  EOT
27
44
 
@@ -189,11 +206,13 @@ EOT
189
206
  def setup_initial_flydata_files
190
207
  %w(positions dump conf).each{|f| FileUtils.mkdir_p(File.join(FLYDATA_HOME, f))}
191
208
  create_file(TEST_SEQUENCE_FILE, TEST_SEQUENCE_NUM.to_s)
209
+ create_file(TEST_SEQUENCE_FILE_1, TEST_SEQUENCE_NUM.to_s)
192
210
  end
193
211
 
194
212
  def cleanup_flydata_files
195
213
  %w(positions dump conf).each{|f| FileUtils.rm_rf(File.join(FLYDATA_HOME, f))}
196
214
  delete_file(TEST_SEQUENCE_FILE)
215
+ delete_file(TEST_SEQUENCE_FILE_1)
197
216
  end
198
217
 
199
218
  before do
@@ -409,6 +428,56 @@ EOT
409
428
  expect_no_emitted_record(event)
410
429
  end
411
430
  end
431
+
432
+ context 'for append only' do
433
+ shared_examples 'emits records correctly' do
434
+ it 'emits records when it receives an insert event for append only table' do
435
+ event = insert_event
436
+ event['table_name'] = TEST_TABLE_APPEND_ONLY
437
+ expect_emitted_records_with_rows(event, :insert, TEST_TABLE_APPEND_ONLY, 628, "mysql-bin.000048",
438
+ [{"1"=>"0SL00000001", "2"=>"foo"}, {"1"=>"0SL00000002", "2"=>"var"}, {"1"=>"0SL00000003", "2"=>"hoge"}])
439
+ end
440
+
441
+ it 'does not emit a record when it receives a delete event for append only table' do
442
+ event = delete_event
443
+ event['table_name'] = TEST_TABLE_APPEND_ONLY
444
+ expect_no_emitted_record(event)
445
+ end
446
+
447
+ it 'emits records when it receives an update event for append only table' do
448
+ event = update_event
449
+ event['table_name'] = TEST_TABLE_APPEND_ONLY
450
+ expect_emitted_records_with_rows(event, :update, TEST_TABLE_APPEND_ONLY, 2528, "mysql-bin.000048",
451
+ [{"1"=>"0SL00000001", "2"=>"wow"}, {"1"=>"0SL00000003", "2"=>"fuga"}])
452
+ end
453
+
454
+ it 'emits a record when it receives a delete event for non-append only table' do
455
+ expect_emitted_records_with_rows(delete_event, :delete, TEST_TABLE, 5324, "mysql-bin.000048",
456
+ [{"1"=>"0SL00000002", "2"=>"var"}, {"1"=>"0SL00000003", "2"=>"hoge"}])
457
+ end
458
+
459
+ it 'emits records when it receives an insert event for non-append only table' do
460
+ expect_emitted_records_with_rows(insert_event, :insert, TEST_TABLE, 628, "mysql-bin.000048",
461
+ [{"1"=>"0SL00000001", "2"=>"foo"}, {"1"=>"0SL00000002", "2"=>"var"}, {"1"=>"0SL00000003", "2"=>"hoge"}])
462
+ end
463
+ end
464
+
465
+ context 'no duplicate entries in tables and tables_append_only' do
466
+ before do
467
+ Test.configure_plugin(plugin, TEST_TABLES_APPEND_ONLY_CONFIG)
468
+ plugin.event_listener(rotate_event)
469
+ end
470
+ include_examples 'emits records correctly'
471
+ end
472
+
473
+ context 'duplicate entries in tables and tables_append_only' do
474
+ before do
475
+ Test.configure_plugin(plugin, TEST_TABLES_DUPLICATE_CONFIG)
476
+ plugin.event_listener(rotate_event)
477
+ end
478
+ include_examples 'emits records correctly'
479
+ end
480
+ end
412
481
  end
413
482
  end
414
483
 
data/spec/spec_helper.rb CHANGED
@@ -33,5 +33,43 @@ RSpec.configure do |config|
33
33
  end
34
34
  end
35
35
  end
36
+ end
37
+
38
+ # https://gist.github.com/stevenharman/2355172
39
+ RSpec::Matchers.define :terminate do |code|
40
+ actual = nil
41
+
42
+ def supports_block_expectations?
43
+ true
44
+ end
45
+
46
+ match do |block|
47
+ begin
48
+ block.call
49
+ rescue SystemExit => e
50
+ actual = e.status
51
+ end
52
+ actual and actual == status_code
53
+ end
54
+
55
+ chain :with_code do |status_code|
56
+ @status_code = status_code
57
+ end
58
+
59
+ failure_message_for_should do |block|
60
+ "expected block to call exit(#{status_code}) but exit" +
61
+ (actual.nil? ? " not called" : "(#{actual}) was called")
62
+ end
63
+
64
+ failure_message_for_should_not do |block|
65
+ "expected block not to call exit(#{status_code})"
66
+ end
67
+
68
+ description do
69
+ "expect block to call exit(#{status_code})"
70
+ end
36
71
 
72
+ def status_code
73
+ @status_code ||= 0
74
+ end
37
75
  end
@@ -8,4 +8,5 @@ mysql_data_entry_preference:
8
8
  #password: abcd
9
9
  #database: dev
10
10
  #tables: users,country,rows
11
+ #tables_append_only: employees
11
12
  #mysqldump_dir: /mnt/dump
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flydata
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Koichi Fujikawa
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-20 00:00:00.000000000 Z
11
+ date: 2014-09-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rest-client
@@ -377,6 +377,7 @@ email: sysadmin@flydata.co
377
377
  executables:
378
378
  - fdmysqldump
379
379
  - flydata
380
+ - serverinfo
380
381
  extensions: []
381
382
  extra_rdoc_files: []
382
383
  files:
@@ -388,6 +389,7 @@ files:
388
389
  - VERSION
389
390
  - bin/fdmysqldump
390
391
  - bin/flydata
392
+ - bin/serverinfo
391
393
  - flydata.gemspec
392
394
  - lib/fly_data_model.rb
393
395
  - lib/flydata.rb