filecluster 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/Rakefile +11 -0
- data/TODO +9 -0
- data/bin/fc-daemon +70 -0
- data/bin/fc-manage +102 -0
- data/bin/fc-setup-db +47 -0
- data/filecluster.gemspec +24 -0
- data/lib/daemon.rb +91 -0
- data/lib/daemon/base_thread.rb +13 -0
- data/lib/daemon/check_thread.rb +12 -0
- data/lib/daemon/global_daemon_thread.rb +60 -0
- data/lib/daemon/task_thread.rb +37 -0
- data/lib/fc/base.rb +82 -0
- data/lib/fc/db.rb +168 -0
- data/lib/fc/error.rb +12 -0
- data/lib/fc/item.rb +102 -0
- data/lib/fc/item_storage.rb +8 -0
- data/lib/fc/policy.rb +18 -0
- data/lib/fc/storage.rb +80 -0
- data/lib/fc/version.rb +3 -0
- data/lib/filecluster.rb +13 -0
- data/lib/manage.rb +4 -0
- data/lib/manage/policies.rb +70 -0
- data/lib/manage/show.rb +57 -0
- data/lib/manage/storages.rb +87 -0
- data/lib/utils.rb +76 -0
- data/test/base_test.rb +74 -0
- data/test/daemon_test.rb +98 -0
- data/test/db_test.rb +182 -0
- data/test/error_test.rb +15 -0
- data/test/functional_test.rb +97 -0
- data/test/helper.rb +17 -0
- data/test/item_test.rb +49 -0
- data/test/policy_test.rb +34 -0
- data/test/storage_test.rb +29 -0
- data/test/version_test.rb +7 -0
- metadata +199 -0
@@ -0,0 +1,12 @@
|
|
1
|
+
class CheckThread < BaseThread
|
2
|
+
def go(storage_name)
|
3
|
+
$log.debug("CheckThread: Run stotage check for #{storage_name}")
|
4
|
+
storage = $storages.detect{|s| s.name == storage_name}
|
5
|
+
if File.writable?(storage.path)
|
6
|
+
storage.update_check_time
|
7
|
+
else
|
8
|
+
error "Storage #{storage.name} with path #{storage.path} not writable"
|
9
|
+
end
|
10
|
+
$log.debug("CheckThread: Finish stotage check for #{storage_name}")
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
class GlobalDaemonThread < BaseThread
|
2
|
+
def go(timeout)
|
3
|
+
$log.info("Start global daemon thread with timeout=#{timeout}")
|
4
|
+
while true do
|
5
|
+
exit if $exit_signal
|
6
|
+
sleep timeout.to_f/2
|
7
|
+
exit if $exit_signal
|
8
|
+
|
9
|
+
r = FC::DB.connect.query("SELECT #{FC::DB.prefix}vars.*, UNIX_TIMESTAMP() as curr_time FROM #{FC::DB.prefix}vars WHERE name='global_daemon_host'").first
|
10
|
+
if r['val'] == FC::Storage.curr_host
|
11
|
+
FC::DB.connect.query("UPDATE #{FC::DB.prefix}vars SET val='#{FC::Storage.curr_host}' WHERE name='global_daemon_host'")
|
12
|
+
else
|
13
|
+
$log.info("Exit from GlobalDaemonThread: global daemon already running on #{r['val']}")
|
14
|
+
FC::DB.close
|
15
|
+
exit
|
16
|
+
end
|
17
|
+
|
18
|
+
make_item_copies
|
19
|
+
|
20
|
+
#периодическая проверка на item со статусом delete, последним обновлением дольше суток (NOW - time > 86400) и без связанных is - удаление таких из базы
|
21
|
+
#периодически удалять (проставлять статус delete) для лиших is (число копий больше необходимого)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# make item copies by policy
|
26
|
+
def make_item_copies
|
27
|
+
$log.debug("GlobalDaemonThread: make_item_copies")
|
28
|
+
|
29
|
+
all_storages = FC::Storage.where
|
30
|
+
all_policies = FC::Policy.where
|
31
|
+
|
32
|
+
# policies.get_storages => all_policies.select
|
33
|
+
all_policies.each do |policy|
|
34
|
+
metaclass = class << policy; self; end
|
35
|
+
metaclass.send(:define_method, :get_storages) do
|
36
|
+
policy_storages = self.storages.split(',')
|
37
|
+
all_storages.select{|storage| policy_storages.member?(storage.name)}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
sql = "SELECT i.id as item_id, i.size, i.copies as item_copies, GROUP_CONCAT(ist.storage_name) as storages, p.id as policy_id, p.copies as policy_copies "+
|
42
|
+
"FROM #{FC::Item.table_name} as i, #{FC::Policy.table_name} as p, #{FC::ItemStorage.table_name} as ist WHERE "+
|
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 1000"
|
44
|
+
r = FC::DB.connect.query(sql)
|
45
|
+
r.each do |row|
|
46
|
+
$log.info("GlobalDaemonThread: new item_storage for item #{row['item_id']}")
|
47
|
+
item_storages = row['storages'].split(',')
|
48
|
+
if row['item_copies'] != item_storages.size
|
49
|
+
$log.warn("GlobalDaemonThread: ItemStorage count <> item.copies for item #{row['item_id']}")
|
50
|
+
elsif item_storages.size >= row['policy_copies']
|
51
|
+
$log.warn("GlobalDaemonThread: ItemStorage count >= policy.copies for item #{row['item_id']}")
|
52
|
+
else
|
53
|
+
policy = all_policies.detect{|p| row['policy_id'] == p.id}
|
54
|
+
storage = policy.get_proper_storage(row['size'], item_storages) if policy
|
55
|
+
error 'No available storage', :item_id => row['item_id'] unless storage
|
56
|
+
FC::Item.new(:id => row['item_id']).make_item_storage(storage, 'copy')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class TaskThread < BaseThread
|
2
|
+
def go(storage_name)
|
3
|
+
while task = $tasks[storage_name].shift do
|
4
|
+
$curr_task[storage_name] = task
|
5
|
+
$log.debug("TaskThread(#{storage_name}): run task type=#{task[:action]}, item_storage=#{task[:item_storage].id}")
|
6
|
+
if task[:action] == :delete
|
7
|
+
make_delete(task[:item_storage])
|
8
|
+
elsif task[:action] == :copy
|
9
|
+
make_copy(task[:item_storage])
|
10
|
+
else
|
11
|
+
error "Unknown task action: #{task[:action]}"
|
12
|
+
end
|
13
|
+
$curr_task[storage_name] = nil
|
14
|
+
$log.debug("TaskThread(#{storage_name}): Finish task type=#{task[:action]}, item_storage=#{task[:item_storage].id}")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def make_delete(item_storage)
|
19
|
+
storage = $storages.detect{|s| s.name == item_storage.storage_name}
|
20
|
+
item = FC::Item.find(item_storage.item_id)
|
21
|
+
storage.delete_file(item.name)
|
22
|
+
item_storage.delete
|
23
|
+
rescue Exception => e
|
24
|
+
error "Delete item_storage error: #{e.message}; #{e.backtrace.join(', ')}", :item_id => item_storage.item_id, :item_storage_id => item_storage.id
|
25
|
+
end
|
26
|
+
|
27
|
+
def make_copy(item_storage)
|
28
|
+
# TODO: не лазить в базу за item, item_storages - перенести на стадию подготовки task-а
|
29
|
+
storage = $storages.detect{|s| s.name == item_storage.storage_name}
|
30
|
+
item = FC::Item.find(item_storage.item_id)
|
31
|
+
src_item_storage = FC::ItemStorage.where("item_id = ? AND status = 'ready'", item.id).sample
|
32
|
+
src_storage = $storages.detect{|s| s.name == src_item_storage.storage_name}
|
33
|
+
item.copy_item_storage(src_storage, storage, item_storage)
|
34
|
+
rescue Exception => e
|
35
|
+
error "Copy item_storage error: #{e.message}; #{e.backtrace.join(', ')}", :item_id => item_storage.item_id, :item_storage_id => item_storage.id
|
36
|
+
end
|
37
|
+
end
|
data/lib/fc/base.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module FC
|
4
|
+
class DbBase
|
5
|
+
attr_accessor :id, :database_fields
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_accessor :table_name, :table_fields
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(params = {})
|
12
|
+
self.class.table_fields.each {|key| self.send("#{key}=", params[key] || params[key.to_sym]) }
|
13
|
+
@id = (params["id"] || params[:id]).to_i if params["id"] || params[:id]
|
14
|
+
@database_fields = params[:database_fields] || {}
|
15
|
+
end
|
16
|
+
|
17
|
+
# define table name and fields
|
18
|
+
def self.set_table(name, fields)
|
19
|
+
self.table_name = "#{FC::DB.prefix}#{name}"
|
20
|
+
self.table_fields = fields.split(',').map{|e| e.gsub(' ','')}
|
21
|
+
self.table_fields.each{|e| attr_accessor e.to_sym}
|
22
|
+
end
|
23
|
+
|
24
|
+
# make instance on fields hash
|
25
|
+
def self.create_from_fiels(data)
|
26
|
+
# use only defined in set_table fields
|
27
|
+
database_fields = data.select{|key, val| self.table_fields.include?(key.to_s)}
|
28
|
+
self.new(database_fields.merge({:id => data["id"].to_s, :database_fields => database_fields}))
|
29
|
+
end
|
30
|
+
|
31
|
+
# get element by id
|
32
|
+
def self.find(id)
|
33
|
+
r = FC::DB.connect.query("SELECT * FROM #{self.table_name} WHERE id=#{id.to_i}")
|
34
|
+
raise "Record not found (#{self.table_name}.id=#{id})" if r.count == 0
|
35
|
+
self.create_from_fiels(r.first)
|
36
|
+
end
|
37
|
+
|
38
|
+
# get elements array by sql condition (possibles '?' placeholders)
|
39
|
+
def self.where(cond = "1", *params)
|
40
|
+
i = -1
|
41
|
+
sql = "SELECT * FROM #{self.table_name} WHERE #{cond.gsub('?'){i+=1; "'#{Mysql2::Client.escape(params[i].to_s)}'"}}"
|
42
|
+
r = FC::DB.connect.query(sql)
|
43
|
+
r.map{|data| self.create_from_fiels(data)}
|
44
|
+
end
|
45
|
+
|
46
|
+
# save changed fields
|
47
|
+
def save
|
48
|
+
sql = @id.to_i != 0 ? "UPDATE #{self.class.table_name} SET " : "INSERT IGNORE INTO #{self.class.table_name} SET "
|
49
|
+
fields = []
|
50
|
+
self.class.table_fields.each do |key|
|
51
|
+
db_val = @database_fields[key]
|
52
|
+
val = self.send(key)
|
53
|
+
if val.to_s != db_val.to_s || val.nil? && !db_val.nil? || !val.nil? && db_val.nil?
|
54
|
+
fields << "#{key}=#{val ? (val.class == String ? "'#{FC::DB.connect.escape(val)}'" : val.to_i) : 'NULL'}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
if fields.length > 0
|
58
|
+
sql << fields.join(',')
|
59
|
+
sql << " WHERE id=#{@id.to_i}" if @id
|
60
|
+
FC::DB.connect.query(sql)
|
61
|
+
@id = FC::DB.connect.last_id unless @id
|
62
|
+
self.class.table_fields.each do |key|
|
63
|
+
@database_fields[key] = self.send(key)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# reload object from DB
|
69
|
+
def reload
|
70
|
+
raise "Can't reload object without id" if !@id || @id.to_i == 0
|
71
|
+
new_obj = self.class.find(@id)
|
72
|
+
self.database_fields = new_obj.database_fields
|
73
|
+
self.class.table_fields.each {|key| self.send("#{key}=", new_obj.send(key)) }
|
74
|
+
end
|
75
|
+
|
76
|
+
# delete object from DB
|
77
|
+
def delete
|
78
|
+
FC::DB.connect.query("DELETE FROM #{self.class.table_name} WHERE id=#{@id.to_i}") if @id
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
data/lib/fc/db.rb
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'mysql2'
|
2
|
+
|
3
|
+
module FC
|
4
|
+
module DB
|
5
|
+
class << self; attr_accessor :options, :prefix end
|
6
|
+
|
7
|
+
def DB.connect_by_config(options)
|
8
|
+
@options = options.clone
|
9
|
+
@options[:port] = options[:port].to_i if options[:port]
|
10
|
+
@connects = {}
|
11
|
+
@connects[Thread.current.object_id] = Mysql2::Client.new(@options)
|
12
|
+
@prefix = options[:prefix].to_s
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.connect
|
16
|
+
if @options[:multi_threads]
|
17
|
+
@connects[Thread.current.object_id] ||= Mysql2::Client.new(@options)
|
18
|
+
else
|
19
|
+
@connects.first[1]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.close
|
24
|
+
if @options[:multi_threads]
|
25
|
+
if @connects[Thread.current.object_id]
|
26
|
+
@connects[Thread.current.object_id].close
|
27
|
+
@connects.delete(Thread.current.object_id)
|
28
|
+
end
|
29
|
+
else
|
30
|
+
@connects.first[1].close
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def DB.init_db
|
35
|
+
FC::DB.connect.query(%{
|
36
|
+
CREATE TABLE #{@prefix}items (
|
37
|
+
id int NOT NULL AUTO_INCREMENT,
|
38
|
+
name varchar(1024) NOT NULL DEFAULT '',
|
39
|
+
tag varchar(255) DEFAULT NULL,
|
40
|
+
outer_id int DEFAULT NULL,
|
41
|
+
policy_id int NOT NULL,
|
42
|
+
dir tinyint(1) NOT NULL DEFAULT 0,
|
43
|
+
size bigint NOT NULL DEFAULT 0,
|
44
|
+
status ENUM('new', 'ready', 'error', 'delete') NOT NULL DEFAULT 'new',
|
45
|
+
time int DEFAULT NULL,
|
46
|
+
copies int NOT NULL DEFAULT 0,
|
47
|
+
PRIMARY KEY (id), UNIQUE KEY (name(255), policy_id),
|
48
|
+
KEY (outer_id), KEY (time, status), KEY (status), KEY (copies, status, policy_id)
|
49
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
|
50
|
+
})
|
51
|
+
proc_time = %{
|
52
|
+
SET NEW.time = UNIX_TIMESTAMP();
|
53
|
+
}
|
54
|
+
FC::DB.connect.query("CREATE TRIGGER fc_items_before_insert BEFORE INSERT on #{@prefix}items FOR EACH ROW BEGIN #{proc_time} END")
|
55
|
+
FC::DB.connect.query("CREATE TRIGGER fc_items_before_update BEFORE UPDATE on #{@prefix}items FOR EACH ROW BEGIN #{proc_time} END")
|
56
|
+
|
57
|
+
FC::DB.connect.query(%{
|
58
|
+
CREATE TABLE #{@prefix}storages (
|
59
|
+
id int NOT NULL AUTO_INCREMENT,
|
60
|
+
name varchar(255) NOT NULL DEFAULT '',
|
61
|
+
host varchar(255) NOT NULL DEFAULT '',
|
62
|
+
path text NOT NULL DEFAULT '',
|
63
|
+
url text NOT NULL DEFAULT '',
|
64
|
+
size bigint NOT NULL DEFAULT 0,
|
65
|
+
size_limit bigint NOT NULL DEFAULT 0,
|
66
|
+
check_time int DEFAULT NULL,
|
67
|
+
PRIMARY KEY (id), UNIQUE KEY (name), KEY (host)
|
68
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
|
69
|
+
})
|
70
|
+
proc = %{
|
71
|
+
# update policy.storages on storage delete and update
|
72
|
+
UPDATE #{@prefix}policies,
|
73
|
+
(SELECT #{@prefix}policies.id, GROUP_CONCAT(name) as storages FROM #{@prefix}policies LEFT JOIN #{@prefix}storages ON FIND_IN_SET(name, storages) GROUP BY #{@prefix}policies.id) as new_policy
|
74
|
+
SET #{@prefix}policies.storages = new_policy.storages WHERE #{@prefix}policies.id = new_policy.id;
|
75
|
+
}
|
76
|
+
proc_update = %{
|
77
|
+
IF OLD.name <> NEW.name THEN
|
78
|
+
#{proc}
|
79
|
+
END IF;
|
80
|
+
}
|
81
|
+
FC::DB.connect.query("CREATE TRIGGER fc_storages_after_delete AFTER DELETE on #{@prefix}storages FOR EACH ROW BEGIN #{proc} END")
|
82
|
+
FC::DB.connect.query("CREATE TRIGGER fc_storages_after_update AFTER UPDATE on #{@prefix}storages FOR EACH ROW BEGIN #{proc_update} END")
|
83
|
+
|
84
|
+
FC::DB.connect.query(%{
|
85
|
+
CREATE TABLE #{@prefix}policies (
|
86
|
+
id int NOT NULL AUTO_INCREMENT,
|
87
|
+
storages text NOT NULL DEFAULT '',
|
88
|
+
copies int NOT NULL DEFAULT 0,
|
89
|
+
PRIMARY KEY (id)
|
90
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
|
91
|
+
})
|
92
|
+
proc = %{
|
93
|
+
# update policy.storages on policy change - guarantee valid policy.storages
|
94
|
+
SELECT GROUP_CONCAT(name) INTO @storages_list FROM #{@prefix}storages WHERE FIND_IN_SET(name, NEW.storages);
|
95
|
+
SET NEW.storages = @storages_list;
|
96
|
+
}
|
97
|
+
FC::DB.connect.query("CREATE TRIGGER fc_policies_before_insert BEFORE INSERT on #{@prefix}policies FOR EACH ROW BEGIN #{proc} END")
|
98
|
+
FC::DB.connect.query("CREATE TRIGGER fc_policies_before_update BEFORE UPDATE on #{@prefix}policies FOR EACH ROW BEGIN #{proc} END")
|
99
|
+
|
100
|
+
FC::DB.connect.query(%{
|
101
|
+
CREATE TABLE #{@prefix}items_storages (
|
102
|
+
id int NOT NULL AUTO_INCREMENT,
|
103
|
+
item_id int DEFAULT NULL,
|
104
|
+
storage_name varchar(255) DEFAULT NULL,
|
105
|
+
status ENUM('new', 'copy', 'error', 'ready', 'delete') NOT NULL DEFAULT 'new',
|
106
|
+
time int DEFAULT NULL,
|
107
|
+
PRIMARY KEY (id), UNIQUE KEY (item_id, storage_name), KEY (storage_name), KEY (time, status), KEY (status, storage_name),
|
108
|
+
FOREIGN KEY (item_id) REFERENCES #{@prefix}items(id) ON UPDATE RESTRICT ON DELETE RESTRICT,
|
109
|
+
FOREIGN KEY (storage_name) REFERENCES #{@prefix}storages(name) ON UPDATE RESTRICT ON DELETE RESTRICT
|
110
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
|
111
|
+
})
|
112
|
+
proc = %{
|
113
|
+
SELECT status, copies, size INTO @item_status, @item_copies, @item_size FROM #{@prefix}items WHERE id = NEW.item_id;
|
114
|
+
SET @curr_copies = (SELECT count(*) FROM #{@prefix}items_storages WHERE item_id = NEW.item_id AND status <> 'delete');
|
115
|
+
SET @curr_copies_ready = (SELECT count(*) FROM #{@prefix}items_storages WHERE item_id = NEW.item_id AND status = 'ready');
|
116
|
+
# calc item.copies
|
117
|
+
IF @curr_copies <> @item_copies THEN
|
118
|
+
UPDATE #{@prefix}items SET copies=@curr_copies WHERE id = NEW.item_id;
|
119
|
+
END IF;
|
120
|
+
# check error status
|
121
|
+
IF @item_status <> 'new' AND @item_status <> 'delete' AND @curr_copies_ready = 0 THEN
|
122
|
+
UPDATE #{@prefix}items SET status='error' WHERE id = NEW.item_id;
|
123
|
+
END IF;
|
124
|
+
# check ready status
|
125
|
+
IF @curr_copies_ready > 0 THEN
|
126
|
+
UPDATE #{@prefix}items SET status='ready' WHERE id = NEW.item_id;
|
127
|
+
END IF;
|
128
|
+
}
|
129
|
+
proc_add = %{
|
130
|
+
#{proc}
|
131
|
+
UPDATE #{@prefix}storages SET size=size+@item_size WHERE name = NEW.storage_name;
|
132
|
+
}
|
133
|
+
proc_del = %{
|
134
|
+
#{proc.gsub('NEW', 'OLD')}
|
135
|
+
UPDATE #{@prefix}storages SET size=size-@item_size WHERE name = OLD.storage_name;
|
136
|
+
}
|
137
|
+
FC::DB.connect.query("CREATE TRIGGER fc_items_storages_before_insert BEFORE INSERT on #{@prefix}items_storages FOR EACH ROW BEGIN #{proc_time} END")
|
138
|
+
FC::DB.connect.query("CREATE TRIGGER fc_items_storages_before_update BEFORE UPDATE on #{@prefix}items_storages FOR EACH ROW BEGIN #{proc_time} END")
|
139
|
+
FC::DB.connect.query("CREATE TRIGGER fc_items_storages_after_update AFTER UPDATE on #{@prefix}items_storages FOR EACH ROW BEGIN #{proc} END")
|
140
|
+
FC::DB.connect.query("CREATE TRIGGER fc_items_storages_after_insert AFTER INSERT on #{@prefix}items_storages FOR EACH ROW BEGIN #{proc_add} END")
|
141
|
+
FC::DB.connect.query("CREATE TRIGGER fc_items_storages_after_delete AFTER DELETE on #{@prefix}items_storages FOR EACH ROW BEGIN #{proc_del} END")
|
142
|
+
|
143
|
+
FC::DB.connect.query(%{
|
144
|
+
CREATE TABLE #{@prefix}errors (
|
145
|
+
id int NOT NULL AUTO_INCREMENT,
|
146
|
+
item_id int DEFAULT NULL,
|
147
|
+
item_storage_id int DEFAULT NULL,
|
148
|
+
host varchar(255) DEFAULT NULL,
|
149
|
+
message varchar(255) DEFAULT NULL,
|
150
|
+
time int DEFAULT NULL,
|
151
|
+
PRIMARY KEY (id), KEY (item_id), KEY (item_storage_id), KEY (host), KEY (time)
|
152
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
|
153
|
+
})
|
154
|
+
FC::DB.connect.query("CREATE TRIGGER fc_errors_before_insert BEFORE INSERT on #{@prefix}errors FOR EACH ROW BEGIN #{proc_time} END")
|
155
|
+
|
156
|
+
FC::DB.connect.query(%{
|
157
|
+
CREATE TABLE #{@prefix}vars (
|
158
|
+
name varchar(255) DEFAULT NULL,
|
159
|
+
val varchar(255) DEFAULT NULL,
|
160
|
+
time int DEFAULT NULL,
|
161
|
+
PRIMARY KEY (name)
|
162
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
|
163
|
+
})
|
164
|
+
FC::DB.connect.query("CREATE TRIGGER fc_vars_before_insert BEFORE INSERT on #{@prefix}vars FOR EACH ROW BEGIN #{proc_time} END")
|
165
|
+
FC::DB.connect.query("CREATE TRIGGER fc_vars_before_update BEFORE UPDATE on #{@prefix}vars FOR EACH ROW BEGIN #{proc_time} END")
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
data/lib/fc/error.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module FC
|
4
|
+
class Error < DbBase
|
5
|
+
set_table :errors, 'item_id, item_storage_id, host, message, time'
|
6
|
+
|
7
|
+
def self.raise(error, options = {})
|
8
|
+
self.new(options.merge(:host => FC::Storage.curr_host, :message => error)).save
|
9
|
+
Kernel.raise error
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/fc/item.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module FC
|
4
|
+
class Item < DbBase
|
5
|
+
set_table :items, 'name, tag, outer_id, policy_id, dir, size, status, time, copies'
|
6
|
+
|
7
|
+
# create item by local path
|
8
|
+
# TODO проверка curr_host и local_path одному из доступных стораджей -> создание без копирования (для кусочков)
|
9
|
+
def self.create_from_local(local_path, item_name, policy, options={})
|
10
|
+
raise 'Path not exists' unless File.exists?(local_path)
|
11
|
+
raise 'Policy is not FC::Policy' unless policy.instance_of?(FC::Policy)
|
12
|
+
item_params = options.merge({
|
13
|
+
:name => item_name.to_s.gsub('//', '/').sub(/\/$/, '').sub(/^\//, '').strip,
|
14
|
+
:policy_id => policy.id,
|
15
|
+
:dir => File.directory?(local_path),
|
16
|
+
:size => `du -sb #{local_path}`.to_i
|
17
|
+
})
|
18
|
+
item_params.delete(:replace)
|
19
|
+
raise 'Name is empty' if item_params[:name].empty?
|
20
|
+
raise 'Zero size path' if item_params[:size] == 0
|
21
|
+
|
22
|
+
# new item?
|
23
|
+
item = FC::Item.where('name=? AND policy_id=?', item_params[:name], policy.id).first
|
24
|
+
if item
|
25
|
+
if options[:replace]
|
26
|
+
# mark delete item_storages on replace
|
27
|
+
FC::DB.connect.query("UPDATE #{FC::ItemStorage.table_name} SET status='delete' WHERE item_id = #{item.id}")
|
28
|
+
# replace all fields
|
29
|
+
item_params.each{|key, val| item.send("#{key}=", val)}
|
30
|
+
else
|
31
|
+
FC::Error.raise 'Item already exists', :item_id => item.id
|
32
|
+
end
|
33
|
+
else
|
34
|
+
item = FC::Item.new(item_params)
|
35
|
+
end
|
36
|
+
item.save
|
37
|
+
|
38
|
+
storage = policy.get_proper_storage(item.size)
|
39
|
+
FC::Error.raise 'No available storage', :item_id => item.id unless storage
|
40
|
+
|
41
|
+
item_storage = item.make_item_storage(storage)
|
42
|
+
item.copy_item_storage(local_path, storage, item_storage)
|
43
|
+
return item
|
44
|
+
end
|
45
|
+
|
46
|
+
def make_item_storage(storage, status = 'new')
|
47
|
+
# new storage_item?
|
48
|
+
item_storage = FC::ItemStorage.where('item_id=? AND storage_name=?', id, storage.name).first
|
49
|
+
item_storage.delete if item_storage
|
50
|
+
|
51
|
+
item_storage = FC::ItemStorage.new({:item_id => id, :storage_name => storage.name, :status => status})
|
52
|
+
item_storage.save
|
53
|
+
item_storage
|
54
|
+
end
|
55
|
+
|
56
|
+
def copy_item_storage(src, storage, item_storage)
|
57
|
+
begin
|
58
|
+
if src.instance_of?(FC::Storage)
|
59
|
+
src.copy_to_local(name, "#{storage.path}#{name}")
|
60
|
+
else
|
61
|
+
storage.copy_path(src, name)
|
62
|
+
end
|
63
|
+
size_on_storage = storage.file_size(name)
|
64
|
+
rescue Exception => e
|
65
|
+
item_storage.status = 'error'
|
66
|
+
item_storage.save
|
67
|
+
FC::Error.raise "Copy error: #{e.message}", :item_id => id, :item_storage_id => item_storage.id
|
68
|
+
else
|
69
|
+
begin
|
70
|
+
item_storage.reload
|
71
|
+
rescue Exception => e
|
72
|
+
FC::Error.raise "After copy error: #{e.message}", :item_id => id, :item_storage_id => item_storage.id
|
73
|
+
else
|
74
|
+
if size_on_storage != size
|
75
|
+
item_storage.status = 'error'
|
76
|
+
item_storage.save
|
77
|
+
FC::Error.raise "Check size after copy error", :item_id => id, :item_storage_id => item_storage.id
|
78
|
+
else
|
79
|
+
item_storage.status = 'ready'
|
80
|
+
item_storage.save
|
81
|
+
reload
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# mark items_storages for delete
|
88
|
+
def mark_deleted
|
89
|
+
FC::DB.connect.query("UPDATE #{FC::ItemStorage.table_name} SET status='delete' WHERE item_id = #{id}")
|
90
|
+
self.status = 'delete'
|
91
|
+
save
|
92
|
+
end
|
93
|
+
|
94
|
+
def dir?
|
95
|
+
dir.to_i == 1
|
96
|
+
end
|
97
|
+
|
98
|
+
def get_item_storages
|
99
|
+
FC::ItemStorage.where("item_id = #{id}")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|