filecluster 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ # encoding: utf-8
2
+
3
+ module FC
4
+ class ItemStorage < DbBase
5
+ set_table :items_storages, 'item_id, storage_name, status, time'
6
+
7
+ end
8
+ end
data/lib/fc/policy.rb ADDED
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+
3
+ module FC
4
+ class Policy < DbBase
5
+ set_table :policies, 'storages, copies'
6
+
7
+ def get_storages
8
+ FC::Storage.where("name IN (#{storages.split(',').map{|s| "'#{s}'"}.join(',')})")
9
+ end
10
+
11
+ # get available storage for object by size
12
+ def get_proper_storage(size, exclude = [])
13
+ get_storages.detect do |storage|
14
+ !exclude.include?(storage.name) && storage.up? && storage.size + size < storage.size_limit
15
+ end
16
+ end
17
+ end
18
+ end
data/lib/fc/storage.rb ADDED
@@ -0,0 +1,80 @@
1
+ # encoding: utf-8
2
+
3
+ module FC
4
+ class Storage < DbBase
5
+ set_table :storages, 'name, host, path, url, size, size_limit, check_time'
6
+
7
+ class << self
8
+ attr_accessor :check_time_limit
9
+ end
10
+ @check_time_limit = 120 # ttl for up status check
11
+
12
+ def self.curr_host
13
+ @uname || @uname = `uname -n`.chomp
14
+ end
15
+
16
+ def initialize(params = {})
17
+ path = (params['path'] || params[:path])
18
+ if path && !path.to_s.empty?
19
+ raise "Storage path must be like '/bla/bla../'" unless path.match(/^\/.*\/$/)
20
+ end
21
+ super params
22
+ end
23
+
24
+ def update_check_time
25
+ self.check_time = Time.new.to_i
26
+ save
27
+ end
28
+
29
+ def check_time_delay
30
+ Time.new.to_i - check_time.to_i
31
+ end
32
+
33
+ def up?
34
+ Time.new.to_i - check_time.to_i <= self.class.check_time_limit
35
+ end
36
+
37
+ # copy local_path to storage
38
+ def copy_path(local_path, file_name)
39
+ cmd = self.class.curr_host == host ?
40
+ "cp -r #{local_path} #{self.path}#{file_name}" :
41
+ "scp -rB #{local_path} #{self.host}:#{self.path}#{file_name}"
42
+ r = `#{cmd} 2>&1`
43
+ raise r if $?.exitstatus != 0
44
+ end
45
+
46
+ # copy object to local_path
47
+ def copy_to_local(file_name, local_path)
48
+ cmd = self.class.curr_host == host ?
49
+ "cp -r #{self.path}#{file_name} #{local_path}" :
50
+ "scp -rB #{self.host}:#{self.path}#{file_name} #{local_path}"
51
+ r = `#{cmd} 2>&1`
52
+ raise r if $?.exitstatus != 0
53
+ end
54
+
55
+ # delete object from storage
56
+ def delete_file(file_name)
57
+ cmd = self.class.curr_host == host ?
58
+ "rm -rf #{self.path}#{file_name}" :
59
+ "ssh -oBatchMode=yes #{self.host} 'rm -rf #{self.path}#{file_name}'"
60
+ r = `#{cmd} 2>&1`
61
+ raise r if $?.exitstatus != 0
62
+
63
+ cmd = self.class.curr_host == host ?
64
+ "ls -la #{self.path}#{file_name}" :
65
+ "ssh -oBatchMode=yes #{self.host} 'ls -la #{self.path}#{file_name}'"
66
+ r = `#{cmd} 2>/dev/null`
67
+ raise "Path #{self.path}#{file_name} not deleted" unless r.empty?
68
+ end
69
+
70
+ # return object size on storage
71
+ def file_size(file_name)
72
+ cmd = self.class.curr_host == host ?
73
+ "du -sb #{self.path}#{file_name}" :
74
+ "ssh -oBatchMode=yes #{self.host} 'du -sb #{self.path}#{file_name}'"
75
+ r = `#{cmd} 2>&1`
76
+ raise r if $?.exitstatus != 0
77
+ r.to_i
78
+ end
79
+ end
80
+ end
data/lib/fc/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module FC
2
+ VERSION = "0.0.3"
3
+ end
@@ -0,0 +1,13 @@
1
+ require "date"
2
+ require "fc/version"
3
+ require "fc/db"
4
+ require "fc/base"
5
+ require "fc/item_storage"
6
+ require "fc/item"
7
+ require "fc/policy"
8
+ require "fc/storage"
9
+ require "fc/error"
10
+
11
+ module FC
12
+
13
+ end
data/lib/manage.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'readline'
2
+ require "manage/policies"
3
+ require "manage/storages"
4
+ require "manage/show"
@@ -0,0 +1,70 @@
1
+ def policies_list
2
+ policies = FC::Policy.where
3
+ if policies.size == 0
4
+ puts "No storages."
5
+ else
6
+ policies.each do |policy|
7
+ puts "##{policy.id} storages: #{policy.storages}, copies: #{policy.copies}"
8
+ end
9
+ end
10
+ end
11
+
12
+ def policies_show
13
+ id = ARGV[2]
14
+ policy = FC::Policy.where('id = ?', id).first
15
+ if !policy
16
+ puts "Policy ##{id} not found."
17
+ else
18
+ count = FC::DB.connect.query("SELECT count(*) as cnt FROM #{FC::Item.table_name} WHERE policy_id = #{policy.id}").first['cnt']
19
+ puts %Q{Policy
20
+ ID: #{policy.id}
21
+ Storages: #{policy.storages}
22
+ Copies: #{policy.copies}
23
+ Items: #{count}}
24
+ end
25
+ end
26
+
27
+ def policies_add
28
+ puts "Add Policy"
29
+ storages = stdin_read_val('Storages')
30
+ copies = stdin_read_val('Copies').to_i
31
+ begin
32
+ policy = FC::Policy.new(:storages => storages, :copies => copies)
33
+ rescue Exception => e
34
+ puts "Error: #{e.message}"
35
+ exit
36
+ end
37
+ puts %Q{\nPolicy
38
+ Storages: #{storages}
39
+ Copies: #{copies}}
40
+ s = Readline.readline("Continue? (y/n) ", false).strip.downcase
41
+ puts ""
42
+ if s == "y" || s == "yes"
43
+ begin
44
+ policy.save
45
+ rescue Exception => e
46
+ puts "Error: #{e.message}"
47
+ exit
48
+ end
49
+ puts "ok"
50
+ else
51
+ puts "Canceled."
52
+ end
53
+ end
54
+
55
+ def policies_rm
56
+ id = ARGV[2]
57
+ policy = FC::Policy.where('id = ?', id).first
58
+ if !policy
59
+ puts "Policy ##{id} not found."
60
+ else
61
+ s = Readline.readline("Continue? (y/n) ", false).strip.downcase
62
+ puts ""
63
+ if s == "y" || s == "yes"
64
+ policy.delete
65
+ puts "ok"
66
+ else
67
+ puts "Canceled."
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,57 @@
1
+ def show_current_host
2
+ puts "Current host: #{FC::Storage.curr_host}"
3
+ end
4
+
5
+ def show_global_daemon
6
+ 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
7
+ if r
8
+ puts "Global daemon run on #{r['val']}\nLast run #{r['curr_time']-r['time']} seconds ago."
9
+ else
10
+ puts "Global daemon is not runnning."
11
+ end
12
+ end
13
+
14
+ def show_errors
15
+ count = ARGV[2] || 10
16
+ errors = FC::Error.where("1 ORDER BY id desc LIMIT #{count.to_i}")
17
+ if errors.size == 0
18
+ puts "No errors."
19
+ else
20
+ errors.each do |error|
21
+ puts "#{Time.at(error.time)} item_id: #{error.item_id}, item_storage_id: #{error.item_storage_id}, host: #{error.host}, message: #{error.message}"
22
+ end
23
+ end
24
+ end
25
+
26
+ def show_host_info
27
+ host = ARGV[2] || FC::Storage.curr_host
28
+ storages = FC::Storage.where("host = ?", host)
29
+ if storages.size == 0
30
+ puts "No storages."
31
+ else
32
+ puts "Info for host #{host}"
33
+ storages.each do |storage|
34
+ counts = FC::DB.connect.query("SELECT status, count(*) as cnt FROM #{FC::ItemStorage.table_name} WHERE storage_name='#{Mysql2::Client.escape(storage.name)}' GROUP BY status")
35
+ str = "#{storage.name} #{size_to_human(storage.size)}/#{size_to_human(storage.size_limit)} "
36
+ str += "#{storage.up? ? colorize_string('UP', :green) : colorize_string('DOWN', :red)}"
37
+ str += " #{storage.check_time_delay} seconds ago" if storage.check_time
38
+ str += "\n"
39
+ counts.each do |r|
40
+ str += " Items storages #{r['status']}: #{r['cnt']}\n"
41
+ end
42
+ puts str
43
+ end
44
+ end
45
+ end
46
+
47
+ def show_items_info
48
+ puts "Items by status:"
49
+ counts = FC::DB.connect.query("SELECT status, count(*) as cnt FROM #{FC::Item.table_name} WHERE 1 GROUP BY status")
50
+ counts.each do |r|
51
+ puts " #{r['status']}: #{r['cnt']}"
52
+ end
53
+ count = FC::DB.connect.query("SELECT count(*) as cnt FROM #{FC::Item.table_name} as i, #{FC::Policy.table_name} as p WHERE i.policy_id = p.id AND i.copies > 0 AND i.copies < p.copies AND i.status = 'ready'").first['cnt']
54
+ puts "Items to copy: #{count}"
55
+ count = FC::DB.connect.query("SELECT count(*) as cnt FROM #{FC::Item.table_name} as i, #{FC::Policy.table_name} as p WHERE i.policy_id = p.id AND i.copies > p.copies AND i.status = 'ready'").first['cnt']
56
+ puts "Items to delete: #{count}"
57
+ end
@@ -0,0 +1,87 @@
1
+ def storages_list
2
+ storages = FC::Storage.where("1 ORDER BY host")
3
+ if storages.size == 0
4
+ puts "No storages."
5
+ else
6
+ storages.each do |storage|
7
+ str = "#{colorize_string(storage.host, :yellow)} #{storage.name} #{size_to_human(storage.size)}/#{size_to_human(storage.size_limit)} "
8
+ str += "#{storage.up? ? colorize_string('UP', :green) : colorize_string('DOWN', :red)}"
9
+ str += " #{storage.check_time_delay} seconds ago" if storage.check_time
10
+ puts str
11
+ end
12
+ end
13
+ end
14
+
15
+ def storages_show
16
+ name = ARGV[2]
17
+ storage = FC::Storage.where('name = ?', name).first
18
+ if !storage
19
+ puts "Storage #{name} not found."
20
+ else
21
+ count = FC::DB.connect.query("SELECT count(*) as cnt FROM #{FC::ItemStorage.table_name} WHERE storage_name='#{Mysql2::Client.escape(storage.name)}'").first['cnt']
22
+ puts %Q{Storage
23
+ Name: #{storage.name}
24
+ Host: #{storage.host}
25
+ Path: #{storage.path}
26
+ Url: #{storage.url}
27
+ Size: #{size_to_human storage.size}
28
+ Size limit: #{size_to_human storage.size_limit}
29
+ Check time: #{storage.check_time ? "#{Time.at(storage.check_time)} (#{storage.check_time_delay} seconds ago)" : ''}
30
+ Status: #{storage.up? ? colorize_string('UP', :green) : colorize_string('DOWN', :red)}
31
+ Items storages: #{count}}
32
+ end
33
+ end
34
+
35
+ def storages_add
36
+ host = FC::Storage.curr_host
37
+ puts "Add storage to host #{host}"
38
+ name = stdin_read_val('Name')
39
+ path = stdin_read_val('Path')
40
+ url = stdin_read_val('Url')
41
+ size_limit = human_to_size stdin_read_val('Size limit') {|val| "Size limit not is valid size." unless human_to_size(val)}
42
+ begin
43
+ storage = FC::Storage.new(:name => name, :host => host, :path => path, :url => url, :size_limit => size_limit)
44
+ size = storage.file_size('')
45
+ rescue Exception => e
46
+ puts "Error: #{e.message}"
47
+ exit
48
+ end
49
+ puts %Q{\nStorage
50
+ Name: #{name}
51
+ Host: #{host}
52
+ Path: #{path}
53
+ Url: #{url}
54
+ Size: #{size_to_human size}
55
+ Size limit: #{size_to_human size_limit}}
56
+ s = Readline.readline("Continue? (y/n) ", false).strip.downcase
57
+ puts ""
58
+ if s == "y" || s == "yes"
59
+ storage.size = size
60
+ begin
61
+ storage.save
62
+ rescue Exception => e
63
+ puts "Error: #{e.message}"
64
+ exit
65
+ end
66
+ puts "ok"
67
+ else
68
+ puts "Canceled."
69
+ end
70
+ end
71
+
72
+ def storages_rm
73
+ name = ARGV[2]
74
+ storage = FC::Storage.where('name = ?', name).first
75
+ if !storage
76
+ puts "Storage #{name} not found."
77
+ else
78
+ s = Readline.readline("Continue? (y/n) ", false).strip.downcase
79
+ puts ""
80
+ if s == "y" || s == "yes"
81
+ storage.delete
82
+ puts "ok"
83
+ else
84
+ puts "Canceled."
85
+ end
86
+ end
87
+ end
data/lib/utils.rb ADDED
@@ -0,0 +1,76 @@
1
+ def option_parser_init(descriptions, text)
2
+ options = {}
3
+ optparse = OptionParser.new do |opts|
4
+ opts.banner = text
5
+ opts.separator "Options:"
6
+
7
+ descriptions.each_entry do |key, desc|
8
+ options[key] = desc[:default]
9
+ opts.on("-#{desc[:short]}", "--#{desc[:full]}#{desc[:no_val] ? '' : '='+desc[:full].upcase}", desc[:text]) {|s| options[key] = s }
10
+ end
11
+ opts.on_tail("-?", "--help", "Show this message") do
12
+ puts opts
13
+ exit
14
+ end
15
+ opts.on_tail("-v", "--version", "Show version") do
16
+ puts FC::VERSION
17
+ exit
18
+ end
19
+ end
20
+ optparse.parse!
21
+ options['optparse'] = optparse
22
+ options
23
+ end
24
+
25
+ def size_to_human(size)
26
+ return "0" if size == 0
27
+ units = %w{B KB MB GB TB}
28
+ e = (Math.log(size)/Math.log(1024)).floor
29
+ s = "%.2f" % (size.to_f / 1024**e)
30
+ s.sub(/\.?0*$/, units[e])
31
+ end
32
+
33
+ def human_to_size(size)
34
+ r = /^(\d+(\.\d+)?)\s*(.*)/
35
+ units = {'k' => 1024, 'm' => 1024*1024, 'g' => 1024*1024*1024, 't' => 1024*1024*1024*1024}
36
+ return nil unless matches = size.to_s.match(r)
37
+ unit = units[matches[3].to_s.strip.downcase[0]]
38
+ result = matches[1].to_f
39
+ result *= unit if unit
40
+ result.to_i
41
+ end
42
+
43
+ def stdin_read_val(name)
44
+ while val = Readline.readline("#{name}: ", false).strip.downcase
45
+ if val.empty?
46
+ puts "Input non empty #{name}."
47
+ else
48
+ if block_given?
49
+ if err = yield(val)
50
+ puts err
51
+ else
52
+ return val
53
+ end
54
+ else
55
+ return val
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ def colorize_string(str, color)
62
+ return str unless color
63
+ case color.to_s
64
+ when 'red'
65
+ color_code = 31
66
+ when 'green'
67
+ color_code = 32
68
+ when 'yellow'
69
+ color_code = 33
70
+ when 'pink'
71
+ color_code = 35
72
+ else
73
+ color_code = color.to_i
74
+ end
75
+ "\e[#{color_code}m#{str}\e[0m"
76
+ end
data/test/base_test.rb ADDED
@@ -0,0 +1,74 @@
1
+ require 'helper'
2
+
3
+ class BaseTest < Test::Unit::TestCase
4
+ class << self
5
+ def shutdown
6
+ FC::DB.connect.query("DELETE FROM items")
7
+ end
8
+ end
9
+
10
+ def setup
11
+ @@policy_id ||= 1
12
+ @item = FC::Item.new(:name => 'test1', :tag => 'test tag', :dir => 0, :size => 100, :blabla => 'blabla', :policy_id => @@policy_id)
13
+ @@policy_id += 1
14
+ end
15
+
16
+ should "correct init" do
17
+ assert_raise(NoMethodError, 'Set not table field') { @item.blabla }
18
+ assert @item, 'Item not created'
19
+ assert_nil @item.id, 'Not nil id for new item'
20
+ end
21
+
22
+ should "correct add and save item" do
23
+ @item.save
24
+ assert id=@item.id, 'Nil id after save'
25
+ # double save check
26
+ @item.save
27
+ assert_equal id, @item.id, 'Changed id after double save'
28
+ @item.copies = 2
29
+ @item.save
30
+ assert_equal id, @item.id, 'Changed id after save with changes'
31
+ end
32
+
33
+ should "correct where" do
34
+ @item.save
35
+ ids = [@item.id]
36
+ item2 = FC::Item.new(:name => 'test2', :tag => 'test tag', :dir => 0, :size => 100, :blabla => 'blabla', :policy_id => 100)
37
+ item2.save
38
+ ids << item2.id
39
+ items = FC::Item.where("id IN (#{ids.join(',')})")
40
+ assert_same_elements items.map(&:id), ids, "Items by where load <> items by find"
41
+ end
42
+
43
+ should "correct reload item" do
44
+ @item.save
45
+ @item.name = 'new test'
46
+ @item.tag = 'new test tag'
47
+ @item.dir = '1'
48
+ @item.size = '777'
49
+ @item.reload
50
+ assert_same_elements ['test1', 'test tag', 0, 100], [@item.name, @item.tag, @item.dir, @item.size], 'Fields not restoted after reload'
51
+ end
52
+
53
+ should "correct update and load item" do
54
+ assert_raise(RuntimeError) { FC::Item.find(12454845) }
55
+ @item.save
56
+ @item.copies = 1
57
+ @item.save
58
+ @item.outer_id = 111
59
+ @item.save
60
+ loaded_item = FC::Item.find(@item.id)
61
+ assert_kind_of FC::Item, loaded_item, 'Load not FC::Item'
62
+ assert_equal @item.name, loaded_item.name, 'Saved item name <> loaded item name'
63
+ assert_equal @item.tag, loaded_item.tag, 'Saved item tag <> loaded item tag'
64
+ assert_equal @item.dir, loaded_item.dir, 'Saved item dir <> loaded item dir'
65
+ assert_equal @item.size, loaded_item.size, 'Saved item size <> loaded item size'
66
+ assert_equal @item.copies, loaded_item.copies, 'Saved item copies <> loaded item copies'
67
+ assert_equal @item.outer_id, loaded_item.outer_id, 'Saved item outer_id <> loaded item outer_id'
68
+ end
69
+
70
+ should "correct delete item" do
71
+ assert_nothing_raised { @item.delete }
72
+ assert_raise(RuntimeError, 'Item not deleted') { FC::Item.find(@item.id) }
73
+ end
74
+ end