filecluster 0.0.3
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.
- 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
|