gom-script 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ gom-script.gemspec
7
+ tmp
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 art+com AG/dirk luesebrink
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,31 @@
1
+ # GOM Script
2
+
3
+ Easy access over the wire to a remote GOM node. This gem includes a GNP
4
+ callback server, support for automatic observer refreshments and command line
5
+ tools for reading, writing and observing GOM entries.
6
+
7
+
8
+ ## Install
9
+
10
+ use the jewler tasks:
11
+
12
+ $ rake gemspec build install
13
+
14
+ ## Dependencies
15
+
16
+ ## credits
17
+
18
+ ## Note on Patches/Pull Requests
19
+
20
+ * Fork the project.
21
+ * Make your feature addition or bug fix.
22
+ * Add tests for it. This is important so I don't break it in a
23
+ future version unintentionally.
24
+ * Commit, do not mess with rakefile, version, or history.
25
+ (if you want to have your own version, that is fine but
26
+ bump version in a commit by itself I can ignore when I pull)
27
+ * Send me a pull request. Bonus points for topic branches.
28
+
29
+ ## Copyright
30
+
31
+ Copyright (c) 2009 art+com AG/dirk luesebrink. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,67 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ # gem is a Gem::Specification... see
8
+ # http://www.rubygems.org/read/chapter/20 for additional settings
9
+ #
10
+ gem.name = "gom-script"
11
+ gem.summary = %Q{connecting scripts and daemons with a remote GOM instance}
12
+ gem.description = %Q{
13
+ GOM is a schema-less object database in ruby with Resource Oriented API,
14
+ server-side javascript, distributed HTTP notifications and some more.
15
+ This gom-script script simplifies coding of clients and daemon which like
16
+ to listen on state change event in the GOM.
17
+ }.gsub /\n\n/, ''
18
+ gem.email = "dirk.luesebrink@gmail.com"
19
+ gem.homepage = "http://github.com/crux/gom-script"
20
+ gem.authors = ["art+com/dirk luesebrink"]
21
+ gem.add_runtime_dependency "applix", ">=0.2.1"
22
+ gem.add_runtime_dependency "rack"
23
+ gem.add_runtime_dependency "gom-core"
24
+
25
+ gem.add_development_dependency "rspec"
26
+ gem.add_development_dependency "fakeweb", ">=1.2.7"
27
+ end
28
+ rescue LoadError
29
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
30
+ end
31
+
32
+ require 'spec/rake/spectask'
33
+ desc "Run all specs in spec directory"
34
+ Spec::Rake::SpecTask.new(:spec) do |t|
35
+ t.spec_files = FileList['spec/**/*_spec.rb']
36
+ t.spec_opts = %w(-c)
37
+ end
38
+
39
+ begin
40
+ require 'rcov/rcovtask'
41
+ Rcov::RcovTask.new do |test|
42
+ test.libs << 'test'
43
+ test.pattern = 'spec/**/*_spec.rb'
44
+ test.verbose = true
45
+ end
46
+ rescue LoadError
47
+ task :rcov do
48
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
49
+ end
50
+ end
51
+
52
+ require 'rake/rdoctask'
53
+ Rake::RDocTask.new do |rdoc|
54
+ if File.exist?('VERSION')
55
+ version = File.read('VERSION')
56
+ else
57
+ version = ""
58
+ end
59
+
60
+ rdoc.rdoc_dir = 'rdoc'
61
+ rdoc.title = "GOM Remote - #{version}"
62
+ rdoc.rdoc_files.include('README*')
63
+ rdoc.rdoc_files.include('lib/**/*.rb')
64
+ end
65
+
66
+ task :test => :check_dependencies
67
+ task :default => :spec
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.2
data/lib/gom-script.rb ADDED
@@ -0,0 +1 @@
1
+ require 'gom/remote'
data/lib/gom/remote.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require 'gom-core'
3
+ require 'gom/remote/connection'
4
+ require 'gom/remote/entry'
5
+ require 'gom/remote/subscription'
6
+ #require 'gom/remote/callback_server'
7
+ require 'gom/remote/http_server'
8
+ require 'gom/remote/daemon'
@@ -0,0 +1,194 @@
1
+ require 'net/http'
2
+ require 'open-uri'
3
+ require 'json'
4
+
5
+ module Gom
6
+ module Remote
7
+
8
+ class << self; attr_accessor :connection; end
9
+
10
+ class Connection
11
+
12
+ attr_reader :target_url, :initial_path, :callback_server
13
+
14
+ class << self
15
+ # take apart the URL into GOM and node path part
16
+ def split_url url
17
+ u = URI.parse url
18
+ re = %r|#{u.scheme}://#{u.host}(:#{u.port})?|
19
+ server = (re.match url).to_s
20
+ path = (url.sub server, '').sub(/\/$/, '')
21
+ [server, path]
22
+ end
23
+
24
+ def init url, callback_port = nil
25
+ connection = (self.new url, callback_port)
26
+ [connection, connection.initial_path]
27
+ end
28
+ end
29
+
30
+ # url: initial GOM url, path or attribute. The remote GOM server
31
+ # address gets extracted from this and, unless nil, the given block
32
+ # will be called with the remaining GOM path, aka:
33
+ #
34
+ # url == http://gom:1234/foo/bar:attribute
35
+ #
36
+ # will use 'http://gom:1234' as GOM server and call the block with
37
+ # '/foo/bar:attribute' as path argument.
38
+ #
39
+ def initialize url, callback_port = nil
40
+ @target_url, @initial_path = (Connection.split_url url)
41
+ #Gom::Remote.connection and (raise "connection already open")
42
+ Gom::Remote.connection = self
43
+
44
+ @subscriptions = []
45
+ @callback_server = init_callback_server callback_port
46
+ end
47
+
48
+ def write path, value
49
+ if value.kind_of? Hash
50
+ write_node path, attributes
51
+ else
52
+ write_attribute path, value
53
+ end
54
+ end
55
+
56
+ def write_attribute path, value
57
+ # TODO: Primitive#encode returns to_s for all unknow types. exception
58
+ # would be correct.
59
+ txt, type = (Gom::Core::Primitive.encode value)
60
+ params = { "attribute" => txt, "type" => type }
61
+ url = "#{@target_url}#{path}"
62
+ http_put(url, params)
63
+ end
64
+
65
+ def write_node path, attributes
66
+ raise "not yet implemented"
67
+ end
68
+
69
+ def read path
70
+ url = "#{@target_url}#{path}"
71
+ open(url).read
72
+ rescue Timeout::Error => e
73
+ raise "connection timeout: #{url}"
74
+ rescue OpenURI::HTTPError => e
75
+ case code = e.to_s.to_i rescue 0
76
+ when 404
77
+ raise NameError, "undefined: #{path}"
78
+ else
79
+ puts " ## gom connection error: #{url} -- #{e}"
80
+ throw e
81
+ end
82
+ rescue => e
83
+ puts " ## read error: #{url} -- #{e}"
84
+ throw e
85
+ end
86
+
87
+ # update subscription observers. GNP callbacks will look like:
88
+ #
89
+ # http://<callback server>:<callback port>/gnp;<subscription name>;<subscription path>
90
+ #
91
+ def refresh
92
+ puts " -- refresh subscriptions(#{@subscriptions.size}):"
93
+
94
+ run_callback_server # call it once to make sure it runs
95
+
96
+ @subscriptions.each do |sub|
97
+ puts " - #{sub.name}"
98
+ params = { "attributes[accept]" => 'application/json' }
99
+
100
+ query = "/gnp;#{sub.name};#{sub.entry_uri}"
101
+ params["attributes[callback_url]"] = "#{callback_server.base_url}#{query}"
102
+
103
+ [:operations, :uri_regexp, :condition_script].each do |key|
104
+ (v = sub.send key) and params["attributes[#{key}]"] = v
105
+ end
106
+
107
+ url = "#{@target_url}#{sub.uri}"
108
+ http_put(url, params) # {|req| req.content_type = 'application/json'}
109
+ end
110
+ end
111
+
112
+ def subscribe sub
113
+ @subscriptions.delete sub # every sub only once!
114
+ @subscriptions.push sub
115
+ end
116
+
117
+ private
118
+
119
+ def init_callback_server port
120
+ txt = (read "/gom/config/connection.txt")
121
+ unless m = (txt.match /^client_ip:\s*(\d+\.\d+\.\d+\.\d+)/)
122
+ raise "/gom/config/connection: No Client IP? '#{txt}'"
123
+ end
124
+ # this is the IP by which i am seen from the GOM
125
+ ip = m[1]
126
+
127
+ http = (HttpServer.new :host => ip, :port => port)
128
+ http.mount "^/gnp;", lambda {|*args| gnp_handler *args}
129
+
130
+ http
131
+ end
132
+
133
+ def run_callback_server
134
+ callback_server.start unless callback_server.running?
135
+ end
136
+
137
+ #def gnp_callback name, entry_uri, req
138
+ def gnp_handler request_uri, env
139
+ op, name, entry_uri = (request_uri.to_s.split /;/)
140
+ unless sub = @subscriptions.find { |s| s.name == name }
141
+ raise "no such subscription: #{name} :: #{entry_uri}"#\n#{@subscriptions.inspect}"
142
+ end
143
+
144
+ begin
145
+ req = Rack::Request.new(env)
146
+ op, payload = (decode_gnp_body req.body.read)
147
+ (sub.callback.call op, payload)
148
+ rescue Exception => e
149
+ callstack = "#{e.backtrace.join "\n "}"
150
+ puts " ## Subscription::callback - #{e}\n -> #{callstack}"
151
+ end
152
+
153
+ # HTTP OK keeps the subscription alive, even in case of handler errors
154
+ [200, {"Content-Type"=>"text/plain"}, ["keep going dude!"]]
155
+ end
156
+
157
+ def decode_gnp_body txt
158
+ debugger if (defined? debugger)
159
+ json = (JSON.parse txt)
160
+ puts " -- json GNP: #{json.inspect}"
161
+
162
+ payload = nil
163
+ op = %w{update delete create}.find { |op| json[op] }
164
+ %w{attribute node}.find { |t| payload = json[op][t] }
165
+ #puts "payload: #{payload.inspect}"
166
+ [op, payload]
167
+
168
+ #op = (json.include? 'update') ? :udpate : nil
169
+ #op ||= (json.include? 'delete') ? :delete : nil
170
+ #op ||= (json.include? 'create') ? :create : nil
171
+ #op or (raise "unknown GNP op: #{txt}")
172
+
173
+ #payload = json[op.to_s]
174
+ #[op, (payload['attribute'] || payload['node'])]
175
+ end
176
+
177
+ # incapsulates the underlying net access
178
+ def http_put(url, params, &request_modifier)
179
+ uri = URI.parse url
180
+ req = Net::HTTP::Put.new uri.path
181
+ req.set_form_data(params)
182
+ request_modifier && (request_modifier.call req)
183
+
184
+ session = (Net::HTTP.new uri.host, uri.port)
185
+ case res = session.start { |http| http.request req }
186
+ when Net::HTTPSuccess, Net::HTTPRedirection
187
+ # OK
188
+ else
189
+ res.error!
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,78 @@
1
+ require 'timeout'
2
+ module Gom
3
+ module Remote
4
+ class Daemon
5
+
6
+ include ::Timeout
7
+
8
+ Defaults = {
9
+ :actor_dt => 60,
10
+ :sensor_dt => 1,
11
+ :callback_port => 8815,
12
+ :stealth => false,
13
+ }
14
+
15
+ include OAttr
16
+ oattr :actor_dt, :sensor_dt, :stealth
17
+
18
+ # service_path is derived from the service_url (server part stripped)
19
+ attr :service_path
20
+
21
+ def initialize service_url, options = {}, &blk
22
+ @options = (Defaults.merge options)
23
+ callback_port = @options[:callback_port]
24
+ @gom, @service_path = (Connection.init service_url, callback_port)
25
+ (blk.call self, @service_path) unless blk.nil?
26
+ end
27
+
28
+ =begin
29
+ def open_nagios_port jjjjj
30
+ @gom.callback_server.mount(%r{^/nagios}, lambda do |*args|
31
+ uri, env = *args
32
+ req = Rack::Request.new(env)
33
+ envmap = (env.map { |k,v| "#{k}: #{v}" }.join "\n")
34
+ body = ["OK -- #{env['REQUEST_URI']}\n---\n#{envmap}"]
35
+ [200, {"Content-Type"=>"text/plain"}, body]
36
+ end)
37
+ end
38
+ hs = HttpServer.new o
39
+ hs.mount "^/gnp;", lambda {|*args| gnp_handler *args}
40
+ =end
41
+ def sensor_loop interval = sensor_dt, &tic
42
+ puts " -- running gom-script sensor loop.."
43
+ forever(interval) { tic.call self }
44
+ end
45
+
46
+ def actor_loop interval = actor_dt, &tic
47
+ puts " -- running gom-script actor loop.."
48
+ forever(interval) do
49
+ @gom.refresh
50
+ tic && (tic.call self) || :continue
51
+ end
52
+ end
53
+
54
+ def forever interval = 0, &callback
55
+ loop do
56
+ begin
57
+ rc = callback.call
58
+ rescue Exception => e
59
+ puts " ## <#{e.class}> #{self} - #{e}\n -> #{e.backtrace.join "\n "}"
60
+ ensure
61
+ break if rc == :stop
62
+ sleep interval
63
+ end
64
+ end
65
+ end
66
+
67
+ # push own ip and monitoring port back to GOM node
68
+ def check_in
69
+ if stealth
70
+ puts " -- no GOM check-in in stealth mode"
71
+ else
72
+ @gom.write "#{service_path}:daemon_ip", @gom.callback_server.host
73
+ #@gom.write "#{service_path}:nagios_port", nagios_port
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,27 @@
1
+ module Gom
2
+ module Remote
3
+ class Entry
4
+ include Gom::Remote
5
+ def gom
6
+ Gom::Remote.connection
7
+ end
8
+ # @deprecated?
9
+ def connection
10
+ Gom::Remote.connection
11
+ end
12
+
13
+ def gnode path
14
+ json = (connection.read "#{path}.json")
15
+ (JSON.parse json)["node"]["entries"].select do |entry|
16
+ # 1. select attribute entries
17
+ entry.has_key? "attribute"
18
+ end.inject({}) do |h, a|
19
+ # 2. make it a key, value list
20
+ h[a["attribute"]["name"].to_sym] = a["attribute"]["value"]
21
+ h
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
@@ -0,0 +1,124 @@
1
+ require 'rack'
2
+ require 'rack/handler/mongrel'
3
+ require 'thread'
4
+ require 'applix/oattr'
5
+
6
+ module Gom
7
+ module Remote
8
+ class HttpServer
9
+
10
+ Defaults = {
11
+ :host => "0.0.0.0", :port => 25191
12
+ }
13
+
14
+ include OAttr
15
+ oattr :host, :port
16
+
17
+ def initialize options = {}
18
+ @options = (Defaults.merge options)
19
+ @mounts = {}
20
+ @mounts_access = Mutex.new
21
+ end
22
+
23
+ def base_url
24
+ p = (port == 80 ? '' : ":#{port}")
25
+ "http://#{host}#{p}"
26
+ end
27
+
28
+ def running?
29
+ !@server.nil?
30
+ end
31
+
32
+ def mount pattern, handler
33
+ @mounts_access.synchronize { @mounts.update pattern => handler }
34
+ self
35
+ end
36
+
37
+ def unmount pattern
38
+ @mounts_access.synchronize { @mounts.delete pattern }
39
+ self
40
+ end
41
+
42
+ def start
43
+ @server.nil? or (raise "already running!")
44
+ @thread = Thread.new { start_mongrel_server }
45
+ sleep 0.1 # give thread time for start-up
46
+ @thread
47
+ end
48
+
49
+ def stop
50
+ @server.nil? and (raise "not running!")
51
+ puts ' -- stopping callback server..'
52
+ @server.stop
53
+ @server = nil
54
+ puts ' down.'
55
+ sleep 2 # sleep added as a precaution
56
+ puts ' -- killing server thread now...'
57
+ @thread.kill
58
+ @thread = nil
59
+ puts ' and gone.'
60
+ self
61
+ end
62
+
63
+ # take the URI on walk it through the list of mounts and return the one
64
+ # with the longest match or nil. In case of a match the corresponding
65
+ # handler is returned, nil otherwise.
66
+ def match uri
67
+ targets = []
68
+ @mounts_access.synchronize do
69
+ targets = @mounts.map do |re, app|
70
+ [app, (uri.path.match re).to_s]
71
+ end
72
+ end
73
+
74
+ # sort for longest match. And target might be nil already for an empty
75
+ # targets list, which is ok as we return nil in that case.
76
+ target = targets.sort!{|a,b| a[1].length <=> b[1].length}.last
77
+ func, pattern = target
78
+ (pattern.nil? || pattern.empty?) ? nil : func
79
+ end
80
+
81
+ private
82
+
83
+ # dispatching a request URI from env['REQUEST_URI'] which look
84
+ # somethings like:
85
+ #
86
+ # http://172.20.2.9:2719/gnp;enttec-dmx;/services/enttec-dmx-usb-pro/values
87
+ #
88
+ def dispatch env
89
+ #req = Rack::Request.new(env)
90
+ uri = (URI.parse env['REQUEST_URI'])
91
+ if func = (match uri)
92
+ func.call uri, env
93
+ else
94
+ puts " !! no handler for: #{req.fullpath} -- #{@mounts.keys.inspect}"
95
+ [404, {"Content-Type"=>"text/plain"}, ["Not Found"]]
96
+ end
97
+ rescue => e
98
+ puts " ## #{e}\n -> #{e.backtrace.join "\n "}"
99
+ [500, {"Content-Type"=>"text/plain"}, [e]]
100
+ end
101
+
102
+ # as i absolutly dislike capitalized options i use lowercase options
103
+ # throughout and only convert them just before i pass them the the
104
+ # mongrel server. Nothing to be proud of, but i definitly don't want to
105
+ # write --Port on the command line...
106
+ def mongrel_opts
107
+ @options.merge :Host => @options[:host], :Port => @options[:port]
108
+ end
109
+
110
+ def start_mongrel_server
111
+ puts " -- starting http server: #{@options.inspect}"
112
+ @server.nil? or (raise "already running!")
113
+ f = Proc.new {|env| dispatch env}
114
+ Rack::Handler::Mongrel.run(f, mongrel_opts) do |server|
115
+ @server = server
116
+ puts " mongrel up: #{@options.inspect}"
117
+ end
118
+ rescue Exception => e
119
+ puts " ## oops: #{e}"
120
+ puts @options.inspect
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,38 @@
1
+ module Gom
2
+ module Remote
3
+ class Subscription
4
+
5
+ Defaults = {
6
+ :name => nil,
7
+ :operations => [:update],
8
+ :condition_script => nil,
9
+ :uri_regexp => nil,
10
+ :callback => nil,
11
+ }
12
+
13
+ attr_reader :entry_uri, :uri, :callback
14
+ attr_accessor :callback
15
+ attr_reader :name, :operations, :condition_script, :uri_regexp
16
+
17
+ def to_s
18
+ "#{self.class}: #{@options.inject}"
19
+ end
20
+
21
+ # hint: supplying a recognizable name helps with distributed gom
22
+ # operations
23
+ #
24
+ def initialize entry_uri, options = {}, &blk
25
+ @name = options[:name] || "0x#{object_id}"
26
+ # URI for the observer node
27
+ @uri = "/gom/observer#{entry_uri.sub ':', '/'}/.#{@name}"
28
+
29
+ @options = Defaults.merge options
30
+ @entry_uri = entry_uri
31
+ @callback = options[:callback] || blk;
32
+ @operations = (@options[:operations] || []).join ', '
33
+ @uri_regexp = (re = @options[:uri_regexp]) && (Regexp.new re) || nil
34
+ @condition_script = @options[:condition_script]
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,109 @@
1
+ require File.dirname(__FILE__)+'/../../spec_helper'
2
+
3
+ describe Gom::Remote::Connection do
4
+
5
+ describe "initialization" do
6
+ it "should split a GOM node url" do
7
+ gom, path = (Gom::Remote::Connection.split_url 'http://gom:345/dmx/node')
8
+ gom.should == 'http://gom:345'
9
+ path.should == '/dmx/node'
10
+ end
11
+ it "should strip last slash from the node" do
12
+ gom, path = (Gom::Remote::Connection.split_url 'http://xxx:4321/a/b/c/')
13
+ gom.should == 'http://xxx:4321'
14
+ path.should == '/a/b/c'
15
+ gom, path = (Gom::Remote::Connection.split_url 'http://xxx:4321/a/b:c/')
16
+ gom.should == 'http://xxx:4321'
17
+ path.should == '/a/b:c'
18
+ end
19
+ it "should split an attribute URL" do
20
+ gom, path = (Gom::Remote::Connection.split_url 'http://xxx/a:x')
21
+ gom.should == 'http://xxx'
22
+ path.should == '/a:x'
23
+ end
24
+ it "should split a GOM node url on init" do
25
+ gom, path = (Gom::Remote::Connection.init 'http://gom:345/dmx/node')
26
+ gom.target_url.should == 'http://gom:345'
27
+ path.should == '/dmx/node'
28
+ end
29
+ end
30
+
31
+ describe "with a connection it" do
32
+ before :each do
33
+ @gom, path = (Gom::Remote::Connection.init 'http://gom:345/dmx/node')
34
+ end
35
+
36
+ it "should fetch the callback_ip from remote" do
37
+ @gom.callback_server.host.should == "10.0.0.23"
38
+ end
39
+
40
+ it "should put attribute values to remote" do
41
+ @gom.should_receive(:http_put).
42
+ with("http://gom:345/some/node:attr", { "attribute" => "abc", "type" => :string })
43
+ @gom.write '/some/node:attr', "abc"
44
+ end
45
+ end
46
+
47
+ describe "with subscriptions" do
48
+ before :each do
49
+ @gom, path = (Gom::Remote::Connection.init 'http://localhost:3000')
50
+ @gom.stub!(:run_callback_server).and_return(true)
51
+ end
52
+
53
+ #it "should have no subscriptions on init" do
54
+ # @gom.subscriptions.should == []
55
+ #end
56
+
57
+ it "should subscribe operations whitelist" do
58
+ s = (Gom::Remote::Subscription.new '/node', :operations => [:delete, :create])
59
+ @gom.should_receive(:http_put).with(
60
+ "http://localhost:3000/gom/observer/node/.#{s.name}",
61
+ hash_including("attributes[operations]" => 'delete, create')
62
+ )
63
+ @gom.subscribe s
64
+ @gom.refresh
65
+ end
66
+
67
+ it "should have an uri regexp" do
68
+ s = (Gom::Remote::Subscription.new '/node', :uri_regexp => /foo/)
69
+ @gom.should_receive(:http_put).with(
70
+ "http://localhost:3000/gom/observer/node/.#{s.name}",
71
+ hash_including("attributes[uri_regexp]" => /foo/)
72
+ )
73
+ @gom.subscribe s
74
+ @gom.refresh
75
+ end
76
+
77
+ it "should have accept=application/json param" do
78
+ s = (Gom::Remote::Subscription.new '/node')
79
+ @gom.should_receive(:http_put).with(
80
+ "http://localhost:3000/gom/observer/node/.#{s.name}",
81
+ hash_including("attributes[accept]" => 'application/json')
82
+ )
83
+ @gom.subscribe s
84
+ @gom.refresh
85
+ end
86
+
87
+ it "should put observer to gom on refresh" do
88
+ s = (Gom::Remote::Subscription.new '/node/values')
89
+ @gom.should_receive(:http_put).with(
90
+ "http://localhost:3000/gom/observer/node/values/.#{s.name}",
91
+ #hash_including("attributes[callback_url]" => "http://1.2.3.4:2179/gnp;#{s.name};/node/values")
92
+ hash_including("attributes[callback_url]" => anything)
93
+ )
94
+ @gom.subscribe s
95
+ @gom.refresh
96
+ end
97
+
98
+ it "should observe an attribute entry" do
99
+ s = (Gom::Remote::Subscription.new '/node:attribute')
100
+ @gom.should_receive(:http_put).with(
101
+ "http://localhost:3000/gom/observer/node/attribute/.#{s.name}",
102
+ #hash_including("attributes[callback_url]" => "http://1.2.3.4:2179/gnp;#{s.name};/node:attribute")
103
+ hash_including("attributes[callback_url]" => anything)
104
+ )
105
+ @gom.subscribe s
106
+ @gom.refresh
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,57 @@
1
+ require File.dirname(__FILE__)+'/../../spec_helper'
2
+
3
+ include Gom::Remote
4
+
5
+ describe Gom::Remote::Daemon do
6
+
7
+ describe "with a plain vanilla daemon" do
8
+ before :each do
9
+ @daemon = Daemon.new 'http://gom:345/gom-script/test'
10
+ end
11
+ it "should have a default actor_dt" do
12
+ @daemon.actor_dt.should == Daemon::Defaults[:actor_dt]
13
+ end
14
+ it "should have stealth mode off by default" do
15
+ @daemon.stealth.should == false
16
+ end
17
+ it "should have a default sensor_dt" do
18
+ @daemon.sensor_dt.should == Daemon::Defaults[:sensor_dt]
19
+ end
20
+
21
+ it "should parse the service_path from the service_url" do
22
+ @daemon.service_path.should == '/gom-script/test'
23
+ end
24
+ it "should terminate sensor loop on :stop" do
25
+ count = 0
26
+ timeout(1) do
27
+ @daemon.sensor_loop(0.1) { count += 1; :stop if count == 3 }
28
+ end
29
+ count.should == 3
30
+ end
31
+ it "should terminate actor loop on :stop" do
32
+ Gom::Remote.connection.should_receive(:refresh)
33
+ timeout(1) { @daemon.actor_loop { :stop } }
34
+ end
35
+
36
+ it "should check in with its client ip" do
37
+ Gom::Remote.connection.should_receive(:write).
38
+ with("/gom-script/test:daemon_ip", "10.0.0.23")
39
+ @daemon.check_in
40
+ end
41
+ end
42
+
43
+ describe "initialization" do
44
+ it "should find the class" do
45
+ Daemon.should_not == nil
46
+ end
47
+
48
+ it "should init the connection" do
49
+ Gom::Remote.connection.should == nil
50
+ Daemon.new 'http://gom:345/gom-script/test'
51
+ (c = Gom::Remote.connection).should_not == nil
52
+ c.target_url.should == 'http://gom:345'
53
+ c.initial_path.should == '/gom-script/test'
54
+ c.callback_server.port.should == Daemon::Defaults[:callback_port]
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,106 @@
1
+ require File.dirname(__FILE__)+'/../../spec_helper'
2
+
3
+ describe Gom::Remote::HttpServer do
4
+
5
+ describe "initialization" do
6
+ it "should not be running on creation" do
7
+ server = Gom::Remote::HttpServer.new
8
+ server.running?.should == false
9
+ end
10
+ end
11
+
12
+ it "should overwrite host option" do
13
+ server = Gom::Remote::HttpServer.new :host => "1.2.3.4"
14
+ server.host.should == "1.2.3.4"
15
+ end
16
+
17
+ it "should overwrite port option" do
18
+ server = Gom::Remote::HttpServer.new :port => 9151
19
+ server.port.should == 9151
20
+ end
21
+
22
+ it "should have a base url" do
23
+ h, p = (Gom::Remote::HttpServer::Defaults.values_at :host, :port)
24
+
25
+ server = Gom::Remote::HttpServer.new
26
+ server.base_url.should == "http://#{h}:#{p}"
27
+ server = Gom::Remote::HttpServer.new :port => 9151
28
+ server.base_url.should == "http://#{h}:9151"
29
+ server = Gom::Remote::HttpServer.new :host => '127.0.0.1'
30
+ server.base_url.should == "http://127.0.0.1:#{p}"
31
+ server = Gom::Remote::HttpServer.new :port => 1234, :host => '10.1.1.10'
32
+ server.base_url.should == "http://10.1.1.10:1234"
33
+ end
34
+
35
+ describe "with a server" do
36
+ before :each do
37
+ @server = Gom::Remote::HttpServer.new
38
+ end
39
+
40
+ it "should match empty mounts to nil" do
41
+ @server.send(:match, (URI.parse '/foo/aa;bb;cc?p1=12&p2=oo')).should == nil
42
+ end
43
+
44
+ it "should not missmatch" do
45
+ @server.mount '^/a', lambda { }
46
+ @server.mount '^/a/b', lambda { }
47
+ @server.mount '^/a/b/c', lambda { }
48
+ @server.match(URI.parse '/x/a/b/c').should == nil
49
+ @server.match(URI.parse '/x/a/b').should == nil
50
+ @server.match(URI.parse '/x/a').should == nil
51
+ @server.match(URI.parse 'a').should == nil
52
+ end
53
+
54
+ it "should unmount" do
55
+ @server.mount '/a/b/c', (l = lambda { "block 6" })
56
+ @server.match(URI.parse '/a/b/c/d').should == l
57
+ @server.unmount '/a/b/c'
58
+ @server.match(URI.parse '/a/b/c/d').should == nil
59
+ end
60
+
61
+ it "should match with regexp as well" do
62
+ @server.mount %r{/a}, (l1 = lambda { "block 1" })
63
+ @server.mount %r{/a/b}, (l2 = lambda { "block 2" })
64
+ @server.mount %r{/a/b/c}, (l3 = lambda { "block 3" })
65
+ @server.match(URI.parse '/a/b').should == l2
66
+ @server.match(URI.parse '/a/b/c').should == l3
67
+ @server.match(URI.parse '/a/b/x').should == l2
68
+ @server.match(URI.parse '/a/b/c/d').should == l3
69
+ end
70
+
71
+ it "should prefer longer matches" do
72
+ @server.mount '/a', (l1 = lambda { "block 1" })
73
+ @server.mount '/a/b', (l2 = lambda { "block 2" })
74
+ @server.mount '/a/b/c', (l3 = lambda { "block 3" })
75
+ @server.match(URI.parse '/a/b').should == l2
76
+ @server.match(URI.parse '/a/b/c').should == l3
77
+ @server.match(URI.parse '/a/b/x').should == l2
78
+ @server.match(URI.parse '/a/b/c/d').should == l3
79
+ end
80
+
81
+ it "should match simple strings" do
82
+ @server.mount "/foo", (l = lambda { puts "needs some code here" })
83
+ @server.match(URI.parse '/foo/aa;bb;cc?p1=12&p2=oo').should == l
84
+ end
85
+
86
+ it "should have default mongrel options" do
87
+ @server.port.should == Gom::Remote::HttpServer::Defaults[:port]
88
+ @server.host.should == Gom::Remote::HttpServer::Defaults[:host]
89
+ end
90
+
91
+ #it "should dispatch a nagios callback" do
92
+ # @cs.should_not_receive(:gnp_dispatcher)
93
+ # response = @cs.send(:dispatch_request_uri, "/nagios;foo;bar", {})
94
+ # response.should == [200, {"Content-Type"=>"text/plain"}, ["OK"]]
95
+ #end
96
+
97
+ it "should start and stop" do
98
+ @server.start.class.should == Thread
99
+ #sleep 1
100
+ @server.running?.should == true
101
+ @server.stop.should == @server
102
+ sleep 1
103
+ @server.running?.should == false
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,48 @@
1
+ require File.dirname(__FILE__)+'/../../spec_helper'
2
+
3
+ describe Gom::Remote::Subscription do
4
+
5
+ describe "when created with default options" do
6
+ before :each do
7
+ @sub = (Gom::Remote::Subscription.new '/dmx/node/values')
8
+ end
9
+ it "should have a object id as name" do
10
+ @sub.name.should == "0x#{@sub.object_id}"
11
+ end
12
+ it "should have operations whitelist" do
13
+ @sub.operations.should == "update"
14
+ end
15
+ it "should have nil condition_script" do
16
+ @sub.condition_script.should == nil
17
+ end
18
+ it "should have a nil uri_regexp" do
19
+ @sub.uri_regexp.should == nil
20
+ end
21
+ it "should have a nil callback" do
22
+ @sub.callback.should == nil
23
+ end
24
+ end
25
+
26
+ it "should overwrite callback from options" do
27
+ callback = lambda {}
28
+ s = (Gom::Remote::Subscription.new '/node/values', :callback => callback )
29
+ s.callback.should == callback
30
+ end
31
+
32
+ it "should overwrite name from options value" do
33
+ name = "test-#{Time.now.to_i}"
34
+ s = (Gom::Remote::Subscription.new '/node/values', :name => name)
35
+ s.name.should == name
36
+ end
37
+
38
+ describe "observer uri" do
39
+ it "should construct a proper gom observer uri" do
40
+ s = (Gom::Remote::Subscription.new '/dmx/node/values')
41
+ s.uri.should == "/gom/observer/dmx/node/values/.#{s.name}"
42
+ end
43
+ it "should interpret attribute paths" do
44
+ s = (Gom::Remote::Subscription.new '/dmx/node:attribute')
45
+ s.uri.should == "/gom/observer/dmx/node/attribute/.#{s.name}"
46
+ end
47
+ end
48
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --backtrace --debugger
@@ -0,0 +1,48 @@
1
+ # figure out where we are being loaded from
2
+ if $LOADED_FEATURES.grep(/spec\/spec_helper\.rb/).any?
3
+ begin
4
+ raise "foo"
5
+ rescue => e
6
+ puts <<-MSG
7
+ ===================================================
8
+ It looks like spec_helper.rb has been loaded
9
+ multiple times. Normalize the require to:
10
+
11
+ require "spec/spec_helper"
12
+
13
+ Things like File.join and File.expand_path will
14
+ cause it to be loaded multiple times.
15
+
16
+ Loaded this time from:
17
+
18
+ #{e.backtrace.join("\n ")}
19
+ ===================================================
20
+ MSG
21
+ end
22
+ end
23
+
24
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
25
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
26
+ require 'spec'
27
+ require 'spec/autorun'
28
+ require 'fakeweb'
29
+
30
+ require 'gom-script'
31
+
32
+ Spec::Runner.configure do |config|
33
+ config.before :each do
34
+ FakeWeb.register_uri(
35
+ :get, "http://gom:345/gom/config/connection.txt",
36
+ :body => "client_ip: 10.0.0.23"
37
+ )
38
+ FakeWeb.register_uri(
39
+ :get, "http://localhost:3000/gom/config/connection.txt",
40
+ :body => "client_ip: 10.0.0.23"
41
+ )
42
+
43
+ Gom::Remote.connection = nil # reset for every test
44
+ end
45
+
46
+ config.after :each do
47
+ end
48
+ end
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gom-script
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - art+com/dirk luesebrink
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-30 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: applix
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.2.1
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: rack
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: gom-core
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: rspec
47
+ type: :development
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ - !ruby/object:Gem::Dependency
56
+ name: fakeweb
57
+ type: :development
58
+ version_requirement:
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: 1.2.7
64
+ version:
65
+ description: " \n GOM is a schema-less object database in ruby with Resource Oriented API,\n server-side javascript, distributed HTTP notifications and some more.\n This gom-script script simplifies coding of clients and daemon which like\n to listen on state change event in the GOM.\n "
66
+ email: dirk.luesebrink@gmail.com
67
+ executables: []
68
+
69
+ extensions: []
70
+
71
+ extra_rdoc_files:
72
+ - LICENSE
73
+ - README.markdown
74
+ files:
75
+ - .document
76
+ - .gitignore
77
+ - LICENSE
78
+ - README.markdown
79
+ - Rakefile
80
+ - VERSION
81
+ - lib/gom-script.rb
82
+ - lib/gom/remote.rb
83
+ - lib/gom/remote/connection.rb
84
+ - lib/gom/remote/daemon.rb
85
+ - lib/gom/remote/entry.rb
86
+ - lib/gom/remote/http_server.rb
87
+ - lib/gom/remote/subscription.rb
88
+ - spec/gom/remote/connection_spec.rb
89
+ - spec/gom/remote/daemon_spec.rb
90
+ - spec/gom/remote/http_server_spec.rb
91
+ - spec/gom/remote/subscription_spec.rb
92
+ - spec/spec.opts
93
+ - spec/spec_helper.rb
94
+ has_rdoc: true
95
+ homepage: http://github.com/crux/gom-script
96
+ licenses: []
97
+
98
+ post_install_message:
99
+ rdoc_options:
100
+ - --charset=UTF-8
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: "0"
108
+ version:
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: "0"
114
+ version:
115
+ requirements: []
116
+
117
+ rubyforge_project:
118
+ rubygems_version: 1.3.5
119
+ signing_key:
120
+ specification_version: 3
121
+ summary: connecting scripts and daemons with a remote GOM instance
122
+ test_files:
123
+ - spec/gom/remote/connection_spec.rb
124
+ - spec/gom/remote/daemon_spec.rb
125
+ - spec/gom/remote/http_server_spec.rb
126
+ - spec/gom/remote/subscription_spec.rb
127
+ - spec/spec_helper.rb