filecluster 0.1.7 → 0.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 +7 -0
- data/README.md +13 -0
- data/TODO +1 -1
- data/bin/fc-daemon +5 -7
- data/bin/fc-manage +18 -26
- data/lib/daemon.rb +19 -11
- data/lib/daemon/global_daemon_thread.rb +24 -8
- data/lib/daemon/task_thread.rb +1 -2
- data/lib/fc/db.rb +13 -1
- data/lib/fc/item.rb +1 -1
- data/lib/fc/var.rb +34 -0
- data/lib/fc/version.rb +1 -1
- data/lib/filecluster.rb +1 -0
- data/lib/manage.rb +1 -0
- data/lib/manage/policies.rb +2 -2
- data/lib/manage/show.rb +1 -1
- data/lib/manage/storages.rb +77 -0
- data/lib/manage/var.rb +51 -0
- data/test/daemon_test.rb +19 -3
- data/test/storage_sync.rb +65 -0
- data/test/var_test.rb +33 -0
- metadata +27 -43
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b456dec7949f50fd5082b13bd5aad2c6ecf852c6
|
4
|
+
data.tar.gz: 0c05bff392e9a9fddb9843104cef69335327fd1e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cdd07e299a989e265c94045198104600cf6a87234cea244ca8d32bc4774a2c5820583d21670d2a396da8792f678e573d772a14607e5d19b600e1f516ce3d180a
|
7
|
+
data.tar.gz: cf967fd389881cf282da7a93516710c5998ef4df4fa4790f915781d2134c57ebd2dcdfd41b2e5d52f884748be118ff998baca0960bca1b84e9dffb5617b67188
|
data/README.md
CHANGED
@@ -33,6 +33,19 @@ Selecting available storage to store item by policy.create_storages (from left t
|
|
33
33
|
|
34
34
|
Selecting available storage to copy item by policy.copy_storages (from left to tight) with the nearest copy_id.
|
35
35
|
|
36
|
+
## Variables
|
37
|
+
|
38
|
+
|name|default value|description|
|
39
|
+
|:---|:-----------:|:---------|
|
40
|
+
|global_daemon_host||set fc_daemon when run global daemon task (only in one instance)|
|
41
|
+
|daemon_cycle_time|30|time between global daemon checks and storages available checks|
|
42
|
+
|daemon_global_wait_time|120|time between runs global daemon if it does not running|
|
43
|
+
|daemon_global_tasks_group_limit|1000|limit for select for tasks|
|
44
|
+
|daemon_global_error_items_ttl|86400|ttl for items with error status before delete|
|
45
|
+
|daemon_global_error_items_storages_ttl|86400|ttl for items_storages with error status before delete|
|
46
|
+
|daemon_global_tasks_per_thread|10|tasks count for one task thread|
|
47
|
+
|daemon_global_tasks_threads_limit|3|tasks threads count limit for one storage|
|
48
|
+
|
36
49
|
## Usage
|
37
50
|
|
38
51
|
TODO: Write usage instructions here
|
data/TODO
CHANGED
data/bin/fc-daemon
CHANGED
@@ -18,10 +18,8 @@ $global_daemon_thread = nil
|
|
18
18
|
|
19
19
|
default_db_config = File.expand_path(File.dirname(__FILE__))+'/db.yml'
|
20
20
|
descriptions = {
|
21
|
-
:config => {:short => 'c', :full => 'config',
|
22
|
-
:log_level => {:short => 'l', :full => 'log_level',
|
23
|
-
:cycle_time => {:short => 't', :full => 'time', :default => 30, :text => 'Time between checks database and storages available, default 30'},
|
24
|
-
:global_wait => {:short => 'g', :full => 'wait', :default => 120, :text => 'Time between runs global daemon if it does not running, default 120'},
|
21
|
+
:config => {:short => 'c', :full => 'config', :default => default_db_config, :text => "path to db.yml file, default #{default_db_config}"},
|
22
|
+
:log_level => {:short => 'l', :full => 'log_level', :default => 'info', :text => 'log level (fatal, error, warn, info or debug), default info'},
|
25
23
|
:curr_host => {:short => 'h', :full => 'host', :default => FC::Storage.curr_host, :text => "Host for storages, default #{FC::Storage.curr_host}"}
|
26
24
|
}
|
27
25
|
desc = %q{Run FileCluster daemon.
|
@@ -42,7 +40,7 @@ FC::DB.connect_by_config(db_options.merge(:reconnect => true, :multi_threads =>
|
|
42
40
|
$log.info('Connected to database')
|
43
41
|
|
44
42
|
def quit_on_quit
|
45
|
-
$log.info('Exit signal')
|
43
|
+
Thread.new { $log.info('Exit signal') }.join
|
46
44
|
$exit_signal = true
|
47
45
|
end
|
48
46
|
trap("TERM") {quit_on_quit}
|
@@ -59,12 +57,12 @@ while true do
|
|
59
57
|
$log.info('Exit')
|
60
58
|
exit
|
61
59
|
else
|
62
|
-
run_global_daemon
|
60
|
+
run_global_daemon
|
63
61
|
update_storages
|
64
62
|
storages_check
|
65
63
|
update_tasks
|
66
64
|
run_tasks
|
67
65
|
end
|
68
66
|
$log.debug('sleep')
|
69
|
-
sleep
|
67
|
+
sleep FC::Var.get('daemon_cycle_time', 30).to_i
|
70
68
|
end
|
data/bin/fc-manage
CHANGED
@@ -24,6 +24,8 @@ Command:
|
|
24
24
|
rm <name> delete storage
|
25
25
|
change <name> change storage attributes
|
26
26
|
update_size <name> update storage size
|
27
|
+
sync_info show information about synchronization storage and file system
|
28
|
+
sync synchronize storage and file system
|
27
29
|
}],
|
28
30
|
'policies' => [
|
29
31
|
'show list and manage plicies',
|
@@ -37,13 +39,21 @@ Command:
|
|
37
39
|
}],
|
38
40
|
'show' => [
|
39
41
|
'show variable',
|
40
|
-
%q{Usage: fc-manage [options] show <
|
41
|
-
|
42
|
+
%q{Usage: fc-manage [options] show <command>
|
43
|
+
Command:
|
42
44
|
current_host show current host
|
43
45
|
global_daemon show host and uptime where run global daemon
|
44
46
|
errors [<count>] show last count (default 10) errors
|
45
47
|
host_info [<host>] show info for host (default current host)
|
46
48
|
items_info show items statistics
|
49
|
+
}],
|
50
|
+
'var' => [
|
51
|
+
'show and change FC::Var',
|
52
|
+
%q{Usage: fc-manage [options] var <command>
|
53
|
+
Command:
|
54
|
+
list show all FC::Var-s
|
55
|
+
show <variable> show current value for variable
|
56
|
+
change <variable> change variable
|
47
57
|
}]
|
48
58
|
}
|
49
59
|
desc = %q{Get info and manage for storages, policies and items.
|
@@ -66,8 +76,7 @@ if ARGV.empty?
|
|
66
76
|
exit
|
67
77
|
end
|
68
78
|
|
69
|
-
|
70
|
-
when 'help'
|
79
|
+
if command == 'help'
|
71
80
|
if !ARGV[1]
|
72
81
|
puts $options['optparse']
|
73
82
|
elsif commands_help[ARGV[1]]
|
@@ -75,31 +84,14 @@ when 'help'
|
|
75
84
|
else
|
76
85
|
puts "'#{command}' is not a fc-manage command. See 'fc-manage --help'."
|
77
86
|
end
|
78
|
-
|
79
|
-
if !ARGV[1]
|
80
|
-
puts "Need variable name. See 'fc-manage help show'."
|
81
|
-
elsif self.private_methods.member?("show_#{ARGV[1]}".to_sym)
|
82
|
-
send "show_#{ARGV[1]}"
|
83
|
-
else
|
84
|
-
puts "Unknown variable. See 'fc-manage help show'."
|
85
|
-
end
|
86
|
-
when 'storages'
|
87
|
+
elsif commands_help[command]
|
87
88
|
if !ARGV[1]
|
88
89
|
puts "Need command. See 'fc-manage help storages'."
|
89
|
-
elsif self.private_methods.member?("
|
90
|
-
send "
|
90
|
+
elsif self.private_methods.member?("#{command}_#{ARGV[1]}".to_sym)
|
91
|
+
send "#{command}_#{ARGV[1]}"
|
91
92
|
else
|
92
|
-
puts "Unknown command. See 'fc-manage help
|
93
|
-
end
|
94
|
-
when 'policies'
|
95
|
-
if !ARGV[1]
|
96
|
-
puts "Need command. See 'fc-manage help policies'."
|
97
|
-
elsif self.private_methods.member?("policies_#{ARGV[1]}".to_sym)
|
98
|
-
send "policies_#{ARGV[1]}"
|
99
|
-
else
|
100
|
-
puts "Unknown command. See 'fc-manage help policies'."
|
93
|
+
puts "Unknown command. See 'fc-manage help #{command}'."
|
101
94
|
end
|
102
95
|
else
|
103
96
|
puts "'#{command}' is not a fc-manage command. See 'fc-manage --help'."
|
104
|
-
end
|
105
|
-
|
97
|
+
end
|
data/lib/daemon.rb
CHANGED
@@ -15,12 +15,13 @@ class << FC::Error
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
def run_global_daemon
|
18
|
+
def run_global_daemon
|
19
19
|
$log.debug('Run global daemon check')
|
20
|
+
timeout = FC::Var.get('daemon_global_wait_time', 120).to_i
|
20
21
|
r = FC::DB.query("SELECT #{FC::DB.prefix}vars.*, UNIX_TIMESTAMP() as curr_time FROM #{FC::DB.prefix}vars WHERE name='global_daemon_host'").first
|
21
22
|
if !r || r['curr_time'].to_i - r['time'].to_i > timeout
|
22
23
|
$log.debug('Set global daemon host to current')
|
23
|
-
FC::
|
24
|
+
FC::Var.set('global_daemon_host', FC::Storage.curr_host)
|
24
25
|
sleep 1
|
25
26
|
r = FC::DB.query("SELECT #{FC::DB.prefix}vars.*, UNIX_TIMESTAMP() as curr_time FROM #{FC::DB.prefix}vars WHERE name='global_daemon_host'").first
|
26
27
|
end
|
@@ -66,14 +67,17 @@ def update_tasks
|
|
66
67
|
cond = "storage_name in (#{storages_names}) AND status='#{type.to_s}'"
|
67
68
|
ids = $tasks.map{|storage_name, storage_tasks| storage_tasks.select{|task| task[:action] == type}}.
|
68
69
|
flatten.map{|task| task[:item_storage].id}
|
69
|
-
ids += $curr_tasks.select{|task| task[:action] == type}
|
70
|
-
|
70
|
+
ids += $curr_tasks.select{|task| task[:action] == type}.map{|task| task[:item_storage].id}
|
71
|
+
|
72
|
+
limit = FC::Var.get('daemon_global_tasks_group_limit', 1000).to_i
|
71
73
|
cond << "AND id not in (#{ids.join(',')})" if (ids.length > 0)
|
72
|
-
cond << "LIMIT
|
74
|
+
cond << " LIMIT #{limit}"
|
73
75
|
FC::ItemStorage.where(cond).each do |item_storage|
|
74
|
-
|
75
|
-
|
76
|
-
|
76
|
+
unless ids.include?(item_storage.id)
|
77
|
+
$tasks[item_storage.storage_name] = [] unless $tasks[item_storage.storage_name]
|
78
|
+
$tasks[item_storage.storage_name] << {:action => type, :item_storage => item_storage}
|
79
|
+
$log.debug("task add: type=#{type}, item_storage=#{item_storage.id}")
|
80
|
+
end
|
77
81
|
end
|
78
82
|
end
|
79
83
|
|
@@ -88,9 +92,13 @@ def run_tasks
|
|
88
92
|
$tasks_threads[storage.name].delete_if {|thread| !thread.alive?}
|
89
93
|
tasks_count = $tasks[storage.name] ? $tasks[storage.name].size : 0
|
90
94
|
threads_count = $tasks_threads[storage.name].count
|
91
|
-
|
92
|
-
|
93
|
-
|
95
|
+
|
96
|
+
# <max_threads> tasks per thread, maximum <tasks_per_thread> threads
|
97
|
+
max_threads = FC::Var.get('daemon_global_tasks_threads_limit', 3).to_i
|
98
|
+
tasks_per_thread = FC::Var.get('daemon_global_tasks_per_thread', 10).to_i
|
99
|
+
|
100
|
+
run_threads_count = (tasks_count/tasks_per_thread.to_f).ceil - threads_count
|
101
|
+
run_threads_count = max_threads if run_threads_count > max_threads
|
94
102
|
$log.debug("tasks_count: #{tasks_count}, threads_count: #{threads_count}, run_threads_count: #{run_threads_count}")
|
95
103
|
run_threads_count.times do
|
96
104
|
$log.debug("spawn TaskThread for #{storage.name}")
|
@@ -6,18 +6,18 @@ class GlobalDaemonThread < BaseThread
|
|
6
6
|
sleep timeout.to_f/2
|
7
7
|
exit if $exit_signal
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
FC::DB.query("UPDATE #{FC::DB.prefix}vars SET val='#{FC::Storage.curr_host}' WHERE name='global_daemon_host'")
|
9
|
+
if FC::Var.get('global_daemon_host') == FC::Storage.curr_host
|
10
|
+
FC::Var.set('global_daemon_host', FC::Storage.curr_host)
|
12
11
|
else
|
13
|
-
$log.info("Exit from GlobalDaemonThread: global daemon already running on #{
|
12
|
+
$log.info("Exit from GlobalDaemonThread: global daemon already running on #{FC::Var.get('global_daemon_host')}")
|
14
13
|
FC::DB.close
|
15
14
|
exit
|
16
15
|
end
|
17
16
|
|
18
17
|
make_item_copies
|
18
|
+
make_deleted_error_items_storages
|
19
|
+
make_deleted_error_items
|
19
20
|
delete_deleted_items
|
20
|
-
#периодическая проверка на item со статусом delete, последним обновлением дольше суток (NOW - time > 86400) и без связанных is - удаление таких из базы
|
21
21
|
#TODO: периодически удалять (проставлять статус delete) для лиших is (число копий больше необходимого)
|
22
22
|
end
|
23
23
|
end
|
@@ -37,9 +37,10 @@ class GlobalDaemonThread < BaseThread
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
+
limit = FC::Var.get('daemon_global_tasks_group_limit', 1000).to_i
|
40
41
|
sql = "SELECT i.id as item_id, i.size, i.copies as item_copies, GROUP_CONCAT(ist.storage_name ORDER BY ist.id) as storages, p.id as policy_id, p.copies as policy_copies "+
|
41
42
|
"FROM #{FC::Item.table_name} as i, #{FC::Policy.table_name} as p, #{FC::ItemStorage.table_name} as ist WHERE "+
|
42
|
-
"i.policy_id = p.id AND ist.item_id = i.id AND i.copies > 0 AND i.copies < p.copies AND i.status = 'ready' AND ist.status <> 'delete' GROUP BY i.id LIMIT
|
43
|
+
"i.policy_id = p.id AND ist.item_id = i.id AND i.copies > 0 AND i.copies < p.copies AND i.status = 'ready' AND ist.status <> 'delete' GROUP BY i.id LIMIT #{limit}"
|
43
44
|
r = FC::DB.query(sql)
|
44
45
|
r.each do |row|
|
45
46
|
$log.info("GlobalDaemonThread: new item_storage for item #{row['item_id']}")
|
@@ -52,8 +53,11 @@ class GlobalDaemonThread < BaseThread
|
|
52
53
|
src_storage = all_storages.detect{|s| item_storages.first == s.name}
|
53
54
|
policy = all_policies.detect{|p| row['policy_id'] == p.id}
|
54
55
|
storage = policy.get_proper_storage_for_copy(row['size'], src_storage.copy_id, item_storages) if src_storage && policy
|
55
|
-
|
56
|
-
|
56
|
+
if storage
|
57
|
+
FC::Item.new(:id => row['item_id']).make_item_storage(storage, 'copy')
|
58
|
+
else
|
59
|
+
error 'No available storage', :item_id => row['item_id']
|
60
|
+
end
|
57
61
|
end
|
58
62
|
end
|
59
63
|
end
|
@@ -69,4 +73,16 @@ class GlobalDaemonThread < BaseThread
|
|
69
73
|
$log.info("GlobalDaemonThread: delete items #{ids}")
|
70
74
|
end
|
71
75
|
end
|
76
|
+
|
77
|
+
def make_deleted_error_items_storages
|
78
|
+
$log.debug("GlobalDaemonThread: make_deleted_error_items_storages")
|
79
|
+
ttl = FC::Var.get('daemon_global_error_items_storages_ttl', 86400).to_i
|
80
|
+
FC::DB.query("UPDATE #{FC::ItemStorage.table_name} SET status = 'delete' WHERE status = 'error' AND time < #{Time.new.to_i - ttl}")
|
81
|
+
end
|
82
|
+
|
83
|
+
def make_deleted_error_items
|
84
|
+
$log.debug("GlobalDaemonThread: make_deleted_error_items")
|
85
|
+
ttl = FC::Var.get('daemon_global_error_items_ttl', 86400).to_i
|
86
|
+
FC::DB.query("UPDATE #{FC::Item.table_name} SET status = 'delete' WHERE status = 'error' AND time < #{Time.new.to_i - ttl}")
|
87
|
+
end
|
72
88
|
end
|
data/lib/daemon/task_thread.rb
CHANGED
@@ -18,7 +18,6 @@ class TaskThread < BaseThread
|
|
18
18
|
|
19
19
|
def make_delete(task)
|
20
20
|
item_storage = task[:item_storage]
|
21
|
-
# TODO: не лазить в базу за item
|
22
21
|
storage = $storages.detect{|s| s.name == item_storage.storage_name}
|
23
22
|
item = FC::Item.find(item_storage.item_id)
|
24
23
|
storage.delete_file(item.name)
|
@@ -30,9 +29,9 @@ class TaskThread < BaseThread
|
|
30
29
|
|
31
30
|
def make_copy(task)
|
32
31
|
item_storage = task[:item_storage]
|
33
|
-
# TODO: не лазить в базу за item, item_storages - перенести на стадию подготовки task-а
|
34
32
|
storage = $storages.detect{|s| s.name == item_storage.storage_name}
|
35
33
|
item = FC::Item.find(item_storage.item_id)
|
34
|
+
return nil unless item && item.status == 'ready'
|
36
35
|
src_item_storage = FC::ItemStorage.where("item_id = ? AND status = 'ready'", item.id).sample
|
37
36
|
unless src_item_storage
|
38
37
|
$log.info("Item ##{item.id} #{item.name} has no ready item_storage")
|
data/lib/fc/db.rb
CHANGED
@@ -48,7 +48,11 @@ module FC
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
-
def
|
51
|
+
def self.server_time
|
52
|
+
FC::DB.query("SELECT UNIX_TIMESTAMP() as curr_time").first['curr_time'].to_i
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.init_db
|
52
56
|
FC::DB.connect.query(%{
|
53
57
|
CREATE TABLE #{@prefix}items (
|
54
58
|
id int NOT NULL AUTO_INCREMENT,
|
@@ -185,12 +189,20 @@ module FC
|
|
185
189
|
CREATE TABLE #{@prefix}vars (
|
186
190
|
name varchar(255) DEFAULT NULL,
|
187
191
|
val varchar(255) DEFAULT NULL,
|
192
|
+
descr text DEFAULT NULL,
|
188
193
|
time int DEFAULT NULL,
|
189
194
|
PRIMARY KEY (name)
|
190
195
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
|
191
196
|
})
|
192
197
|
FC::DB.connect.query("CREATE TRIGGER fc_vars_before_insert BEFORE INSERT on #{@prefix}vars FOR EACH ROW BEGIN #{proc_time} END")
|
193
198
|
FC::DB.connect.query("CREATE TRIGGER fc_vars_before_update BEFORE UPDATE on #{@prefix}vars FOR EACH ROW BEGIN #{proc_time} END")
|
199
|
+
FC::DB.connect.query("INSERT INTO #{@prefix}vars SET name='daemon_cycle_time', val='30', descr='time between global daemon checks and storages available checks'")
|
200
|
+
FC::DB.connect.query("INSERT INTO #{@prefix}vars SET name='daemon_global_wait_time', val='120', descr='time between runs global daemon if it does not running'")
|
201
|
+
FC::DB.connect.query("INSERT INTO #{@prefix}vars SET name='daemon_global_tasks_group_limit', val='1000', descr='limit for select for tasks'")
|
202
|
+
FC::DB.connect.query("INSERT INTO #{@prefix}vars SET name='daemon_global_error_items_ttl', val='86400', descr='ttl for items with error status before delete'")
|
203
|
+
FC::DB.connect.query("INSERT INTO #{@prefix}vars SET name='daemon_global_error_items_storages_ttl', val='86400', descr='ttl for items_storages with error status before delete'")
|
204
|
+
FC::DB.connect.query("INSERT INTO #{@prefix}vars SET name='daemon_global_tasks_per_thread', val='10', descr='tasks count for one task thread'")
|
205
|
+
FC::DB.connect.query("INSERT INTO #{@prefix}vars SET name='daemon_global_tasks_threads_limit', val='3', descr='tasks threads count limit for one storage'")
|
194
206
|
end
|
195
207
|
end
|
196
208
|
end
|
data/lib/fc/item.rb
CHANGED
@@ -7,7 +7,7 @@ module FC
|
|
7
7
|
# Create item by local path.
|
8
8
|
# Additional options:
|
9
9
|
# :replace=true - replace item if it exists
|
10
|
-
# If item_name is part of local_path it
|
10
|
+
# If item_name is part of local_path it processed as inplace - local_path is valid path to the item for policy
|
11
11
|
def self.create_from_local(local_path, item_name, policy, options={})
|
12
12
|
raise 'Path not exists' unless File.exists?(local_path)
|
13
13
|
raise 'Policy is not FC::Policy' unless policy.instance_of?(FC::Policy)
|
data/lib/fc/var.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module FC
|
4
|
+
class Var
|
5
|
+
class << self
|
6
|
+
attr_accessor :cache_time
|
7
|
+
end
|
8
|
+
@cache_time = 120 # ttl for get_all
|
9
|
+
@all_vars = {}
|
10
|
+
|
11
|
+
def self.set(name, val)
|
12
|
+
FC::DB.query("UPDATE #{FC::DB.prefix}vars SET val='#{Mysql2::Client.escape(val.to_s)}' WHERE name='#{Mysql2::Client.escape(name.to_s)}'")
|
13
|
+
FC::DB.query("INSERT IGNORE INTO #{FC::DB.prefix}vars SET val='#{Mysql2::Client.escape(val.to_s)}', name='#{Mysql2::Client.escape(name.to_s)}'")
|
14
|
+
@all_vars[name.to_s] = val.to_s
|
15
|
+
@all_vars[name.to_sym] = val.to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.get(name, default_value = nil)
|
19
|
+
get_all[name] || default_value
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.get_all
|
23
|
+
if !@get_all_read_time || Time.new.to_i - @get_all_read_time > cache_time
|
24
|
+
@all_vars = {}
|
25
|
+
FC::DB.query("SELECT * FROM #{FC::DB.prefix}vars").each do |row|
|
26
|
+
@all_vars[row['name']] = row['val']
|
27
|
+
@all_vars[row['name'].to_sym] = row['val']
|
28
|
+
end
|
29
|
+
@get_all_read_time = Time.new.to_i
|
30
|
+
end
|
31
|
+
@all_vars
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/fc/version.rb
CHANGED
data/lib/filecluster.rb
CHANGED
data/lib/manage.rb
CHANGED
data/lib/manage/policies.rb
CHANGED
@@ -85,8 +85,8 @@ def policies_change
|
|
85
85
|
copy_storages = copy_storages.split(',').select{|s| storages.member?(s.strip)}.join(',').strip unless copy_storages.empty?
|
86
86
|
|
87
87
|
policy.name = name unless name.empty?
|
88
|
-
policy.create_storages =
|
89
|
-
policy.copy_storages =
|
88
|
+
policy.create_storages = create_storages unless create_storages.empty?
|
89
|
+
policy.copy_storages = copy_storages unless copy_storages.empty?
|
90
90
|
policy.copies = copies.to_i unless copies.empty?
|
91
91
|
|
92
92
|
puts %Q{\nStorage
|
data/lib/manage/show.rb
CHANGED
@@ -4,7 +4,7 @@ end
|
|
4
4
|
|
5
5
|
def show_global_daemon
|
6
6
|
r = FC::DB.query("SELECT #{FC::DB.prefix}vars.*, UNIX_TIMESTAMP() as curr_time FROM #{FC::DB.prefix}vars WHERE name='global_daemon_host'").first
|
7
|
-
if r
|
7
|
+
if r['val']
|
8
8
|
puts "Global daemon run on #{r['val']}\nLast run #{r['curr_time']-r['time']} seconds ago."
|
9
9
|
else
|
10
10
|
puts "Global daemon is not runnning."
|
data/lib/manage/storages.rb
CHANGED
@@ -146,6 +146,28 @@ def storages_change
|
|
146
146
|
end
|
147
147
|
end
|
148
148
|
|
149
|
+
def sync_info
|
150
|
+
if storage = find_storage
|
151
|
+
print "Synchronization info for (#{storage.name}) storage and file system (#{storage.path})."
|
152
|
+
make_storages_sync(storage, false)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def storages_sync
|
157
|
+
if storage = find_storage
|
158
|
+
print "Synchronize (#{storage.name}) storage and file system (#{storage.path})."
|
159
|
+
s = Readline.readline("Continue? (y/n) ", false).strip.downcase
|
160
|
+
puts ""
|
161
|
+
if s == "y" || s == "yes"
|
162
|
+
make_storages_sync(storage, true)
|
163
|
+
puts "Synchronize done."
|
164
|
+
storages_update_size
|
165
|
+
else
|
166
|
+
puts "Canceled."
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
149
171
|
private
|
150
172
|
|
151
173
|
def find_storage
|
@@ -154,3 +176,58 @@ def find_storage
|
|
154
176
|
puts "Storage #{name} not found." if !storage
|
155
177
|
storage
|
156
178
|
end
|
179
|
+
|
180
|
+
def make_storages_sync(storage, make_delete, silent = false)
|
181
|
+
# get all items for storage
|
182
|
+
db_items = {}
|
183
|
+
FC::DB.query("SELECT i.name, ist.id FROM #{FC::Item.table_name} as i, #{FC::ItemStorage.table_name} as ist WHERE ist.item_id = i.id AND ist.storage_name = '#{storage.name}'").each do |row|
|
184
|
+
name = row['name'].sub(/\/$/, '').sub(/^\//, '').strip
|
185
|
+
path = ''
|
186
|
+
name.split('/').each do |dir|
|
187
|
+
path << dir
|
188
|
+
db_items[path] = [false, path == name ? row['id'].to_i : nil]
|
189
|
+
path << '/'
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# walk on all storage folders and files
|
194
|
+
delete_files = []
|
195
|
+
process_storage_dir_sync = lambda do |dir = ''|
|
196
|
+
Dir.glob(storage.path+dir+'*').each do |f|
|
197
|
+
path = f.sub(storage.path, '')
|
198
|
+
if db_items[path]
|
199
|
+
db_items[path][0] = true
|
200
|
+
next if db_items[path][1]
|
201
|
+
end
|
202
|
+
delete_files << path if File.file?(f)
|
203
|
+
process_storage_dir_sync.call(path+'/') if File.directory?(f)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
process_storage_dir_sync.call
|
207
|
+
|
208
|
+
# rm delete_files
|
209
|
+
if make_delete
|
210
|
+
delete_files.each do |f|
|
211
|
+
# check in DB again
|
212
|
+
next if FC::DB.query("SELECT ist.id FROM #{FC::Item.table_name} as i, #{FC::ItemStorage.table_name} as ist WHERE ist.item_id = i.id AND ist.storage_name = '#{storage.name}' AND i.name='#{f}'").first
|
213
|
+
path = storage.path+f
|
214
|
+
File.delete(path) rescue nil
|
215
|
+
end
|
216
|
+
end
|
217
|
+
puts "Deleted #{delete_files.count} files" unless silent
|
218
|
+
|
219
|
+
# delete non synchronize items_storages
|
220
|
+
count = 0
|
221
|
+
db_items.values.each do |item|
|
222
|
+
if !item[0] && item[1]
|
223
|
+
count += 1
|
224
|
+
FC::DB.query("DELETE FROM #{FC::ItemStorage.table_name} WHERE id=#{item[1]}") if make_delete
|
225
|
+
end
|
226
|
+
end
|
227
|
+
puts "Deleted #{count} items_storages" unless silent
|
228
|
+
|
229
|
+
# delete empty folders
|
230
|
+
count = `find #{storage.path} -empty -type d`.split("\n").count
|
231
|
+
`find #{storage.path} -empty -type d -delete` if make_delete
|
232
|
+
puts "Deleted #{count} empty folders" unless silent
|
233
|
+
end
|
data/lib/manage/var.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
def var_list
|
2
|
+
vars = FC::DB.query("SELECT * FROM #{FC::DB.prefix}vars WHERE descr IS NOT NULL")
|
3
|
+
if vars.size == 0
|
4
|
+
puts "No vars."
|
5
|
+
else
|
6
|
+
vars.each do |var|
|
7
|
+
puts var['name']
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def var_show
|
13
|
+
if var = find_var
|
14
|
+
puts %Q{Var
|
15
|
+
Name: #{var['name']}
|
16
|
+
Value: #{var['val']}
|
17
|
+
Description: #{var['descr']}}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def var_change
|
22
|
+
if var = find_var
|
23
|
+
puts "Change var #{var['name']}"
|
24
|
+
val = stdin_read_val("Value (now #{var['val']})")
|
25
|
+
|
26
|
+
puts %Q{\nVar
|
27
|
+
Name: #{var['name']}
|
28
|
+
Value: #{val}}
|
29
|
+
s = Readline.readline("Continue? (y/n) ", false).strip.downcase
|
30
|
+
puts ""
|
31
|
+
if s == "y" || s == "yes"
|
32
|
+
begin
|
33
|
+
FC::Var.set(var['name'], val)
|
34
|
+
rescue Exception => e
|
35
|
+
puts "Error: #{e.message}"
|
36
|
+
exit
|
37
|
+
end
|
38
|
+
puts "ok"
|
39
|
+
else
|
40
|
+
puts "Canceled."
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def find_var
|
48
|
+
var = FC::DB.query("SELECT * FROM #{FC::DB.prefix}vars WHERE descr IS NOT NULL AND name='#{Mysql2::Client.escape(ARGV[2].to_s)}'").first
|
49
|
+
puts "Var #{ARGV[2]} not found." unless var
|
50
|
+
var
|
51
|
+
end
|
data/test/daemon_test.rb
CHANGED
@@ -17,9 +17,13 @@ class DaemonTest < Test::Unit::TestCase
|
|
17
17
|
|
18
18
|
@@errors_count = FC::Error.where.count
|
19
19
|
|
20
|
+
FC::Var.set('daemon_cycle_time', 1)
|
21
|
+
FC::Var.set('daemon_global_wait_time', 1)
|
22
|
+
FC::Var.set('daemon_global_error_items_storages_ttl', 0)
|
23
|
+
FC::Var.set('daemon_global_error_items_ttl', 0)
|
20
24
|
@stotage_checks = 0
|
21
25
|
Thread.new do
|
22
|
-
Open3.popen2e("#{daemon_bin} -c #{db_config_file} -l debug -
|
26
|
+
Open3.popen2e("#{daemon_bin} -c #{db_config_file} -l debug -h host1") do |stdin, stdout, t|
|
23
27
|
@@pid = t.pid
|
24
28
|
while line = stdout.readline
|
25
29
|
@stotage_checks += 1 if line =~ /Finish stotage check/i
|
@@ -90,7 +94,8 @@ class DaemonTest < Test::Unit::TestCase
|
|
90
94
|
assert_equal `du -sb /tmp/host1-sda/bla/bla/test$i 2>&1`.to_i, `du -sb /tmp/host$i-sd$j/bla/bla/test$i 2>&1`.to_i
|
91
95
|
end
|
92
96
|
end
|
93
|
-
|
97
|
+
assert_equal @@errors_count, FC::Error.where.count, "new errors in errors table"
|
98
|
+
|
94
99
|
@@policy.copies = 2
|
95
100
|
@@policy.save
|
96
101
|
item_storage = FC::ItemStorage.where('item_id = ? AND storage_name = ?', @item1.id, 'host1-sdc').first
|
@@ -101,7 +106,18 @@ class DaemonTest < Test::Unit::TestCase
|
|
101
106
|
assert_equal @@errors_count, FC::Error.where.count, "new errors in errors table"
|
102
107
|
|
103
108
|
@item1.mark_deleted
|
109
|
+
FC::ItemStorage.where('item_id = ?', @item2.id).each do |item_storage|
|
110
|
+
item_storage.status = 'error'
|
111
|
+
item_storage.save
|
112
|
+
end
|
113
|
+
@item3.status = 'error'
|
114
|
+
@item3.save
|
115
|
+
|
104
116
|
sleep 2
|
105
|
-
assert_raise(RuntimeError,
|
117
|
+
assert_raise(RuntimeError, "Item not deleted after mark_deleted") {@item1.reload}
|
118
|
+
assert_equal 0, FC::ItemStorage.where('item_id = ?', @item2.id).count, "ItemStorages not deleted after status='error'"
|
119
|
+
@item3.reload
|
120
|
+
assert_equal 'delete', @item3.status, "ItemStorages not deleted after status='error'"
|
121
|
+
assert_equal @@errors_count, FC::Error.where.count, "new errors in errors table"
|
106
122
|
end
|
107
123
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'manage'
|
3
|
+
|
4
|
+
class DaemonTest < Test::Unit::TestCase
|
5
|
+
class << self
|
6
|
+
def startup
|
7
|
+
# tmp fake storage dir
|
8
|
+
`rm -rf /tmp/host*-sd*`
|
9
|
+
`mkdir -p /tmp/host1-sda/`
|
10
|
+
|
11
|
+
# test files to copy
|
12
|
+
@@test_file_path = '/tmp/fc_test_file'
|
13
|
+
`dd if=/dev/urandom of=#{@@test_file_path} bs=1M count=1 2>&1`
|
14
|
+
|
15
|
+
@@storage = FC::Storage.new(:name => 'host1-sda', :host => 'host1', :path => '/tmp/host1-sda/', :copy_id => 1, :size_limit => 1000000000, :check_time => Time.new.to_i)
|
16
|
+
@@storage.save
|
17
|
+
@@policy = FC::Policy.new(:create_storages => 'host1-sda', :copy_storages => 'host1-sda', :copies => 1, :name => 'policy 1')
|
18
|
+
@@policy.save
|
19
|
+
end
|
20
|
+
|
21
|
+
def shutdown
|
22
|
+
FC::DB.query("DELETE FROM items_storages")
|
23
|
+
FC::DB.query("DELETE FROM items")
|
24
|
+
FC::DB.query("DELETE FROM policies")
|
25
|
+
FC::DB.query("DELETE FROM storages")
|
26
|
+
`rm -rf /tmp/host*-sd*`
|
27
|
+
`rm -rf #{@@test_file_path}`
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def setup
|
32
|
+
FC::Storage.any_instance.stubs(:host).returns('host1')
|
33
|
+
FC::Storage.stubs(:curr_host).returns('host1')
|
34
|
+
end
|
35
|
+
|
36
|
+
should "sync_all" do
|
37
|
+
assert_nothing_raised { @item1 = FC::Item.create_from_local(@@test_file_path, 'a/test1', @@policy, {:tag => 'test'}) }
|
38
|
+
assert_nothing_raised { @item2 = FC::Item.create_from_local(@@test_file_path, 'a/b/test2', @@policy, {:tag => 'test'}) }
|
39
|
+
assert_nothing_raised { @item3 = FC::Item.create_from_local(@@test_file_path, 'a/b/c/test3', @@policy, {:tag => 'test'}) }
|
40
|
+
assert_nothing_raised { @item4 = FC::Item.create_from_local(@@test_file_path, 'a/b/c/d/test4', @@policy, {:tag => 'test'}) }
|
41
|
+
`mv /tmp/host1-sda/a/test1 /tmp/host1-sda/test1`
|
42
|
+
`mv /tmp/host1-sda/a/b/c/d/test4 /tmp/host1-sda/a/b/c/d/test5`
|
43
|
+
`mkdir /tmp/host1-sda/test_dir`
|
44
|
+
`cp #{@@test_file_path} /tmp/host1-sda/test_dir/t1`
|
45
|
+
`cp #{@@test_file_path} /tmp/host1-sda/test_dir/t2`
|
46
|
+
|
47
|
+
make_storages_sync(@@storage, true, true)
|
48
|
+
|
49
|
+
@item1.reload
|
50
|
+
@item2.reload
|
51
|
+
@item3.reload
|
52
|
+
@item4.reload
|
53
|
+
assert_equal 'error', @item1.status
|
54
|
+
assert_equal 'ready', @item2.status
|
55
|
+
assert_equal 'ready', @item3.status
|
56
|
+
assert_equal 'error', @item4.status
|
57
|
+
size = `du -sb #{@@test_file_path} 2>&1`.to_i
|
58
|
+
assert_equal 0, `du -sb /tmp/host1-sda/test1 2>&1`.to_i
|
59
|
+
assert_equal size, `du -sb /tmp/host1-sda/a/b/test2 2>&1`.to_i
|
60
|
+
assert_equal size, `du -sb /tmp/host1-sda/a/b/c/test3 2>&1`.to_i
|
61
|
+
assert_equal 0, `du -sb /tmp/host1-sda/a/b/c/d/test5 2>&1`.to_i
|
62
|
+
assert_equal 0, `du -sb /tmp/host1-sda/a/b/c/d 2>&1`.to_i
|
63
|
+
assert_equal 0, `du -sb tmp/host1-sda/test_dir 2>&1`.to_i
|
64
|
+
end
|
65
|
+
end
|
data/test/var_test.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class VarTest < Test::Unit::TestCase
|
4
|
+
should "set and get" do
|
5
|
+
assert_nothing_raised {FC::Var.set('test1', 111)}
|
6
|
+
assert_nothing_raised {FC::Var.set(:test2, '222')}
|
7
|
+
assert_equal '111', FC::Var.get(:test1)
|
8
|
+
assert_equal '222', FC::Var.get('test2')
|
9
|
+
end
|
10
|
+
should "change" do
|
11
|
+
assert_nothing_raised {FC::Var.set('test3', '333')}
|
12
|
+
assert_equal '333', FC::Var.get('test3')
|
13
|
+
FC::Var.set('test3', '3332')
|
14
|
+
assert_equal '3332', FC::Var.get('test3')
|
15
|
+
end
|
16
|
+
should "get all" do
|
17
|
+
assert_nothing_raised {FC::Var.set('test4', '444')}
|
18
|
+
assert_nothing_raised {FC::Var.set('test5', '555')}
|
19
|
+
assert_nothing_raised {FC::Var.set('test6', '666')}
|
20
|
+
vars = FC::Var.get_all
|
21
|
+
assert_equal '444', vars['test4']
|
22
|
+
assert_equal '555', vars[:test5]
|
23
|
+
assert_equal '666', vars[:test6]
|
24
|
+
end
|
25
|
+
should "change and get all" do
|
26
|
+
assert_nothing_raised {FC::Var.set('test7', '777')}
|
27
|
+
vars = FC::Var.get_all
|
28
|
+
assert_equal '777', vars['test7']
|
29
|
+
FC::Var.set('test7', '7772')
|
30
|
+
vars = FC::Var.get_all
|
31
|
+
assert_equal '7772', vars['test7']
|
32
|
+
end
|
33
|
+
end
|
metadata
CHANGED
@@ -1,126 +1,111 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: filecluster
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: '0.2'
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- sh
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2013-
|
11
|
+
date: 2013-07-03 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: rb-readline
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - '>='
|
20
18
|
- !ruby/object:Gem::Version
|
21
19
|
version: '0'
|
22
20
|
type: :runtime
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
|
-
- -
|
24
|
+
- - '>='
|
28
25
|
- !ruby/object:Gem::Version
|
29
26
|
version: '0'
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: mysql2
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
|
-
- -
|
31
|
+
- - '>='
|
36
32
|
- !ruby/object:Gem::Version
|
37
33
|
version: '0'
|
38
34
|
type: :runtime
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
|
-
- -
|
38
|
+
- - '>='
|
44
39
|
- !ruby/object:Gem::Version
|
45
40
|
version: '0'
|
46
41
|
- !ruby/object:Gem::Dependency
|
47
42
|
name: bundler
|
48
43
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
44
|
requirements:
|
51
|
-
- -
|
45
|
+
- - '>='
|
52
46
|
- !ruby/object:Gem::Version
|
53
47
|
version: '0'
|
54
48
|
type: :development
|
55
49
|
prerelease: false
|
56
50
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
51
|
requirements:
|
59
|
-
- -
|
52
|
+
- - '>='
|
60
53
|
- !ruby/object:Gem::Version
|
61
54
|
version: '0'
|
62
55
|
- !ruby/object:Gem::Dependency
|
63
56
|
name: test-unit
|
64
57
|
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
58
|
requirements:
|
67
|
-
- -
|
59
|
+
- - '>='
|
68
60
|
- !ruby/object:Gem::Version
|
69
61
|
version: '0'
|
70
62
|
type: :development
|
71
63
|
prerelease: false
|
72
64
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
65
|
requirements:
|
75
|
-
- -
|
66
|
+
- - '>='
|
76
67
|
- !ruby/object:Gem::Version
|
77
68
|
version: '0'
|
78
69
|
- !ruby/object:Gem::Dependency
|
79
70
|
name: rake
|
80
71
|
requirement: !ruby/object:Gem::Requirement
|
81
|
-
none: false
|
82
72
|
requirements:
|
83
|
-
- -
|
73
|
+
- - '>='
|
84
74
|
- !ruby/object:Gem::Version
|
85
75
|
version: '0'
|
86
76
|
type: :development
|
87
77
|
prerelease: false
|
88
78
|
version_requirements: !ruby/object:Gem::Requirement
|
89
|
-
none: false
|
90
79
|
requirements:
|
91
|
-
- -
|
80
|
+
- - '>='
|
92
81
|
- !ruby/object:Gem::Version
|
93
82
|
version: '0'
|
94
83
|
- !ruby/object:Gem::Dependency
|
95
84
|
name: shoulda-context
|
96
85
|
requirement: !ruby/object:Gem::Requirement
|
97
|
-
none: false
|
98
86
|
requirements:
|
99
|
-
- -
|
87
|
+
- - '>='
|
100
88
|
- !ruby/object:Gem::Version
|
101
89
|
version: '0'
|
102
90
|
type: :development
|
103
91
|
prerelease: false
|
104
92
|
version_requirements: !ruby/object:Gem::Requirement
|
105
|
-
none: false
|
106
93
|
requirements:
|
107
|
-
- -
|
94
|
+
- - '>='
|
108
95
|
- !ruby/object:Gem::Version
|
109
96
|
version: '0'
|
110
97
|
- !ruby/object:Gem::Dependency
|
111
98
|
name: mocha
|
112
99
|
requirement: !ruby/object:Gem::Requirement
|
113
|
-
none: false
|
114
100
|
requirements:
|
115
|
-
- -
|
101
|
+
- - '>='
|
116
102
|
- !ruby/object:Gem::Version
|
117
103
|
version: 0.13.3
|
118
104
|
type: :development
|
119
105
|
prerelease: false
|
120
106
|
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
none: false
|
122
107
|
requirements:
|
123
|
-
- -
|
108
|
+
- - '>='
|
124
109
|
- !ruby/object:Gem::Version
|
125
110
|
version: 0.13.3
|
126
111
|
description: Distributed storage
|
@@ -155,12 +140,14 @@ files:
|
|
155
140
|
- lib/fc/item_storage.rb
|
156
141
|
- lib/fc/policy.rb
|
157
142
|
- lib/fc/storage.rb
|
143
|
+
- lib/fc/var.rb
|
158
144
|
- lib/fc/version.rb
|
159
145
|
- lib/filecluster.rb
|
160
146
|
- lib/manage.rb
|
161
147
|
- lib/manage/policies.rb
|
162
148
|
- lib/manage/show.rb
|
163
149
|
- lib/manage/storages.rb
|
150
|
+
- lib/manage/var.rb
|
164
151
|
- lib/utils.rb
|
165
152
|
- test/base_test.rb
|
166
153
|
- test/daemon_test.rb
|
@@ -170,37 +157,32 @@ files:
|
|
170
157
|
- test/helper.rb
|
171
158
|
- test/item_test.rb
|
172
159
|
- test/policy_test.rb
|
160
|
+
- test/storage_sync.rb
|
173
161
|
- test/storage_test.rb
|
162
|
+
- test/var_test.rb
|
174
163
|
- test/version_test.rb
|
175
164
|
homepage: ''
|
176
165
|
licenses: []
|
166
|
+
metadata: {}
|
177
167
|
post_install_message:
|
178
168
|
rdoc_options: []
|
179
169
|
require_paths:
|
180
170
|
- lib
|
181
171
|
required_ruby_version: !ruby/object:Gem::Requirement
|
182
|
-
none: false
|
183
172
|
requirements:
|
184
|
-
- -
|
173
|
+
- - '>='
|
185
174
|
- !ruby/object:Gem::Version
|
186
175
|
version: '0'
|
187
|
-
segments:
|
188
|
-
- 0
|
189
|
-
hash: -778580397
|
190
176
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
191
|
-
none: false
|
192
177
|
requirements:
|
193
|
-
- -
|
178
|
+
- - '>='
|
194
179
|
- !ruby/object:Gem::Version
|
195
180
|
version: '0'
|
196
|
-
segments:
|
197
|
-
- 0
|
198
|
-
hash: -778580397
|
199
181
|
requirements: []
|
200
182
|
rubyforge_project:
|
201
|
-
rubygems_version:
|
183
|
+
rubygems_version: 2.0.3
|
202
184
|
signing_key:
|
203
|
-
specification_version:
|
185
|
+
specification_version: 4
|
204
186
|
summary: Distributed storage
|
205
187
|
test_files:
|
206
188
|
- test/base_test.rb
|
@@ -211,5 +193,7 @@ test_files:
|
|
211
193
|
- test/helper.rb
|
212
194
|
- test/item_test.rb
|
213
195
|
- test/policy_test.rb
|
196
|
+
- test/storage_sync.rb
|
214
197
|
- test/storage_test.rb
|
198
|
+
- test/var_test.rb
|
215
199
|
- test/version_test.rb
|