gom-script 0.1.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.
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