filecluster 0.5.14 → 0.5.15
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 +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
|