LanGrove 0.0.1

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 (51) hide show
  1. data/.rspec +1 -0
  2. data/.rvmrc +62 -0
  3. data/.watchr +19 -0
  4. data/Gemfile +16 -0
  5. data/Gemfile.lock +61 -0
  6. data/Rakefile +17 -0
  7. data/bin/datagram_example +4 -0
  8. data/config/boot.rb +64 -0
  9. data/config/daemons.yml.tmpl +78 -0
  10. data/config/environment.rb +28 -0
  11. data/config/environments/development.rb +0 -0
  12. data/config/environments/production.rb +0 -0
  13. data/config/environments/test.rb +0 -0
  14. data/lib/adaptor/base.rb +1 -0
  15. data/lib/adaptor/datagram.rb +20 -0
  16. data/lib/adaptor/socket_handler.rb +27 -0
  17. data/lib/adaptor_base.rb +39 -0
  18. data/lib/client/base.rb +1 -0
  19. data/lib/client/puppet_state.rb +74 -0
  20. data/lib/client/radio_state.rb +81 -0
  21. data/lib/client_base.rb +24 -0
  22. data/lib/daemon/base.rb +1 -0
  23. data/lib/daemon/datagram_example.rb +12 -0
  24. data/lib/daemon_base.rb +168 -0
  25. data/lib/ext/config_item.rb +35 -0
  26. data/lib/ext/config_loader.rb +16 -0
  27. data/lib/ext/persistable.rb +103 -0
  28. data/lib/ext/string.rb +35 -0
  29. data/lib/handler/base.rb +1 -0
  30. data/lib/handler/socket_to_file.rb +30 -0
  31. data/lib/handler_base.rb +125 -0
  32. data/lib/jobs/Rakefile +0 -0
  33. data/lib/jobs/jobs.rb +1 -0
  34. data/lib/jobs/updated_puppet_state.rb +17 -0
  35. data/lib/protocol_base.rb +5 -0
  36. data/libexec/daemon.rb +57 -0
  37. data/spec/adaptor/datagram_spec.rb +6 -0
  38. data/spec/adaptor/socket_handler_spec.rb +5 -0
  39. data/spec/adaptor_base_spec.rb +45 -0
  40. data/spec/client_base_spec.rb +5 -0
  41. data/spec/daemon_base_spec.rb +97 -0
  42. data/spec/ext/config_item_spec.rb +77 -0
  43. data/spec/ext/config_loader_spec.rb +5 -0
  44. data/spec/ext/persistable_spec.rb +118 -0
  45. data/spec/ext/string_spec.rb +16 -0
  46. data/spec/functional/datagram_spec.rb +122 -0
  47. data/spec/handler_base_spec.rb +71 -0
  48. data/spec/protocol_base_spec.rb +6 -0
  49. data/spec/todo_spec.rb +13 -0
  50. data/tmp/TMP +0 -0
  51. metadata +97 -0
@@ -0,0 +1,168 @@
1
+ require 'eventmachine'
2
+
3
+ require 'ext/string'
4
+
5
+ class DaemonConfigException < Exception; end
6
+
7
+ module Daemon class Base
8
+
9
+ def listen
10
+
11
+ #
12
+ # Handler, the CollectionOfClients,
13
+ # is passed through the adaptor to
14
+ # be assigned to the attaching sockets.
15
+ #
16
+
17
+ @adaptor.listen( @handler )
18
+
19
+ end
20
+
21
+ def stop_daemon
22
+
23
+ #
24
+ # Handle stopping daemon
25
+ #
26
+ @logger.info "#{self} received stop_daemon"
27
+ @handler.stop_daemon
28
+
29
+ end
30
+
31
+ def reload_daemon
32
+
33
+ #
34
+ # TODO: Handle reloading daemon properly
35
+ #
36
+ # [ !!complexities abound around reloading config!! ]
37
+ #
38
+
39
+ @logger.info "#{self} received reload_daemon"
40
+ @handler.reload_daemon
41
+
42
+ end
43
+
44
+ def schedule
45
+
46
+ EM::add_periodic_timer( @my_config['periodic'] ) do
47
+
48
+ @logger.info "#{self} Scheduling periodic ...TODO..."
49
+
50
+ #
51
+ # TODO: This can be a lot more usefull than
52
+ # then one ticker to one function call
53
+ #
54
+
55
+ @handler.periodic
56
+
57
+ end if @my_config.has_key? 'periodic'
58
+
59
+ end
60
+
61
+ def run
62
+
63
+ EventMachine::run do
64
+
65
+ #
66
+ # https://github.com/eventmachine/eventmachine/wiki/Protocol-Implementations
67
+ #
68
+
69
+ schedule
70
+
71
+ listen if @server
72
+
73
+ end
74
+
75
+ end
76
+
77
+ def initialize( config_hash, daemon_name, logger )
78
+
79
+ @config = config_hash
80
+ @daemon_name = daemon_name
81
+ @logger = logger
82
+ @server = true
83
+
84
+ #
85
+ # A network connection.
86
+ #
87
+ @adaptor = nil
88
+
89
+ #
90
+ # A handler to direct the traffic.
91
+ #
92
+ @handler = nil
93
+
94
+
95
+ begin
96
+
97
+ @logger.info "Starting daemon: #{@daemon_name}"
98
+
99
+ rescue
100
+
101
+ #
102
+ # you shall not pass
103
+ #
104
+ raise DaemonConfigException.new( "Requires a logger" )
105
+
106
+ end
107
+
108
+ begin
109
+
110
+ @my_config = config_hash['daemons'][daemon_name]
111
+
112
+ @server = @my_config['listen'] if @my_config.has_key? 'listen'
113
+
114
+ adaptor = @my_config['adaptor']['connection']
115
+
116
+ handler = @my_config['handler']['collection']
117
+
118
+ rescue
119
+
120
+ error = "Missing config item for daemon: #{daemon_name}"
121
+
122
+ @logger.error "EXIT: #{error}"
123
+
124
+ raise DaemonConfigException.new(
125
+
126
+ "Missing config item for daemon: #{daemon_name}"
127
+
128
+ )
129
+
130
+ end
131
+
132
+ #
133
+ # initialize the connection adaptor specified in config
134
+ #
135
+ # daemons:
136
+ # name_of_daemon:
137
+ # adaptor:
138
+ # connection: TcpServer <----------
139
+ # handler:
140
+ # collection: CollectionOfClients
141
+ #
142
+
143
+ @logger.info "Initialize connection: Adaptor::#{adaptor}.new( @my_config['adaptor'], logger )"
144
+ eval "require 'adaptor/#{adaptor.underscore}'"
145
+ @adaptor = Adaptor.const_get(adaptor).new( @my_config['adaptor'], logger )
146
+
147
+
148
+ #
149
+ # bind to the handler collection specified in config
150
+ #
151
+ # daemons:
152
+ # name_of_daemon:
153
+ # adaptor:
154
+ # connection: TcpServer
155
+ # handler:
156
+ # collection: CollectionOfClients <----------
157
+ #
158
+ # CollectionOfClients ---> is the handler passed into listen()
159
+ #
160
+
161
+ @logger.info "Initialize collection: #{handler}.new( config_hash, daemon_name, logger )"
162
+ eval "require 'handler/#{handler.underscore}'"
163
+ @handler = Handler.const_get(handler).new( config_hash, daemon_name, logger )
164
+
165
+
166
+ end
167
+
168
+ end; end
@@ -0,0 +1,35 @@
1
+ class ConfigException < Exception
2
+
3
+ end
4
+
5
+ class ConfigItem
6
+
7
+ def self.get(config_hash, key_array, mandatory = true)
8
+
9
+ # raise ConfigException.new("match")
10
+
11
+ hash = config_hash
12
+
13
+ key_array.each do |key|
14
+
15
+ if mandatory != false then
16
+ # raises a config exception if the item isnt present
17
+ raise ConfigException.new("Missing config item '#{key}'") unless hash.has_key?(key)
18
+
19
+ # raises a config exception if the nested item isnt present NEITHER NOT
20
+ # raise ConfigException.new("Missing config item '#{key}'") if hash[key].empty?
21
+
22
+ hash = hash[key]
23
+
24
+ # raises a config exception if the key isnt present BLANK NEITHER
25
+ raise ConfigException.new("Missing config item '#{key}'") if hash == ""
26
+
27
+ end
28
+
29
+ end
30
+
31
+ return hash
32
+
33
+ end
34
+
35
+ end
@@ -0,0 +1,16 @@
1
+ # TODO: spec
2
+
3
+ require 'yaml'
4
+
5
+ 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
@@ -0,0 +1,103 @@
1
+ require 'resque'
2
+ require 'yaml'
3
+
4
+ # require 'updated_puppet_state'
5
+
6
+ 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
data/lib/ext/string.rb ADDED
@@ -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 @@
1
+ require 'handler_base'
@@ -0,0 +1,30 @@
1
+ #
2
+ # Mainly to facilitate functional tests
3
+ #
4
+
5
+ require 'handler/base'
6
+
7
+ module Handler
8
+
9
+ class SocketToFile < Base
10
+
11
+ def message( data )
12
+
13
+ @logger.debug( "Received: #{data}" )
14
+
15
+ filename = data.split('|')[0]
16
+ filecontents = data.split('|',2)[1]
17
+
18
+ File.open( filename, 'w' ) do |f|
19
+
20
+ f.write( filecontents )
21
+
22
+ end
23
+
24
+ @logger.debug "Wrote file: #{filename}"
25
+
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,125 @@
1
+ require 'protocol/base'
2
+ require 'ext/string'
3
+
4
+ module Handler class Base
5
+
6
+ #
7
+ # One handler exists in the daemon with
8
+ # the primary purpose of housing through
9
+ # an addressable Hash the set of connected
10
+ # clients,
11
+ #
12
+ # In this:
13
+ #
14
+ attr_writer :clients
15
+
16
+
17
+ def daemon_start
18
+
19
+ #
20
+ # OVERRIDE
21
+ #
22
+ # To prepare the Handler in any necessary
23
+ # ways before the thread is handed over to
24
+ # the socket listener, (or thing),
25
+ #
26
+ # The daemon has just started.
27
+ #
28
+
29
+ end
30
+
31
+ def daemon_stop
32
+
33
+ #
34
+ # OVERRIDE
35
+ #
36
+ # To perfom any necessties before process
37
+ # termination,
38
+ #
39
+ # The daemon has received a signal to stop.
40
+ #
41
+
42
+ end
43
+
44
+ def periodic
45
+
46
+ #
47
+ # TODO: There shall not only be one
48
+ #
49
+ # Changes are pending.
50
+ #
51
+ # OVERRIDE
52
+ #
53
+ # To perform activities at the interval
54
+ # defined in the config.
55
+ #
56
+
57
+ end
58
+
59
+ def initialize( config_hash, daemon_name, logger )
60
+
61
+ @config = config_hash
62
+ @my_config = @config['daemons'][daemon_name]['handler']
63
+ @daemon_name = daemon_name
64
+ @logger = logger
65
+
66
+ protocol = 'Base'
67
+
68
+ if @my_config.has_key? 'protocol' then
69
+
70
+ protocol = @my_config['protocol']
71
+
72
+ end
73
+
74
+ #
75
+ # SIGNIFICANT DECISION
76
+ #
77
+ # - (currently) - There is one >>Instance<< of the protocol
78
+ #
79
+ # - It is an object ( has .new )
80
+ #
81
+ # - All attached socket traffic is directed through it for de/encode
82
+ # -
83
+ # - ?.. ie. multiple TcpClients all routing through the same object..?
84
+ # -
85
+ # - THIS MAY NEED TO CHANGE (thinks & thinks & thinks)
86
+ #
87
+ # - (Thinks) - It may move to the Client::Base so that each attached
88
+ # client is paired with its own instance of the protocol.
89
+ #
90
+ # - For now it wil be instanciated here and binded to each client at
91
+ # connect - to be shared for the decoding. That should be fine for
92
+ # as long as the protocol itself stores no state and is simply a
93
+ # code path (Still, thinks & ...
94
+ #
95
+
96
+ #
97
+ # initialize the protocol specified in config
98
+ #
99
+ # daemons:
100
+ #
101
+ # viking-invasion-early-warning-net_CoreHub_co_uk-D4RK1:
102
+ #
103
+ # adaptor:
104
+ # connection: TriFocalOcularRotisserie
105
+ # lightsource:
106
+ # type: Fire
107
+ # fuel: Peat
108
+ #
109
+ # handler:
110
+ # collection: ScottishCoastalVillages
111
+ # protocol: MedievalLanternMorse <----------
112
+ #
113
+ # Note: This is the application layer protocol
114
+ # and is therefore entirely agnostic of
115
+ # transport and disassociated from the
116
+ # adapter in use.
117
+ #
118
+
119
+ @logger.info "Initialize protocol: Protocol::#{protocol}.new( logger )"
120
+ eval "require 'protocol/#{protocol.underscore}'"
121
+ @protocol = Protocol.const_get(protocol).new( logger )
122
+
123
+ end
124
+
125
+ end; end
data/lib/jobs/Rakefile ADDED
File without changes
data/lib/jobs/jobs.rb ADDED
@@ -0,0 +1 @@
1
+ require 'jobs/updated_puppet_state'
@@ -0,0 +1,17 @@
1
+ class UpdatedPuppetState
2
+
3
+ #
4
+ # Name of the queue these jobs
5
+ # are waiting in.
6
+ #
7
+ @queue = :updated_puppet_state
8
+
9
+ def self.perform( id, parameters )
10
+
11
+ #
12
+ # Process for changes.
13
+ #
14
+
15
+ end
16
+
17
+ end
@@ -0,0 +1,5 @@
1
+ module Protocol class Base
2
+
3
+
4
+
5
+ end; end
data/libexec/daemon.rb ADDED
@@ -0,0 +1,57 @@
1
+ require 'ext/string'
2
+ require 'ext/config_loader'
3
+
4
+ CLASS_NAME = DAEMON_NAME.camelize
5
+ CONFIG_FILE = "#{DAEMON_ROOT}/config/daemons.yml"
6
+ CONFIG_HASH = ConfigLoader.yaml( CONFIG_FILE )
7
+
8
+ daemon = nil
9
+
10
+ DaemonKit::Application.running! do |config|
11
+
12
+ DaemonKit.logger.info "Spawing #{DAEMON_NAME} at pidfile #{config.pid_file}"
13
+
14
+ #
15
+ # setup signal traps
16
+ #
17
+ config.trap( 'TERM', Proc.new {
18
+
19
+ #
20
+ # Handle stop signal from os
21
+ #
22
+ daemon.stop_daemon
23
+
24
+ } )
25
+
26
+ config.trap( 'INT', Proc.new {
27
+
28
+ #
29
+ # Handle ^C from user (eg. developer) running un-daemonized
30
+ #
31
+ daemon.stop_daemon
32
+
33
+ } )
34
+
35
+ #
36
+ # Have not yet '''suitably''' succeeded in getting a sigHUP
37
+ # into a daemon with the monit/daemonkit combo yet....
38
+ #
39
+ # grumbles...
40
+ #
41
+ config.trap( 'HUP', Proc.new {
42
+
43
+ #
44
+ # Handle reload
45
+ #
46
+ daemon.reload_daemon
47
+
48
+ } )
49
+
50
+ #
51
+ # Latebind the daemon class
52
+ #
53
+ require "daemon/#{DAEMON_NAME}"
54
+ daemon = Object.const_get( CLASS_NAME ).new( CONFIG_HASH, DAEMON_NAME, DaemonKit.logger )
55
+ daemon.run
56
+
57
+ end
@@ -0,0 +1,6 @@
1
+ require 'adaptor/datagram'
2
+
3
+ describe Adaptor::Datagram do
4
+
5
+
6
+ end
@@ -0,0 +1,5 @@
1
+ require 'adaptor/socket_handler'
2
+
3
+ describe Adaptor::SocketHandler do
4
+
5
+ end
@@ -0,0 +1,45 @@
1
+ require 'adaptor/base'
2
+
3
+ require 'ext/fake_logger'
4
+
5
+ describe Adaptor::Base do
6
+
7
+ before :each do
8
+
9
+ @logger = FakeLogger.new
10
+
11
+ @extendhandler = 'ExtendedHandler'
12
+
13
+ @config = {
14
+
15
+ 'iface' => '111.111.111.111',
16
+
17
+ 'port' => 11111,
18
+
19
+ 'connector' => @extendhandler
20
+
21
+ }
22
+
23
+ end
24
+
25
+ pending 'may need to override default connection handler' do
26
+
27
+ Adaptor.const_set( @extendhandler, Class.new )
28
+
29
+ subject = Adaptor::Base.new( @config, @logger )
30
+ connector = subject.instance_variable_get( :@handler )
31
+
32
+ connector.should be_a( Adaptor.const_get( @extendhandler ) )
33
+
34
+ end
35
+
36
+ it 'assigns defaults' do
37
+
38
+ subject = Adaptor::Base.new( {}, @logger )
39
+ connector = subject.instance_variable_get( :@connector )
40
+
41
+ connector.should == Adaptor::SocketHandler
42
+
43
+ end
44
+
45
+ end
@@ -0,0 +1,5 @@
1
+ require 'client_base'
2
+
3
+ describe Client::Base do
4
+
5
+ end