langrove 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/.rspec +1 -0
  2. data/.rvmrc +62 -0
  3. data/.watchr +27 -0
  4. data/Gemfile +16 -0
  5. data/Gemfile.lock +61 -0
  6. data/Rakefile +24 -0
  7. data/functional/.gitignore +1 -0
  8. data/functional/bin/datagram +6 -0
  9. data/functional/config/.gitignore +0 -0
  10. data/functional/config/boot.rb +64 -0
  11. data/functional/config/daemons.yml +12 -0
  12. data/functional/config/environment.rb +28 -0
  13. data/functional/config/environments/development.rb +0 -0
  14. data/functional/config/environments/production.rb +0 -0
  15. data/functional/config/environments/test.rb +0 -0
  16. data/functional/lib/daemon/datagram.rb +21 -0
  17. data/functional/lib/handler/socket_to_file.rb +36 -0
  18. data/functional/lib/protocol/socket_to_file.rb +55 -0
  19. data/functional/libexec/daemon.rb +68 -0
  20. data/functional/log/.gitignore +3 -0
  21. data/functional/tmp/README +1 -0
  22. data/lib/langrove/_base.rb +26 -0
  23. data/lib/langrove/adaptor/base.rb +3 -0
  24. data/lib/langrove/adaptor/datagram.rb +27 -0
  25. data/lib/langrove/adaptor_base.rb +89 -0
  26. data/lib/langrove/client/base.rb +2 -0
  27. data/lib/langrove/client/datagram.rb +25 -0
  28. data/lib/langrove/client_base.rb +114 -0
  29. data/lib/langrove/daemon/base.rb +2 -0
  30. data/lib/langrove/daemon_base.rb +175 -0
  31. data/lib/langrove/ext/class_loader.rb +148 -0
  32. data/lib/langrove/ext/config_item.rb +34 -0
  33. data/lib/langrove/ext/config_loader.rb +16 -0
  34. data/lib/langrove/ext/fake_logger.rb +8 -0
  35. data/lib/langrove/ext/persistable.rb +103 -0
  36. data/lib/langrove/ext/string.rb +35 -0
  37. data/lib/langrove/ext.rb +7 -0
  38. data/lib/langrove/handler/base.rb +2 -0
  39. data/lib/langrove/handler_base.rb +141 -0
  40. data/lib/langrove/protocol/base.rb +2 -0
  41. data/lib/langrove/protocol/syslog.rb +32 -0
  42. data/lib/langrove/protocol_base.rb +32 -0
  43. data/lib/langrove/version.rb +3 -0
  44. data/lib/langrove.rb +1 -0
  45. data/spec/functional/daemon/datagram_spec.rb +115 -0
  46. data/spec/langrove/adaptor/datagram_spec.rb +6 -0
  47. data/spec/langrove/adaptor_base_spec.rb +48 -0
  48. data/spec/langrove/client/datagram_spec.rb +1 -0
  49. data/spec/langrove/client_base_spec.rb +5 -0
  50. data/spec/langrove/daemon_base_spec.rb +101 -0
  51. data/spec/langrove/ext/class_loader_spec.rb +83 -0
  52. data/spec/langrove/ext/config_item_spec.rb +81 -0
  53. data/spec/langrove/ext/config_loader_spec.rb +5 -0
  54. data/spec/langrove/ext/fake_logger_spec.rb +0 -0
  55. data/spec/langrove/ext/persistable_spec.rb +117 -0
  56. data/spec/langrove/ext/string_spec.rb +16 -0
  57. data/spec/langrove/handler_base_spec.rb +57 -0
  58. data/spec/langrove/protocol/syslog_spec.rb +45 -0
  59. data/spec/langrove/protocol_base_spec.rb +6 -0
  60. data/spec/todo_spec.rb +12 -0
  61. data/tmp/README +2 -0
  62. metadata +200 -0
@@ -0,0 +1,114 @@
1
+ require 'eventmachine'
2
+ require 'langrove'
3
+
4
+ module LanGrove
5
+
6
+ module Client
7
+
8
+ class Base < EventMachine::Connection
9
+
10
+ #
11
+ # Misnomer.
12
+ #
13
+ #
14
+ # This is not the client,
15
+ #
16
+ #
17
+ # It is the server's perspective of the client,
18
+ #
19
+ # These are generally stored in the daemon's
20
+ # handler collection,
21
+ #
22
+ # Which contains this and all similar clients,
23
+ #
24
+ # Each bonded through the daemon's connection
25
+ # adaptor to the socket that couples them to
26
+ # their actual client..
27
+ #
28
+
29
+ #
30
+ # The handler collection specified in config
31
+ # is bound to this attribute after connect.
32
+ #
33
+ attr_accessor :handler
34
+ attr_accessor :protocol
35
+ attr_accessor :config
36
+
37
+ #
38
+ # Data arriving through the Adaptor is passed
39
+ # through the config assigned protocol and
40
+ # then arrives here in whatever format the
41
+ # <Protocol>.decode() method returned.
42
+ #
43
+ #
44
+ def receive( decoded_data )
45
+
46
+ #
47
+ # OVERRIDE
48
+ #
49
+ # - Inbound data arrived at the adaptor
50
+ #
51
+ # - It has passed through the protocol as
52
+ # defined in config.
53
+ #
54
+ # The protocol arranged the data into
55
+ # a Hash of:
56
+ #
57
+ # - key and value pairs
58
+ #
59
+ # - key and more_complicated_thing pairs.
60
+ #
61
+ # - The decoded data was then passed into
62
+ # this function.
63
+ #
64
+ #
65
+
66
+ end
67
+
68
+ def receive_data( data )
69
+
70
+ #
71
+ # Note: The protocol currently lives on the Handler
72
+ #
73
+ # It MIG( ...in progress... )HT move,
74
+ #
75
+ # To a separate instance of the protocol object
76
+ # residing in each Client::ClientType instance
77
+ # in the Handler::Base.client collection.
78
+ #
79
+ # Pending examining the logical barrier of having
80
+ # the Handler route to the Cleint per a key value
81
+ # extracted from the decoded data - as decoded by
82
+ # the client's instance of the protocol not yet
83
+ # accessable within the as yet undeterminable
84
+ # instance in the Handlers collection.
85
+ #
86
+ # Catch22(ish)
87
+ #
88
+ # But that was suitable as a pettern for the
89
+ # datagram server.
90
+ #
91
+ # Perhaps after implementing Adapter::TcpServer
92
+ #
93
+ # And gaining a more knowsome notion of the
94
+ # intricacies of the application layer, esp
95
+ # ACKing and NAKing.
96
+ #
97
+ # Then the possibilities of the Client::ClientType
98
+ # being a decendant of <self> --
99
+ #
100
+ # ie. The EM::Connection as instanciated upon each
101
+ # socket connect.
102
+ #
103
+ # -- will likely resolve.
104
+ #
105
+ #
106
+ receive( @protocol.decode( data ) )
107
+
108
+ end
109
+
110
+ end
111
+
112
+ end
113
+
114
+ end
@@ -0,0 +1,2 @@
1
+ module LanGrove::Daemon; end
2
+ require 'langrove/daemon_base'
@@ -0,0 +1,175 @@
1
+ require 'eventmachine'
2
+
3
+ require 'langrove'
4
+
5
+ module LanGrove::Daemon class Base
6
+
7
+ def listen
8
+
9
+ #
10
+ # Handler - The CollectionOfClients, is passed
11
+ # through the adaptor to be assigned
12
+ # to the attaching Clients.
13
+ #
14
+ @adaptor.listen( @handler, @handler.protocol )
15
+
16
+ end
17
+
18
+ def stop_daemon
19
+
20
+ #
21
+ # Handle stopping daemon
22
+ #
23
+ @logger.info "#{self} received stop_daemon"
24
+ @handler.stop_daemon
25
+
26
+ end
27
+
28
+ def reload_daemon
29
+
30
+ #
31
+ # TODO: Handle reloading daemon properly
32
+ #
33
+ # [ !!complexities abound around reloading config!! ]
34
+ #
35
+
36
+ @logger.info "#{self} received reload_daemon"
37
+ @handler.reload_daemon
38
+
39
+ end
40
+
41
+ def schedule
42
+
43
+ EM::add_periodic_timer( @my_config['periodic'] ) do
44
+
45
+ @logger.info "#{self} Scheduling periodic ...TODO..."
46
+
47
+ #
48
+ # TODO: This can be a lot more usefull than
49
+ # then one ticker to one function call
50
+ #
51
+
52
+ @handler.periodic
53
+
54
+ end if @my_config.has_key? 'periodic'
55
+
56
+ end
57
+
58
+ def run
59
+
60
+ EventMachine::run do
61
+
62
+ #
63
+ # https://github.com/eventmachine/eventmachine/wiki/Protocol-Implementations
64
+ #
65
+
66
+ schedule
67
+
68
+ listen if @server
69
+
70
+ end
71
+
72
+ end
73
+
74
+ def initialize( config_hash, daemon_name, logger )
75
+
76
+ @config = config_hash
77
+ @daemon_name = daemon_name
78
+ @logger = logger
79
+ @server = true
80
+
81
+ #
82
+ # A network connection.
83
+ #
84
+ @adaptor = nil
85
+
86
+ #
87
+ # A handler to direct the traffic.
88
+ #
89
+ @handler = nil
90
+
91
+
92
+ begin
93
+
94
+ @logger.info "Starting daemon: #{@daemon_name}"
95
+
96
+ rescue
97
+
98
+ #
99
+ # you shall not pass
100
+ #
101
+ raise LanGrove::DaemonConfigException.new( "Requires a logger" )
102
+
103
+ end
104
+
105
+ begin
106
+
107
+ @my_config = @config[ :daemons ][ @daemon_name ]
108
+
109
+ @server = @my_config[ :server ] if @my_config.has_key? :server
110
+
111
+ adaptor = @my_config[ :adaptor ][ :connection ]
112
+
113
+ handler = @my_config[ :handler ][ :collection ]
114
+
115
+ rescue
116
+
117
+ error = "Missing config item for daemon: #{@daemon_name}"
118
+
119
+ @logger.error "EXIT: #{error}"
120
+
121
+ raise LanGrove::DaemonConfigException.new(
122
+
123
+ "Missing config item for daemon: #{@daemon_name}"
124
+
125
+ )
126
+
127
+ end
128
+
129
+ #
130
+ # initialize the connection adaptor specified in config
131
+ #
132
+ # daemons:
133
+ # name_of_daemon:
134
+ # adaptor:
135
+ # connection: TcpServer <----------
136
+ # handler:
137
+ # collection: CollectionOfClients
138
+ #
139
+
140
+ @logger.info "Initialize instance: Adaptor::#{adaptor}.new( @my_config[ :adaptor ], logger )"
141
+
142
+ @adaptor = LanGrove::ClassLoader.create( {
143
+
144
+ :module => 'Adaptor',
145
+ :class => adaptor
146
+
147
+ } ).new( @my_config[ :adaptor ], logger )
148
+
149
+
150
+ #
151
+ # bind to the handler collection specified in config
152
+ #
153
+ # daemons:
154
+ # name_of_daemon:
155
+ # adaptor:
156
+ # connection: TcpServer
157
+ # handler:
158
+ # collection: CollectionOfClients <----------
159
+ #
160
+ # CollectionOfClients ---> is the handler passed into listen()
161
+ #
162
+
163
+ @logger.info "Initialize instance: Handler::#{handler}.new( config_hash, '#{daemon_name}', logger )"
164
+
165
+ @handler = LanGrove::ClassLoader.create( {
166
+
167
+ :module => 'Handler',
168
+ :class => handler
169
+
170
+ } ).new( @config, @daemon_name, @logger )
171
+
172
+
173
+ end
174
+
175
+ end; end
@@ -0,0 +1,148 @@
1
+ require 'langrove/ext/string'
2
+ module LanGrove
3
+
4
+ class ClassLoaderException < Exception; end
5
+
6
+ class ClassLoader
7
+
8
+ def self.create( class_config, logger = nil )
9
+
10
+ logger.info( "#{self} is loading Class with #{class_config.inspect}" ) unless logger.nil?
11
+
12
+ #
13
+ # When <config> = {
14
+ #
15
+ # :module => 'ModuleName'
16
+ # :class => 'ClassName'
17
+ #
18
+ # }
19
+ #
20
+ # Then this will return the constantized
21
+ # definition instance of ClassName as
22
+ # loaded from the F1RST found .rb file
23
+ # according to:
24
+ #
25
+ # - lib/module_name/class_name.rb
26
+ # - langrove/module_name/class_name.rb
27
+ #
28
+ # Which then facilitates the following
29
+ # construct:
30
+ #
31
+ # planet = ClassLoader.create(
32
+ #
33
+ # :module => 'Planet',
34
+ # :class => 'Mercury'
35
+ #
36
+ # ).new( *initializer_parameters )
37
+ #
38
+
39
+ raise ClassLoaderException.new(
40
+
41
+ "class_config requires :module"
42
+
43
+ ) unless class_config.has_key? :module
44
+
45
+ raise ClassLoaderException.new(
46
+
47
+ "class_config requires :class"
48
+
49
+ ) unless class_config.has_key? :class
50
+
51
+
52
+ module_name = class_config[:module]
53
+ class_name = class_config[:class]
54
+
55
+
56
+ #
57
+ # SIGNIFICANT DECISION
58
+ #
59
+ # - Late binding to the extent of also calling
60
+ # to require the actual class.rb file could
61
+ # potentially be avoided by
62
+ #
63
+ # << using this layer in the abstraction >>
64
+ #
65
+ # to do the necessary requiring for the specific
66
+ # daemon being spawned.
67
+ #
68
+ # - Obviously there are downsides to eval...
69
+ #
70
+ # - But there are upsides to having this layer
71
+ # totally aliteral - it leaves the window open
72
+ # to the later posibility of collecting the class
73
+ # definition itself from across the network.
74
+ #
75
+ #
76
+ # Which was the cental purpose behind daemons
77
+ # by configuration in the first place.
78
+ #
79
+ # Taking latebinding to a whole new level...
80
+ #
81
+
82
+
83
+ #
84
+ # First try local implementation root
85
+ #
86
+
87
+ exception = nil
88
+
89
+ location = nil
90
+
91
+ begin
92
+
93
+ location = "#{module_name.underscore}/#{class_name.underscore}"
94
+
95
+ eval "require '#{location}'"
96
+
97
+ return Object.const_get( module_name ).const_get( class_name )
98
+
99
+ rescue LoadError => e
100
+
101
+ exception = e
102
+
103
+ logger.error "Missing class definition in lib/#{location}.rb" unless logger.nil?
104
+
105
+ #rescue Exception => e
106
+ #
107
+ # #
108
+ # # Incase of more... (discovery phase)
109
+ # #
110
+ #
111
+
112
+
113
+ end
114
+
115
+ #
116
+ # Fall back to langrove gem lib
117
+ #
118
+
119
+ begin
120
+
121
+ path = File.expand_path('../../', __FILE__)
122
+
123
+ location = "#{path}/#{module_name.underscore}/#{class_name.underscore}"
124
+
125
+ eval "require '#{location}'"
126
+
127
+ return LanGrove.const_get( module_name ).const_get( class_name )
128
+
129
+ rescue Exception => e
130
+
131
+ #
132
+ # Raise from the original exception to
133
+ # inform the local implementation
134
+ # it is missing a module/class .rb
135
+ # "no such file 'module/class"
136
+ #
137
+ # And not confuse the issue by raising
138
+ # "no such file 'langrove/module/class"
139
+ #
140
+ raise ClassLoaderException.new "#{exception.message}.rb"
141
+
142
+ end
143
+
144
+ end
145
+
146
+ end
147
+
148
+ end
@@ -0,0 +1,34 @@
1
+
2
+ module LanGrove class ConfigException < Exception; end; end
3
+
4
+ module LanGrove class ConfigItem
5
+
6
+ def self.get(config_hash, key_array, mandatory = true)
7
+
8
+ # raise ConfigException.new("match")
9
+
10
+ hash = config_hash
11
+
12
+ key_array.each do |key|
13
+
14
+ if mandatory != false then
15
+ # raises a config exception if the item isnt present
16
+ raise ConfigException.new("Missing config item '#{key}'") unless hash.has_key?(key)
17
+
18
+ # raises a config exception if the nested item isnt present NEITHER NOT
19
+ # raise ConfigException.new("Missing config item '#{key}'") if hash[key].empty?
20
+
21
+ hash = hash[key]
22
+
23
+ # raises a config exception if the key isnt present BLANK NEITHER
24
+ raise ConfigException.new("Missing config item '#{key}'") if hash == ""
25
+
26
+ end
27
+
28
+ end
29
+
30
+ return hash
31
+
32
+ end
33
+
34
+ end; end
@@ -0,0 +1,16 @@
1
+ # TODO: spec
2
+
3
+ require 'yaml'
4
+
5
+ module LanGrove class ConfigLoader
6
+
7
+ def self.yaml( file )
8
+
9
+ # no rescue block
10
+ # daemon fails on missing config file
11
+
12
+ YAML.load_file( file )
13
+
14
+ end
15
+
16
+ end; end
@@ -0,0 +1,8 @@
1
+ module LanGrove class FakeLogger
2
+ def initialize( parameter = :noisey )
3
+ @parameter = parameter
4
+ end
5
+ def method_missing( symbol, *args, &block )
6
+ puts "#{symbol}: #{args}" unless @parameter == :silent
7
+ end
8
+ end; end
@@ -0,0 +1,103 @@
1
+ require 'resque'
2
+ require 'yaml'
3
+
4
+ # require 'updated_puppet_state'
5
+
6
+ module LanGrove class Persistable
7
+
8
+ def initialize( notifies = [] )
9
+
10
+ @notifies = notifies
11
+
12
+ @notifies.each do |worker|
13
+
14
+ Object.const_set(
15
+
16
+ worker.camelize, Class.new
17
+
18
+ ) ### ??unless defined already??
19
+
20
+ end
21
+
22
+ end
23
+
24
+ def load_hash( hash_instance, from_file )
25
+ #
26
+ # <hash_instance> as String (name of) variable storing the Hash
27
+ #
28
+
29
+ @logger.debug "loading from: #{from_file}" if @logger
30
+
31
+ #
32
+ # get the instance variable (late bind)
33
+ #
34
+ hash = self.instance_variable_get( hash_instance.to_sym )
35
+ hash ||= {}
36
+
37
+ begin
38
+
39
+ #
40
+ # load file contents, merge into hash and
41
+ # store it at the instance variable
42
+ #
43
+ persisted = YAML.load_file( from_file )
44
+ hash.merge!( persisted ) if persisted.is_a?( Hash )
45
+ self.instance_variable_set( hash_instance.to_sym, hash )
46
+
47
+ #
48
+ # set flag to 'already in storage'
49
+ #
50
+ @stored = true
51
+
52
+ rescue Exception => e
53
+ @logger.error "FAILED loading from #{from_file} #{e}" if @logger
54
+
55
+ end
56
+ end
57
+
58
+
59
+ def store_hash( hash_instance, to_file )
60
+ #
61
+ # <hash_instance> as String (name of) variable storing the Hash
62
+ #
63
+
64
+ if @stored != nil && @stored
65
+
66
+ @logger.debug "storing to: #{to_file} skipped - no change"
67
+ return
68
+
69
+ end
70
+
71
+ @logger.debug "storing to: #{to_file}"
72
+
73
+ #
74
+ # get the instance variable (late bind)
75
+ #
76
+ hash = self.instance_variable_get( hash_instance.to_sym )
77
+
78
+ begin
79
+
80
+ File.open(to_file, 'w') do |f|
81
+
82
+ YAML::dump( hash, f )
83
+ @stored = true
84
+
85
+ end
86
+
87
+ #@notifies.each do |worker|
88
+ #
89
+ # Resque.enqueue(
90
+ #
91
+ # Object.const_get( worker.camelize ), to_file
92
+ #
93
+ # )
94
+ #
95
+ #end
96
+
97
+ rescue Exception => e
98
+ @logger.error "FAILED storing to: #{to_file} #{e}" if @logger
99
+ @stored = false
100
+
101
+ end
102
+ end
103
+ end; end
@@ -0,0 +1,35 @@
1
+ # extend ruby String class
2
+
3
+ class String
4
+ #
5
+ # from the rails inflector
6
+ # (de-camelize)
7
+ #
8
+ # ie. CamelThing becomes camel_thing
9
+ #
10
+ def underscore
11
+ gsub(/::/, '/').
12
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
13
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
14
+ tr("-", "_").
15
+ downcase
16
+ end
17
+
18
+ #
19
+ # unless already defined
20
+ #
21
+ end unless String.method_defined?(:underscore)
22
+
23
+ class String
24
+ #
25
+ # from the rails inflector
26
+ # (camelize)
27
+ #
28
+ # ie. CamelThing becomes camel_thing
29
+ #
30
+ def camelize(first_letter_in_uppercase = :upper)
31
+ s = gsub(/\/(.?)/){|x| "::#{x[-1..-1].upcase unless x == '/'}"}.gsub(/(^|_)(.)/){|x| x[-1..-1].upcase}
32
+ s[0...1] = s[0...1].downcase unless first_letter_in_uppercase == :upper
33
+ s
34
+ end
35
+ end unless String.method_defined?(:camelize)
@@ -0,0 +1,7 @@
1
+ module Langrove; end
2
+ require 'langrove/ext/class_loader'
3
+ require 'langrove/ext/config_item'
4
+ require 'langrove/ext/config_loader'
5
+ require 'langrove/ext/fake_logger'
6
+ require 'langrove/ext/persistable'
7
+ require 'langrove/ext/string'
@@ -0,0 +1,2 @@
1
+ module LanGrove::Handler; end
2
+ require 'langrove/handler_base'