evesync 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +25 -0
  3. data/Rakefile +83 -0
  4. data/bin/evedatad +34 -0
  5. data/bin/evehand +36 -0
  6. data/bin/evemond +42 -0
  7. data/bin/evesync +164 -0
  8. data/bin/evesyncd +44 -0
  9. data/bin/start +16 -0
  10. data/config/example.conf +58 -0
  11. data/lib/evesync/config.rb +63 -0
  12. data/lib/evesync/constants.rb +17 -0
  13. data/lib/evesync/database.rb +107 -0
  14. data/lib/evesync/discover.rb +77 -0
  15. data/lib/evesync/err.rb +25 -0
  16. data/lib/evesync/handler/file.rb +28 -0
  17. data/lib/evesync/handler/package.rb +30 -0
  18. data/lib/evesync/handler.rb +89 -0
  19. data/lib/evesync/ipc/client.rb +37 -0
  20. data/lib/evesync/ipc/data/file.rb +64 -0
  21. data/lib/evesync/ipc/data/hashable.rb +74 -0
  22. data/lib/evesync/ipc/data/ignore.rb +22 -0
  23. data/lib/evesync/ipc/data/package.rb +58 -0
  24. data/lib/evesync/ipc/data/utils.rb +14 -0
  25. data/lib/evesync/ipc/data.rb +50 -0
  26. data/lib/evesync/ipc/ipc.rb +42 -0
  27. data/lib/evesync/ipc/server.rb +56 -0
  28. data/lib/evesync/log.rb +66 -0
  29. data/lib/evesync/ntp.rb +16 -0
  30. data/lib/evesync/os/linux/arch/package_manager.rb +29 -0
  31. data/lib/evesync/os/linux/arch/package_watcher.rb +59 -0
  32. data/lib/evesync/os/linux/arch.rb +2 -0
  33. data/lib/evesync/os/linux/base_package_manager.rb +80 -0
  34. data/lib/evesync/os/linux/deb/dpkg.rb +30 -0
  35. data/lib/evesync/os/linux/deb/package_manager.rb +38 -0
  36. data/lib/evesync/os/linux/deb/package_watcher.rb +32 -0
  37. data/lib/evesync/os/linux/deb.rb +2 -0
  38. data/lib/evesync/os/linux/rhel/package_manager.rb +42 -0
  39. data/lib/evesync/os/linux/rhel/package_watcher.rb +32 -0
  40. data/lib/evesync/os/linux/rhel/rpm.rb +41 -0
  41. data/lib/evesync/os/linux/rhel.rb +3 -0
  42. data/lib/evesync/os/linux.rb +9 -0
  43. data/lib/evesync/os.rb +2 -0
  44. data/lib/evesync/sync.rb +209 -0
  45. data/lib/evesync/trigger/base.rb +56 -0
  46. data/lib/evesync/trigger/file.rb +20 -0
  47. data/lib/evesync/trigger/package.rb +20 -0
  48. data/lib/evesync/trigger.rb +106 -0
  49. data/lib/evesync/utils.rb +51 -0
  50. data/lib/evesync/watcher/file.rb +156 -0
  51. data/lib/evesync/watcher/interface.rb +21 -0
  52. data/lib/evesync/watcher/package.rb +8 -0
  53. data/lib/evesync/watcher.rb +68 -0
  54. data/lib/evesync.rb +3 -0
  55. metadata +198 -0
@@ -0,0 +1,77 @@
1
+ require 'evesync/ipc/client'
2
+ require 'evesync/log'
3
+ require 'evesync/config'
4
+
5
+ module Evesync
6
+
7
+ ##
8
+ # Discover other nodes
9
+ # Handles discovering messages sending and receiving
10
+ #
11
+ # = Example
12
+ #
13
+ # disc = Discover.new
14
+ # disc.send_discovery_message
15
+ # ...
16
+ # disc.stop
17
+
18
+ class Discover
19
+ DISCOVERY_REQ = 'EVESYNC'.freeze
20
+ DISCOVERY_ANS = 'DISCOVERED'.freeze
21
+
22
+ def initialize
23
+ # Starting thread that sends and accepts UDP-packages.
24
+ # This is how a node can say that it's online
25
+ @evesync = IPC::Client.new(
26
+ port: :evemond
27
+ )
28
+ @port = Config[:evesyncd]['broadcast_port']
29
+ @listen_sock = UDPSocket.new
30
+ @listen_sock.bind('0.0.0.0', @port)
31
+ @listen_thread = Thread.new { listen_discovery }
32
+ end
33
+
34
+ ##
35
+ # Sending UDP message on broadcast
36
+ # Discovering our nodes
37
+
38
+ def send_discovery_message(ip = '<broadcast>', message = DISCOVERY_REQ)
39
+ udp_sock = UDPSocket.new
40
+ if ip == '<broadcast>'
41
+ udp_sock.setsockopt(
42
+ Socket::SOL_SOCKET, Socket::SO_BROADCAST, true
43
+ )
44
+ end
45
+ udp_sock.send(message, 0, ip, @port)
46
+ udp_sock.close
47
+ end
48
+
49
+ def stop
50
+ @listen_thread.exit
51
+ end
52
+
53
+ private
54
+
55
+ def listen_discovery
56
+ loop do
57
+ data, recvdata = @listen_sock.recvfrom(1024)
58
+ node_ip = recvdata[-1]
59
+
60
+ next if Utils.local_ip?(node_ip)
61
+
62
+ if [DISCOVERY_REQ, DISCOVERY_ANS].include? data
63
+ # Push new node_ip to trigger
64
+ @evesync.add_remote_node(node_ip)
65
+ end
66
+
67
+ case data
68
+ when DISCOVERY_REQ
69
+ Log.info("Discover host request got: #{node_ip}")
70
+ send_discovery_message(node_ip, DISCOVERY_ANS)
71
+ when DISCOVERY_ANS
72
+ Log.info("Discover host response got: #{node_ip}")
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,25 @@
1
+ # TODO: move to a err directory
2
+ module Evesync
3
+ module Err
4
+
5
+ # Base Evesync error class.
6
+ # Using it as a base for all Evesync exceptions.
7
+ #
8
+ # = Example:
9
+ # class MyError < Evesync::Err::Base
10
+ # def initialize(message, *args)
11
+ # super(message)
12
+ # ...
13
+ # end
14
+ # ...
15
+ # end
16
+ class Base < RuntimeError
17
+ def initialize(message)
18
+ super(message)
19
+ end
20
+ end
21
+
22
+ class SaveError < Base
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ require 'evesync/ipc/data/file'
2
+
3
+ module Evesync
4
+ class Handler
5
+ class File
6
+ def handle(file)
7
+ Log.debug("Handler File handling started...")
8
+ content = file.content
9
+ name = file.name
10
+ Log.debug("Handler File writing received content to #{name}")
11
+
12
+ if file.action == IPC::Data::File::Action::DELETE
13
+ ::File.delete(name)
14
+ else # TODO: handle move_to and move_from
15
+ # TODO: handle exceptions or throw them
16
+ # Writing content
17
+ ::File.write(name, content)
18
+ # Changing mode
19
+ ::File.chmod(name, file.mode)
20
+ end
21
+
22
+ # Returning all fine!
23
+ Log.debug("Handler File handling done!")
24
+ true
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ require 'evesync/log'
2
+ require 'evesync/os'
3
+
4
+ module Evesync
5
+ class Handler
6
+ class Package
7
+ def handle(message)
8
+ Log.debug('Handler Package handling started...')
9
+
10
+ args = [message.name, message.version]
11
+
12
+ case message.command
13
+ when /install/
14
+ OS::PackageManager.install(*args)
15
+ when /remove/
16
+ OS::PackageManager.remove(*args)
17
+ when /update/
18
+ OS::PackageManager.update(*args)
19
+ when /downgrade/
20
+ OS::PackageManager.downgrade(*args)
21
+ else
22
+ Log.warn("Handler Package command unknown: #{message.command}")
23
+ return false
24
+ end
25
+ Log.debug('Handler Package handling done!')
26
+ true
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,89 @@
1
+ require 'evesync/log'
2
+ require 'evesync/handler/package'
3
+ require 'evesync/handler/file'
4
+ require 'evesync/ipc/client'
5
+
6
+ module Evesync
7
+
8
+ # Handles package changes, sent via Package class and queue
9
+ # Sends messages to evedatad and available evehands.
10
+ #
11
+ # [See]
12
+ # - *Evesync::Trigger::File*
13
+ # - *Evesync::Trigger::Package*
14
+ #
15
+ # [Handlers available:]
16
+ # - *Evesync::Handler::Package*
17
+ # - *Evesync::Handler::File*
18
+ #
19
+ # = Example:
20
+ #
21
+ # handler = Evesync::Handler.new(queue)
22
+ # Evesync::IPC::Server.new(
23
+ # :proxy => handler,
24
+ # ...
25
+ # )
26
+ #
27
+ # = Example call:
28
+ #
29
+ # Evesync::IPC::Client.new(
30
+ # :port => :evehand
31
+ # ).handle(IPC::Data::Package.new(
32
+ # :name => 'tree',
33
+ # :version => '0.0.1',
34
+ # :command => :install
35
+ # )
36
+ #
37
+ # = TODO:
38
+ #
39
+ # * Make anoter daemon\Thread to search for available
40
+ # evehands daemons
41
+ # * Delegate +handle+ to another daemon if not found
42
+ class Handler
43
+ def initialize
44
+ @package_handler = Handler::Package.new
45
+ @files_handler = Handler::File.new
46
+ @monitor = IPC::Client.new(
47
+ port: :evemond
48
+ )
49
+ @database = IPC::Client.new(
50
+ port: :evedatad
51
+ )
52
+ Log.debug('Handler initialization done!')
53
+ end
54
+
55
+ def handle(message)
56
+ Log.info "Handler triggered with: #{message}"
57
+
58
+ handler = if message.is_a? IPC::Data::Package
59
+ @package_handler
60
+ elsif message.is_a? IPC::Data::File
61
+ @files_handler
62
+ else
63
+ Log.error('Handler: unknown message type')
64
+ nil
65
+ end
66
+ return unless handler
67
+
68
+ @monitor.ignore(message)
69
+
70
+ # TODO: add PackageManagerLock exception
71
+ # FIXME: package manger may be locked
72
+ # Add sleep and ones again try if PackageManagerLock
73
+ # exception is cought
74
+ handler.handle(message) || @monitor.unignore(message)
75
+ @database.save(message)
76
+
77
+ true
78
+ end
79
+
80
+ # For syncing and other remove db access
81
+ def events
82
+ @database.events
83
+ end
84
+
85
+ def messages(*args)
86
+ @database.messages(*args)
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,37 @@
1
+ require 'drb/drb'
2
+ require 'evesync/log'
3
+ require 'evesync/ipc/ipc'
4
+
5
+ module Evesync
6
+ module IPC
7
+ class Client
8
+ include IPC
9
+
10
+ attr_reader :ip, :uri
11
+
12
+ def initialize(params)
13
+ check_params_provided(params, [:port])
14
+ port = get_port(params)
15
+ @ip = params[:ip] || 'localhost' # TODO: check ip
16
+ @uri = "druby://#{@ip}:#{port}"
17
+ # to remote calls for unmarshallable objects
18
+ DRb.start_service
19
+ end
20
+
21
+ # TODO: add callbacks
22
+ def method_missing(method, *args, &block)
23
+ Log.debug("RPC Client calling '#{method}' on #{@uri}")
24
+ # FIXME: don't send +start+ and +stop+ and +initialize+
25
+ begin
26
+ service = DRbObject.new_with_uri(@uri)
27
+ res = service.send(method, *args, &block)
28
+ Log.debug("RPC Client method '#{method}' handled on #{@uri}")
29
+ res
30
+ rescue StandardError
31
+ Log.warn("RPC Client ERROR: no connection")
32
+ nil
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,64 @@
1
+ require 'evesync/ipc/data/hashable'
2
+ require 'evesync/ntp'
3
+
4
+ module Evesync
5
+ module IPC
6
+ module Data
7
+ class File
8
+ include Hashable
9
+ extend Unhashable
10
+
11
+ module Action
12
+ MODIFY = :modify # File was modified
13
+ DELETE = :delete # File was deleted
14
+ MOVED_TO = :moved_to # File was renamed
15
+ CREATE = :create # File was created
16
+ end
17
+
18
+ attr_reader :name, :mode, :action, :timestamp
19
+
20
+ def initialize(params)
21
+ @name = params[:name].freeze
22
+ @mode = params[:mode].freeze
23
+ @action = parse_action(params[:action]).freeze
24
+ @timestamp = params[:timestamp] || NTP.timestamp
25
+ @content = params[:content] || IO.read(@name).freeze if ::File.exist? @name
26
+ end
27
+
28
+ def ==(other)
29
+ (@name == other.name) &&
30
+ (@action == other.action) &&
31
+ (@mode == other.mode)
32
+ # timestamps may differ
33
+ # conten comparing may cost too much
34
+ end
35
+
36
+ # The content of a file for remote call. Sends as
37
+ # a plain text(?), no extra calls between machines.
38
+ #
39
+ # = TODO
40
+ # * Think about binary data
41
+ # * Encoding information
42
+ # * Large file sending
43
+ attr_reader :content
44
+
45
+ private
46
+
47
+ def parse_action(action)
48
+ case action.to_s
49
+ when /modify/i
50
+ result = Action::MODIFY
51
+ when /delete/i
52
+ result = Action::DELETE
53
+ when /moved_to/i
54
+ result = Action::MOVED_TO
55
+ when /create/i
56
+ result = Action::CREATE
57
+ end
58
+
59
+ result
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,74 @@
1
+ require 'evesync/log'
2
+ require 'json'
3
+
4
+ module Evesync
5
+ module IPC
6
+ module Data
7
+ # The class, that includes it must implement method
8
+ # *initialize(params)*
9
+ # This is a MUST BE requirement
10
+ #
11
+ module Hashable
12
+ def to_hash
13
+ hash = {}
14
+ instance_variables.each do |var|
15
+ value = instance_variable_get(var)
16
+
17
+ if value.respond_to? :to_hash
18
+ # FIXME: if it wasn't implemented it'll be an error
19
+ # for a complex type
20
+ hash[var] = value.to_hash
21
+ hash[var]['type'] = value.class.to_s
22
+ else
23
+ hash[var] = value
24
+ end
25
+ hash['type'] = self.class.to_s
26
+ end
27
+ Log.debug("IPC Data message hash created: #{hash}")
28
+ hash
29
+ end
30
+ end
31
+
32
+ module Unhashable
33
+ def from_hash(hash)
34
+ Log.debug("IPC Data message hash parsing: #{hash}")
35
+ params = {}
36
+ hash.each do |key, value|
37
+ next unless key =~ /^@/
38
+
39
+ if value.is_a? Hash
40
+ # FIXME: code dumplication ipc_data.rb:31
41
+ begin
42
+ cl = Object.const_get value['type']
43
+ rescue NameError => e
44
+ Log.fatal("IPC Data Unsupported type: #{hash['type']}")
45
+ raise e
46
+ end
47
+
48
+ unless cl.respond_to? :from_hash
49
+ err_msg = "IPC Data ERROR Class #{cl} must implement `self.from_hash'"
50
+ Log.fatal(err_msg)
51
+ raise err_msg
52
+ end
53
+
54
+ complex_value = cl.from_hash value
55
+ params[key.sub('@', '').to_sym] = complex_value
56
+ else
57
+ params[key.sub('@', '').to_sym] = value
58
+ end
59
+ end
60
+
61
+ begin
62
+ # If the `type' is imported it will be used
63
+ cl = Object.const_get hash['type']
64
+ rescue TypeError, NameError
65
+ # Or the base class will be the type
66
+ cl = self
67
+ end
68
+
69
+ cl.new(params)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,22 @@
1
+ require 'evesync/ipc/data/hashable'
2
+
3
+ module Evesync
4
+ module IPC
5
+ module Data
6
+ class Ignore
7
+ include Hashable
8
+ extend Unhashable
9
+
10
+ attr_reader :subject
11
+
12
+ def initialize(params)
13
+ @subject = params[:subject]
14
+ end
15
+
16
+ def to_s
17
+ "Ignoring: #{@subject}"
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,58 @@
1
+ require 'evesync/ipc/data/hashable'
2
+
3
+ module Evesync
4
+ module IPC
5
+ module Data
6
+ class Package
7
+ include Data::Hashable
8
+ extend Data::Unhashable
9
+
10
+ module Command
11
+ INSTALL = :install
12
+ UPDATE = :update
13
+ DOWNGRADE = :downgrade
14
+ REMOVE = :remove
15
+ UNKNOWN = :unknown
16
+ end
17
+
18
+ attr_reader :name, :version, :command, :timestamp
19
+
20
+ def initialize(params)
21
+ @name = params[:name].freeze
22
+ @version = params[:version].freeze
23
+ @command = parse_command(params[:command]).freeze
24
+ @timestamp = params[:timestamp] || NTP.timestamp
25
+ end
26
+
27
+ def ==(pkg)
28
+ (pkg.name == @name) &&
29
+ (pkg.version == @version) &&
30
+ (pkg.command == @command)
31
+ end
32
+
33
+ def to_s
34
+ "Package(#{@command.upcase}: #{name}-#{@version})"
35
+ end
36
+
37
+ private
38
+
39
+ def parse_command(command)
40
+ cmd = case command
41
+ when /^inst\w+$/
42
+ Command::INSTALL
43
+ when /^(remove\w*|delete\w*)$/
44
+ Command::REMOVE
45
+ when /^(update\w*|upgrade\w*)$/
46
+ Command::UPDATE
47
+ when /^downgrade\w*$/
48
+ Command::DOWNGRADE
49
+ else
50
+ Command::UNKNOWN
51
+ end
52
+
53
+ cmd.to_s
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,14 @@
1
+ Dir[File.dirname(__FILE__) + '/*.rb'].each do |file|
2
+ require file unless file.include?(__FILE__)
3
+ end
4
+
5
+ module Evesync
6
+ module IPC
7
+ module Data
8
+ def self.from_json(json)
9
+ hash = JSON.parse(json)
10
+ Class.new.extend(Unhashable).from_hash(hash)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,50 @@
1
+ require 'drb/drb'
2
+
3
+ # TODO: add custom exceptions for IPCData
4
+ module Evesync
5
+ module IPCData
6
+ def self.pack(message)
7
+ unless message.respond_to? :to_hash
8
+ err_msg = "IPC ERROR Instance #{message} must implement `to_hash'"
9
+ Log.fatal(err_msg)
10
+ raise err_msg
11
+ end
12
+
13
+ hash = message.to_hash
14
+
15
+ hash.to_json
16
+ end
17
+
18
+ def self.unpack(message)
19
+ unless message.is_a? String
20
+ raise "IPC ERROR message #{message} must be of type String"
21
+ end
22
+
23
+ begin
24
+ hash = JSON.parse(message)
25
+ rescue JSON::ParseError => e
26
+ Log.fatal("IPC ERROR Unable to parse message #{message}")
27
+ raise e
28
+ end
29
+
30
+ begin
31
+ Log.debug("IPC Accepted basic hash #{hash}")
32
+ cl = Object.const_get hash['type']
33
+ rescue NameError => e
34
+ # FIXME: just sent JSON, this event will be delegated
35
+ # to another daemon (maybe) with fields:
36
+ # redirect_to_port: <port number>
37
+ Log.fatal("Unsupported basic type #{hash['type']}")
38
+ raise e
39
+ end
40
+
41
+ unless cl.respond_to? :from_hash
42
+ err_msg = "IPC ERROR Class #{cl} must implement `self.from_hash'"
43
+ Log.fatal(err_msg)
44
+ raise err_msg
45
+ end
46
+
47
+ cl.from_hash hash
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,42 @@
1
+ require 'evesync/config'
2
+
3
+ module Evesync
4
+ #
5
+ # Constants and helpful functions for Evesync::IPC module.
6
+ #
7
+ module IPC
8
+ $SAFE = 1 # 'no eval' rule
9
+
10
+ private
11
+
12
+ # Checks if params have the provided keys
13
+ #
14
+ # [*Raise*] RuntimeError if params don't include on of the
15
+ # keys
16
+ def check_params_provided(params, keys)
17
+ keys.each do |param|
18
+ raise ":#{param} missed" unless
19
+ params.key?(param)
20
+ end
21
+ end
22
+
23
+ # Maps symbols like :evemond, :evehand to appropriate
24
+ # port number.
25
+ #
26
+ # [*Return*] Port number, if it's in (49152..65535)
27
+ # or one of daemons' name
28
+ def get_port(params)
29
+ port = params[:port]
30
+ if port.is_a? Symbol
31
+ Config[port.to_s]['port']
32
+ else
33
+ port_i = port.to_i
34
+ unless (port_i < 65_535) && (port_i > 49_152)
35
+ raise RuntimeError.call('Port MUST be in (49152..65535)')
36
+ end
37
+
38
+ port
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,56 @@
1
+ require 'drb/drb'
2
+ require 'evesync/log'
3
+ require 'evesync/ipc/ipc'
4
+
5
+ module Evesync
6
+ module IPC
7
+
8
+ ##
9
+ # Server is a DRb object, using +:port+ and +:proxy+
10
+ # object to handle requests.
11
+ #
12
+ # = Params
13
+ #
14
+ # [*:proxy*] all methods go to this object
15
+ # [*:port*] defines which port which port connect to
16
+ #
17
+ # = Example:
18
+ #
19
+ # # Setup the server
20
+ # server = Evesync::IPC::Server(
21
+ # :port => '8089',
22
+ # :proxy => SomeHandler.new
23
+ # )
24
+ # ...
25
+ # server.start # now it starts recieving requests
26
+ # ...
27
+ # server.stop # main thread exits
28
+ #
29
+ # TODO:
30
+ # * Handle blocks
31
+
32
+ class Server
33
+ include IPC
34
+
35
+ attr_reader :uri
36
+
37
+ def initialize(params)
38
+ check_params_provided(params, %i[port proxy])
39
+ port = get_port params
40
+ ip = params[:ip] || 'localhost'
41
+ @uri = "druby://#{ip}:#{port}"
42
+ @proxy = params[:proxy]
43
+ end
44
+
45
+ def start
46
+ DRb.start_service(@uri, @proxy)
47
+ self
48
+ end
49
+
50
+ def stop
51
+ DRb.thread.exit
52
+ self
53
+ end
54
+ end
55
+ end
56
+ end