evesync 1.0.2

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