filecluster 0.5.14 → 0.5.15
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.ssh/.keep +0 -0
- data/Dockerfile +40 -0
- data/Gemfile +1 -0
- data/README_RU.md +32 -0
- data/bin/fc-daemon +4 -1
- data/bin/fc-manage +8 -0
- data/bin/fc-setup-db +2 -2
- data/docker-compose-mysql56.yaml +35 -0
- data/docker-compose.yaml +35 -0
- data/docker/Dockerfile.sshd +17 -0
- data/docker/development.env +7 -0
- data/entrypoint.sh +19 -0
- data/filecluster.gemspec +2 -0
- data/lib/autosync.rb +149 -0
- data/lib/daemon.rb +17 -1
- data/lib/daemon/autosync_thread.rb +16 -0
- data/lib/daemon/global_daemon_thread.rb +1 -1
- data/lib/daemon/update_tasks_thread.rb +1 -1
- data/lib/fc/db.rb +5 -1
- data/lib/fc/storage.rb +3 -3
- data/lib/fc/var.rb +20 -1
- data/lib/fc/version.rb +1 -1
- data/lib/iostat.rb +48 -0
- data/lib/manage.rb +3 -1
- data/lib/manage/autosync.rb +53 -0
- data/lib/manage/copy_speed.rb +1 -1
- data/lib/manage/storages.rb +57 -1
- data/test/autosync_test.rb +186 -0
- data/test/daemon_test.rb +44 -31
- data/test/db_test.rb +28 -21
- data/test/error_test.rb +3 -3
- data/test/functional_test.rb +21 -20
- data/test/helper.rb +8 -2
- data/test/storage_sync_test.rb +7 -7
- metadata +44 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6dc0301108025f48875d13f484a6dda7d1d9be97
|
4
|
+
data.tar.gz: 35987690b4cd3f40cf439f1f63e6c78bfa6b98fb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8953e38a31a07cddf4912fa30abc6f3897703ca09ff318986ba6158901c434dfca5331dd5ff2a47b6a0f94f5f46008d000e9be15ee0536000bf3bdbcac20f78b
|
7
|
+
data.tar.gz: bde827cae587aaeda3b7d8d1259dd1033c2d55549afb7e3faa78ab89d4ce500bb17136a4bf01570320b8b3f3796d8bd095d9b7575359a9154c3cc91f0e8c38c3
|
data/.gitignore
CHANGED
data/.ssh/.keep
ADDED
File without changes
|
data/Dockerfile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
FROM ubuntu:16.04
|
2
|
+
|
3
|
+
RUN apt-get update \
|
4
|
+
&& apt-get install -y wget \
|
5
|
+
build-essential \
|
6
|
+
zlib1g-dev \
|
7
|
+
openssl \
|
8
|
+
libssl-dev \
|
9
|
+
git \
|
10
|
+
libreadline-dev \
|
11
|
+
libmysqlclient-dev \
|
12
|
+
tzdata \
|
13
|
+
super \
|
14
|
+
&& rm -rf /var/lib/apt/lists/*
|
15
|
+
|
16
|
+
RUN wget http://ftp.ruby-lang.org/pub/ruby/2.3/ruby-2.3.3.tar.gz \
|
17
|
+
&& tar -xzvf ruby-2.3.3.tar.gz \
|
18
|
+
&& rm ruby-2.3.3.tar.gz \
|
19
|
+
&& cd ruby-2.3.3/ \
|
20
|
+
&& ./configure --with-openssl-dir=/usr/bin \
|
21
|
+
&& make \
|
22
|
+
&& make install \
|
23
|
+
&& cd .. \
|
24
|
+
&& rm -rf ruby-2.3.3 \
|
25
|
+
&& gem install bundler
|
26
|
+
|
27
|
+
RUN mkdir -p /app
|
28
|
+
WORKDIR /app
|
29
|
+
|
30
|
+
COPY ./ /app
|
31
|
+
|
32
|
+
RUN groupadd --gid 1000 filecluster \
|
33
|
+
&& useradd --uid 1000 --gid 1000 filecluster --shell /bin/bash
|
34
|
+
|
35
|
+
|
36
|
+
COPY ./entrypoint.sh /usr/local/bin/
|
37
|
+
RUN bundle install --jobs 20 --retry 6
|
38
|
+
|
39
|
+
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
40
|
+
CMD ["/app/bin/fc-daemon", "-l", "debug"]
|
data/Gemfile
CHANGED
data/README_RU.md
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
FileCluster
|
2
|
+
|
3
|
+
Набор скриптов для управления контентом по верх файловой системы
|
4
|
+
Уровень регистрации в базе - файл или папка
|
5
|
+
|
6
|
+
|
7
|
+
Принцип работы
|
8
|
+
|
9
|
+
Информация о файлах/папках записывается в базу данных (mysql)
|
10
|
+
Демон на основе записей в базе данных приводит соответствие файлов списку зарегистрированных файлов/папок
|
11
|
+
|
12
|
+
|
13
|
+
Компоненты
|
14
|
+
|
15
|
+
filecluster - deamon/manager (ruby)
|
16
|
+
mysql - мета информация
|
17
|
+
sshd + rsync - транспорт
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
Быстрый запуск тестов
|
22
|
+
|
23
|
+
первый запуск mysql -
|
24
|
+
|
25
|
+
docker-compose run --rm filecluster-db
|
26
|
+
|
27
|
+
Окончание инициализации можно будет увидеть по сообщению о готовности к работе mysql
|
28
|
+
CTRL+C
|
29
|
+
|
30
|
+
Запуск тестов
|
31
|
+
|
32
|
+
docker-compose run --rm filecluser1 rake
|
data/bin/fc-daemon
CHANGED
@@ -90,9 +90,12 @@ while true do
|
|
90
90
|
storages_check
|
91
91
|
update_tasks
|
92
92
|
run_tasks
|
93
|
+
autosync
|
93
94
|
end
|
94
95
|
$log.debug('sleep')
|
95
|
-
|
96
|
+
sleep_time = FC::Var.get('daemon_cycle_time', 30).to_f
|
97
|
+
sleep_time = 0.3 if sleep_time < 0.3
|
98
|
+
sleep sleep_time
|
96
99
|
alive_time = Time.new.to_i - start_time
|
97
100
|
if !$exit_signal && alive_time > FC::Var.get('daemon_restart_period', 86400).to_i
|
98
101
|
$log.info("Self restart, #{alive_time} seconds up")
|
data/bin/fc-manage
CHANGED
@@ -72,6 +72,14 @@ Command:
|
|
72
72
|
list show all limits
|
73
73
|
change <host> change current copy speed limit for host
|
74
74
|
add add copy speed limit for host
|
75
|
+
}],
|
76
|
+
'autosync' => [
|
77
|
+
'show and change autosync intervals for FC hosts',
|
78
|
+
%q{Usage: fc-manage autosync <command>
|
79
|
+
Command:
|
80
|
+
list show all intervals
|
81
|
+
change <host> change sync interval for host
|
82
|
+
add add non default sync interval for host
|
75
83
|
}],
|
76
84
|
'item' => [
|
77
85
|
'show and manage items',
|
data/bin/fc-setup-db
CHANGED
@@ -52,9 +52,9 @@ if s == "y" || s == "yes"
|
|
52
52
|
end
|
53
53
|
unless default_db_config
|
54
54
|
print "Save to config.. "
|
55
|
-
options.select
|
55
|
+
config = options.select { |key, _| descriptions[key][:save] }
|
56
56
|
File.open(File.expand_path(File.dirname(__FILE__))+'/db.yml', 'w') do |f|
|
57
|
-
f.write(
|
57
|
+
f.write(config.to_yaml)
|
58
58
|
end
|
59
59
|
puts "ok"
|
60
60
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
version: '3.5'
|
2
|
+
|
3
|
+
services:
|
4
|
+
filecluster-db:
|
5
|
+
image: mysql:5.6
|
6
|
+
volumes:
|
7
|
+
- filecluster_db56:/var/lib/mysql
|
8
|
+
env_file:
|
9
|
+
- ./docker/development.env
|
10
|
+
|
11
|
+
filecluster1:
|
12
|
+
build: ./
|
13
|
+
depends_on:
|
14
|
+
- filecluster-db
|
15
|
+
- filecluster1-ssh
|
16
|
+
env_file:
|
17
|
+
- ./docker/development.env
|
18
|
+
|
19
|
+
volumes:
|
20
|
+
- filetest_1:/tmp/
|
21
|
+
- ./:/app/
|
22
|
+
- ./.ssh:/home/filecluster/.ssh
|
23
|
+
|
24
|
+
filecluster1-ssh:
|
25
|
+
image: asigatchov/ubuntu16-sshd
|
26
|
+
volumes:
|
27
|
+
- filetest_1:/tmp/
|
28
|
+
- ./.ssh:/home/filecluster/.ssh
|
29
|
+
|
30
|
+
|
31
|
+
volumes:
|
32
|
+
filecluster_db56:
|
33
|
+
driver: local
|
34
|
+
filetest_1:
|
35
|
+
driver: local
|
data/docker-compose.yaml
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
version: '3.5'
|
2
|
+
|
3
|
+
services:
|
4
|
+
filecluster-db:
|
5
|
+
image: mysql:5.7.21
|
6
|
+
volumes:
|
7
|
+
- filecluster_db:/var/lib/mysql
|
8
|
+
env_file:
|
9
|
+
- ./docker/development.env
|
10
|
+
|
11
|
+
filecluster1:
|
12
|
+
build: ./
|
13
|
+
depends_on:
|
14
|
+
- filecluster-db
|
15
|
+
- filecluster1-ssh
|
16
|
+
volumes:
|
17
|
+
- filetest_1:/tmp/
|
18
|
+
- ./:/app/
|
19
|
+
- ./.ssh:/home/filecluster/.ssh
|
20
|
+
hostname: filecluster1
|
21
|
+
env_file:
|
22
|
+
- ./docker/development.env
|
23
|
+
|
24
|
+
|
25
|
+
filecluster1-ssh:
|
26
|
+
image: asigatchov/ubuntu16-sshd
|
27
|
+
volumes:
|
28
|
+
- filetest_1:/tmp/
|
29
|
+
- ./.ssh:/home/filecluster/.ssh
|
30
|
+
|
31
|
+
volumes:
|
32
|
+
filecluster_db:
|
33
|
+
driver: local
|
34
|
+
filetest_1:
|
35
|
+
driver: local
|
@@ -0,0 +1,17 @@
|
|
1
|
+
FROM ubuntu:16.04
|
2
|
+
|
3
|
+
RUN apt-get update && apt-get install -y openssh-server rsync
|
4
|
+
RUN mkdir /var/run/sshd
|
5
|
+
|
6
|
+
#RUN sed -i 's/PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
|
7
|
+
|
8
|
+
# SSH login fix. Otherwise user is kicked off after login
|
9
|
+
RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd
|
10
|
+
|
11
|
+
ENV NOTVISIBLE "in users profile"
|
12
|
+
RUN echo "export VISIBLE=now" >> /etc/profile
|
13
|
+
RUN groupadd --gid 1000 filecluster \
|
14
|
+
&& useradd --uid 1000 --gid 1000 filecluster --shell /bin/bash
|
15
|
+
|
16
|
+
EXPOSE 22
|
17
|
+
CMD ["/usr/sbin/sshd", "-D"]
|
data/entrypoint.sh
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
|
4
|
+
if [ ! -f /app/bin/db.yml ] ; then
|
5
|
+
./bin/fc-setup-db -h $MYSQL_HOST -u $MYSQL_USER -p $MYSQL_PASSWORD -d $MYSQL_DATABASE -f -i -m
|
6
|
+
fi
|
7
|
+
|
8
|
+
if [ ! -f /home/filecluster/.ssh/id_rsa ]; then
|
9
|
+
mkdir /home/filecluster/.ssh -p
|
10
|
+
chown filecluster:filecluster /home/filecluster/.ssh
|
11
|
+
chmod 0700 /home/filecluster/.ssh
|
12
|
+
ssh-keygen -t rsa -b 2048 -N "" -C "filecluster key" -f /home/filecluster/.ssh/id_rsa
|
13
|
+
cp /home/filecluster/.ssh/{id_rsa.pub,authorized_keys}
|
14
|
+
chown -R filecluster:filecluster /home/filecluster
|
15
|
+
echo "ID_RSA - generate"
|
16
|
+
fi
|
17
|
+
|
18
|
+
export HOME=/home/filecluster/
|
19
|
+
exec setuid 1000 "$@"
|
data/filecluster.gemspec
CHANGED
@@ -22,4 +22,6 @@ Gem::Specification.new do |gem|
|
|
22
22
|
gem.add_development_dependency "rake"
|
23
23
|
gem.add_development_dependency "shoulda-context"
|
24
24
|
gem.add_development_dependency "mocha", ">= 0.13.3"
|
25
|
+
gem.add_development_dependency "byebug"
|
26
|
+
gem.add_development_dependency "rubocop"
|
25
27
|
end
|
data/lib/autosync.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'iostat'
|
2
|
+
|
3
|
+
class Autosync
|
4
|
+
attr_accessor :files_to_delete, :items_to_delete
|
5
|
+
def initialize(storage, dry_run = false)
|
6
|
+
@dry_run = dry_run
|
7
|
+
@start_time = Time.now.to_i - 3600
|
8
|
+
@storage = storage
|
9
|
+
@files_to_delete = []
|
10
|
+
@items_to_delete = []
|
11
|
+
@removed_files_size = 0
|
12
|
+
@io_stat = Iostat.new(@storage.path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.error(msg, options = {})
|
16
|
+
$log.error(msg) if $log
|
17
|
+
FC::Error.new(options.merge(:host => FC::Storage.curr_host, :message => msg)).save
|
18
|
+
end
|
19
|
+
|
20
|
+
def run
|
21
|
+
@db_struct = fill_db
|
22
|
+
$log.debug("Autosync: Scanning disk #{@storage.name} (#{@storage.path})") if $log
|
23
|
+
scan_disk(@db_struct, '')
|
24
|
+
return if $exit_signal
|
25
|
+
$log.debug("Autosync: Scanning DB items #{@storage.name}") if $log
|
26
|
+
scan_db(@db_struct, '')
|
27
|
+
return if $exit_signal
|
28
|
+
delete_diffs unless @dry_run
|
29
|
+
ensure
|
30
|
+
@io_stat.stop
|
31
|
+
end
|
32
|
+
|
33
|
+
def relax_drive
|
34
|
+
sleep 1 while @io_stat.util > 50
|
35
|
+
end
|
36
|
+
|
37
|
+
def fill_db
|
38
|
+
$log.debug("Autosync: Reading DB items for #{@storage.name} ...") if $log
|
39
|
+
|
40
|
+
db_struct = {}
|
41
|
+
last_item_storage_id = 0
|
42
|
+
items_count = 0
|
43
|
+
loop do
|
44
|
+
items = FC::DB.connect.query(%(
|
45
|
+
SELECT its.id, itm.name
|
46
|
+
FROM #{FC::Item.table_name} itm
|
47
|
+
JOIN #{FC::ItemStorage.table_name} its ON its.item_id = itm.id
|
48
|
+
WHERE its.storage_name = '#{@storage.name}'
|
49
|
+
AND its.status = 'ready'
|
50
|
+
AND its.id > #{last_item_storage_id}
|
51
|
+
ORDER BY its.id
|
52
|
+
LIMIT 10000
|
53
|
+
), cache_rows: false, symbolize_keys: true)
|
54
|
+
break if $exit_signal
|
55
|
+
|
56
|
+
# make tree structure with array of values (items) on leafs
|
57
|
+
items.each do |i|
|
58
|
+
items_count += 1
|
59
|
+
last_item_storage_id = i[:id]
|
60
|
+
ref = db_struct
|
61
|
+
path = i[:name].split('/')
|
62
|
+
last_idx = path.size - 1
|
63
|
+
path.each_with_index do |part, idx|
|
64
|
+
if idx == last_idx
|
65
|
+
ref[part] = [false, i[:id]]
|
66
|
+
else
|
67
|
+
ref[part] ||= {}
|
68
|
+
ref = ref[part]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
break unless items.size == 10_000
|
73
|
+
end
|
74
|
+
$log.debug("Autosync: Reading DB items for #{@storage.name} done. Items: #{items_count}") if $log
|
75
|
+
db_struct
|
76
|
+
end
|
77
|
+
|
78
|
+
def scan_disk(db_path, relative_path)
|
79
|
+
return if $exit_signal
|
80
|
+
sleep 0.001
|
81
|
+
relax_drive
|
82
|
+
Dir.glob("#{@storage.path}#{relative_path}*").each do |disk_entry|
|
83
|
+
next if disk_entry == "#{@storage.path}healthcheck"
|
84
|
+
db_item = db_path[disk_entry.split('/').last]
|
85
|
+
case
|
86
|
+
when db_item.is_a?(Array) # tree leaf
|
87
|
+
db_item[0] = true # mark db_item as exists on disk
|
88
|
+
when db_item.is_a?(Hash) && File.directory?(disk_entry) # tree node
|
89
|
+
scan_disk(db_item, "#{disk_entry[@storage.path.size..-1]}/")
|
90
|
+
else # not found in db
|
91
|
+
mtime = File.stat(disk_entry).mtime.to_i rescue Time.now.to_i
|
92
|
+
@files_to_delete << disk_entry if @start_time > mtime # older than 1 hour
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def scan_db(db_item, node_path)
|
98
|
+
return if $exit_signal
|
99
|
+
db_item.each do |item_name, item_data|
|
100
|
+
if item_data.is_a?(Array) # tree leaf
|
101
|
+
@items_to_delete << item_data[1] unless item_data[0]
|
102
|
+
else # tree node
|
103
|
+
scan_db(item_data, "#{node_path}#{item_name}/")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def delete_disk_entry(entry)
|
109
|
+
return false if $exit_signal
|
110
|
+
return true unless File.exist?(entry)
|
111
|
+
remove = true
|
112
|
+
stat = File.stat(entry) rescue nil
|
113
|
+
if File.directory?(entry)
|
114
|
+
Dir.glob("#{entry}/*").each do |sub_entry|
|
115
|
+
relax_drive
|
116
|
+
remove = false unless delete_disk_entry(sub_entry)
|
117
|
+
end
|
118
|
+
else
|
119
|
+
mtime = stat ? stat.mtime.to_i : Time.now.to_i
|
120
|
+
remove = @start_time > mtime
|
121
|
+
end
|
122
|
+
if remove
|
123
|
+
@removed_files_size += stat.size if stat
|
124
|
+
$log.debug("deleting disk entry #{entry}") if $log
|
125
|
+
FileUtils.rm_rf(entry)
|
126
|
+
end
|
127
|
+
remove
|
128
|
+
end
|
129
|
+
|
130
|
+
def delete_diffs
|
131
|
+
$log.debug("Removing #{@files_to_delete.size} disk entries") if $log
|
132
|
+
@files_to_delete.each do |f|
|
133
|
+
break if $exit_signal
|
134
|
+
delete_disk_entry(f)
|
135
|
+
end
|
136
|
+
return if $exit_signal
|
137
|
+
self.class.error("Autosync removed #{@files_to_delete.size} files/dirs from #{@storage.name}. Size: #{@removed_files_size} bytes") if @removed_files_size > 0
|
138
|
+
$log.debug("Removing items #{@items_to_delete.size} from DB for #{@storage.name}") if $log
|
139
|
+
counter = 0
|
140
|
+
@items_to_delete.each do |item_storage_id|
|
141
|
+
its = FC::ItemStorage.where('id = ?', item_storage_id).first
|
142
|
+
next unless its
|
143
|
+
its.status = 'error'
|
144
|
+
its.save
|
145
|
+
self.class.error("item does not exist on storage #{@storage.name}", item_storage_id: item_storage_id.to_i) rescue nil
|
146
|
+
sleep 10 if (counter += 1) % 1000 == 0
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
data/lib/daemon.rb
CHANGED
@@ -6,6 +6,7 @@ require "daemon/run_tasks_thread"
|
|
6
6
|
require "daemon/update_tasks_thread"
|
7
7
|
require "daemon/copy_task_thread"
|
8
8
|
require "daemon/delete_task_thread"
|
9
|
+
require "daemon/autosync_thread"
|
9
10
|
|
10
11
|
def error(msg, options = {})
|
11
12
|
$log.error(msg)
|
@@ -20,7 +21,8 @@ end
|
|
20
21
|
|
21
22
|
def run_global_daemon
|
22
23
|
$log.debug('Run global daemon check')
|
23
|
-
timeout = FC::Var.get('daemon_global_wait_time', 120).
|
24
|
+
timeout = FC::Var.get('daemon_global_wait_time', 120).to_f
|
25
|
+
timeout = 0.3 if timeout < 0.3
|
24
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
|
25
27
|
if !r || r['curr_time'].to_i - r['time'].to_i > timeout
|
26
28
|
$log.debug('Set global daemon host to current')
|
@@ -72,3 +74,17 @@ def run_tasks
|
|
72
74
|
$run_tasks_thread = RunTasksThread.new
|
73
75
|
end
|
74
76
|
end
|
77
|
+
|
78
|
+
def autosync
|
79
|
+
if !$autosync_thread || !$autosync_thread.alive?
|
80
|
+
intervals = FC::Var.get_autosync
|
81
|
+
storage_interval = intervals[FC::Storage.curr_host] || intervals['all']
|
82
|
+
return if storage_interval.zero? # do not run aytosync
|
83
|
+
storages = $storages.select do |s|
|
84
|
+
s.autosync_at.to_i + storage_interval < Time.now.to_i
|
85
|
+
end
|
86
|
+
return unless storages.any?
|
87
|
+
$log.debug("spawn AutosyncThread for storages #{storages.map(&:name).join(', ')}")
|
88
|
+
$autosync_thread = AutosyncThread.new(storages)
|
89
|
+
end
|
90
|
+
end
|