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.
@@ -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