bud 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/LICENSE +9 -0
  2. data/README +30 -0
  3. data/bin/budplot +134 -0
  4. data/bin/budvis +201 -0
  5. data/bin/rebl +4 -0
  6. data/docs/README.md +13 -0
  7. data/docs/bfs.md +379 -0
  8. data/docs/bfs.raw +251 -0
  9. data/docs/bfs_arch.png +0 -0
  10. data/docs/bloom-loop.png +0 -0
  11. data/docs/bust.md +83 -0
  12. data/docs/cheat.md +291 -0
  13. data/docs/deploy.md +96 -0
  14. data/docs/diffs +181 -0
  15. data/docs/getstarted.md +296 -0
  16. data/docs/intro.md +36 -0
  17. data/docs/modules.md +112 -0
  18. data/docs/operational.md +96 -0
  19. data/docs/rebl.md +99 -0
  20. data/docs/ruby_hooks.md +19 -0
  21. data/docs/visualizations.md +75 -0
  22. data/examples/README +1 -0
  23. data/examples/basics/hello.rb +12 -0
  24. data/examples/basics/out +1103 -0
  25. data/examples/basics/out.new +856 -0
  26. data/examples/basics/paths.rb +51 -0
  27. data/examples/bust/README.md +9 -0
  28. data/examples/bust/bustclient-example.rb +23 -0
  29. data/examples/bust/bustinspector.html +135 -0
  30. data/examples/bust/bustserver-example.rb +18 -0
  31. data/examples/chat/README.md +9 -0
  32. data/examples/chat/chat.rb +45 -0
  33. data/examples/chat/chat_protocol.rb +8 -0
  34. data/examples/chat/chat_server.rb +29 -0
  35. data/examples/deploy/tokenring-ec2.rb +26 -0
  36. data/examples/deploy/tokenring-local.rb +17 -0
  37. data/examples/deploy/tokenring.rb +39 -0
  38. data/lib/bud/aggs.rb +126 -0
  39. data/lib/bud/bud_meta.rb +185 -0
  40. data/lib/bud/bust/bust.rb +126 -0
  41. data/lib/bud/bust/client/idempotence.rb +10 -0
  42. data/lib/bud/bust/client/restclient.rb +49 -0
  43. data/lib/bud/collections.rb +937 -0
  44. data/lib/bud/depanalysis.rb +44 -0
  45. data/lib/bud/deploy/countatomicdelivery.rb +50 -0
  46. data/lib/bud/deploy/deployer.rb +67 -0
  47. data/lib/bud/deploy/ec2deploy.rb +200 -0
  48. data/lib/bud/deploy/localdeploy.rb +41 -0
  49. data/lib/bud/errors.rb +15 -0
  50. data/lib/bud/graphs.rb +405 -0
  51. data/lib/bud/joins.rb +300 -0
  52. data/lib/bud/rebl.rb +314 -0
  53. data/lib/bud/rewrite.rb +523 -0
  54. data/lib/bud/rtrace.rb +27 -0
  55. data/lib/bud/server.rb +43 -0
  56. data/lib/bud/state.rb +108 -0
  57. data/lib/bud/storage/tokyocabinet.rb +170 -0
  58. data/lib/bud/storage/zookeeper.rb +178 -0
  59. data/lib/bud/stratify.rb +83 -0
  60. data/lib/bud/viz.rb +65 -0
  61. data/lib/bud.rb +797 -0
  62. metadata +330 -0
@@ -0,0 +1,185 @@
1
+ require 'bud/rewrite'
2
+ require 'bud/state'
3
+ require 'parse_tree'
4
+ require 'pp'
5
+
6
+ class BudMeta #:nodoc: all
7
+ attr_reader :depanalysis
8
+
9
+ def initialize(bud_instance, declarations)
10
+ @bud_instance = bud_instance
11
+ @declarations = declarations
12
+ end
13
+
14
+ def meta_rewrite
15
+ shred_rules
16
+ top_stratum = stratify
17
+ stratum_map = binaryrel2map(@bud_instance.t_stratum)
18
+
19
+ rewritten_strata = Array.new(top_stratum + 2) { [] }
20
+ @bud_instance.t_rules.each do |d|
21
+ if d.op.to_s == '<='
22
+ # deductive rules are assigned to strata based on
23
+ # the basic datalog stratification algorithm
24
+ belongs_in = stratum_map[d.lhs]
25
+ belongs_in ||= 0
26
+ rewritten_strata[belongs_in] << d.src
27
+ else
28
+ # all temporal rules are placed in the last stratum
29
+ rewritten_strata[top_stratum + 1] << d.src
30
+ end
31
+ end
32
+
33
+ @depanalysis = DepAnalysis.new
34
+ @bud_instance.t_depends_tc.each {|d| @depanalysis.depends_tc << d}
35
+ @bud_instance.t_provides.each {|p| @depanalysis.providing << p}
36
+ 3.times { @depanalysis.tick }
37
+
38
+ @depanalysis.underspecified.each do |u|
39
+ puts "Warning: underspecified dataflow: #{u.inspect}"
40
+ @bud_instance.t_underspecified << u
41
+ end
42
+ dump_rewrite(rewritten_strata) if @bud_instance.options[:dump_rewrite]
43
+
44
+ return rewritten_strata
45
+ end
46
+
47
+ def binaryrel2map(rel)
48
+ map = {}
49
+ rel.each do |s|
50
+ raise Bud::BudError unless s.length == 2
51
+ map[s[0]] = s[1]
52
+ end
53
+ return map
54
+ end
55
+
56
+ def shred_rules
57
+ # to completely characterize the rules of a bud class we must extract
58
+ # from all parent classes/modules
59
+ # after making this pass, we no longer care about the names of methods.
60
+ # we are shredding down to the granularity of rule heads.
61
+ seed = 0
62
+ rulebag = {}
63
+ @bud_instance.class.ancestors.reverse.each do |anc|
64
+ @declarations.each do |meth_name|
65
+ rw = rewrite_rule_block(anc, meth_name, seed)
66
+ if rw
67
+ seed = rw.rule_indx
68
+ rulebag[meth_name] = rw
69
+ end
70
+ end
71
+ end
72
+
73
+ rulebag.each_value do |v|
74
+ v.rules.each {|r| @bud_instance.t_rules << r}
75
+ v.depends.each {|d| @bud_instance.t_depends << d}
76
+ end
77
+ end
78
+
79
+ def rewrite_rule_block(klass, block_name, seed)
80
+ parse_tree = ParseTree.translate(klass, block_name)
81
+ return unless parse_tree.first
82
+
83
+ pt = Unifier.new.process(parse_tree)
84
+ pp pt if @bud_instance.options[:dump_ast]
85
+ begin
86
+ check_rule_ast(pt)
87
+ rescue Exception => e
88
+ # try to "generate" the source code associated with the problematic
89
+ # block, so as to generate a more meaningful error message.
90
+ # if this parse fails, return the original exception (not the new one).
91
+ begin
92
+ code = Ruby2Ruby.new.process(pt)
93
+ rescue Exception => sub_e
94
+ raise e, "Error parsing rule block #{block_name}. Could not extract source."
95
+ end
96
+ raise e, "Error parsing rule block #{block_name}:\n#{code}"
97
+ end
98
+
99
+ rewriter = RuleRewriter.new(seed, @bud_instance)
100
+ rewriter.process(pt)
101
+ return rewriter
102
+ end
103
+
104
+ # Perform some basic sanity checks on the AST of a rule block. We expect a
105
+ # rule block to consist of a :defn, a nested :scope, and then a sequence of
106
+ # statements. Each statement is a :call node.
107
+ def check_rule_ast(pt)
108
+ # :defn format: node tag, block name, args, nested scope
109
+ raise Bud::CompileError if pt.sexp_type != :defn
110
+ scope = pt[3]
111
+ raise Bud::CompileError if scope.sexp_type != :scope
112
+ block = scope[1]
113
+
114
+ block.each_with_index do |n,i|
115
+ if i == 0
116
+ raise Bud::CompileError if n != :block
117
+ next
118
+ end
119
+
120
+ raise Bud::CompileError if n.sexp_type != :call
121
+ raise Bud::CompileError unless n.length == 4
122
+
123
+ # Rule format: call tag, lhs, op, rhs
124
+ tag, lhs, op, rhs = n
125
+
126
+ # Check that LHS references a named collection
127
+ raise Bud::CompileError if lhs.nil? or lhs.sexp_type != :call
128
+ lhs_name = lhs[2]
129
+ unless @bud_instance.tables.has_key? lhs_name.to_sym
130
+ raise Bud::CompileError, "Table does not exist: '#{lhs_name}'"
131
+ end
132
+
133
+ # Check that op is a legal Bloom operator
134
+ unless [:<, :<=].include? op
135
+ raise Bud::CompileError, "Illegal operator: '#{op}'"
136
+ end
137
+
138
+ # Check superator invocation. A superator that begins with "<" is parsed
139
+ # as a call to the binary :< operator. The right operand to :< is a :call
140
+ # node; the LHS of the :call is the actual rule body, the :call's oper is
141
+ # the rest of the superator (unary ~, -, +), and the RHS is empty. Note
142
+ # that ParseTree encodes unary "-" and "+" as :-@ and :-+, respectively.
143
+ # XXX: We don't check for illegal superators (e.g., "<--"). That would be
144
+ # tricky, because they are encoded as a nested unary op in the rule body.
145
+ if op == :<
146
+ raise Bud::CompileError unless rhs.sexp_type == :arglist
147
+ body = rhs[1]
148
+ raise Bud::CompileError unless body.sexp_type == :call
149
+ op_tail = body[2]
150
+ raise Bud::CompileError unless [:~, :-@, :+@].include? op_tail
151
+ rhs_args = body[3]
152
+ raise Bud::CompileError unless rhs_args.sexp_type == :arglist
153
+ raise Bud::CompileError if rhs_args.length != 1
154
+ end
155
+ end
156
+ end
157
+
158
+ def stratify
159
+ strat = Stratification.new
160
+ @bud_instance.t_depends.each {|d| strat.depends << d}
161
+ strat.tick
162
+
163
+ # Copy computed data back into Bud runtime
164
+ strat.stratum.each {|s| @bud_instance.t_stratum << s}
165
+ strat.depends_tc.each {|d| @bud_instance.t_depends_tc << d}
166
+ strat.cycle.each {|c| @bud_instance.t_cycle << c}
167
+ if strat.top_strat.length > 0
168
+ top = strat.top_strat.first.stratum
169
+ else
170
+ top = 1
171
+ end
172
+ return top
173
+ end
174
+
175
+ def dump_rewrite(strata)
176
+ fout = File.new("#{@bud_instance.class}_rewritten.txt", "w")
177
+ fout.puts "Declarations:"
178
+
179
+ strata.each_with_index do |src_ary, i|
180
+ text = src_ary.join("\n")
181
+ fout.puts "R[#{i}]:\n#{text}"
182
+ end
183
+ fout.close
184
+ end
185
+ end
@@ -0,0 +1,126 @@
1
+ require 'rubygems'
2
+ require 'bud'
3
+ require 'json'
4
+ require 'socket'
5
+ require 'uri'
6
+ require 'cgi'
7
+
8
+ HTTP_VERBS = ["GET", "POST"] #, "DELETE"]
9
+
10
+ # a RESTful interface to Bloom code
11
+ module Bust
12
+ include Bud
13
+
14
+ # used this for inspiration:
15
+ # http://blogs.msdn.com/b/abhinaba/archive/2005/10/14/474841.aspx
16
+
17
+ # allow state to be queried easily
18
+ state do
19
+ table :t_table_info, [:tab_name, :tab_type]
20
+ table :t_table_schema, [:tab_name, :scheme]
21
+ end
22
+
23
+ bootstrap do
24
+ # copied from peter's code; this should probably be in the Bud runtime or in
25
+ # some meta module
26
+ @tables.each do |t|
27
+ t_table_schema << [t[0], t[1].schema.clone]
28
+ t_table_info << [t[0], t[1].class.to_s]
29
+ end
30
+
31
+ q = Queue.new
32
+ Thread.start(self) do |bud|
33
+ BustClass.new(bud, q)
34
+ end
35
+ # Wait for socket to be ready before we return from bootstrap.
36
+ q.pop
37
+ end
38
+
39
+ class BustClass
40
+
41
+ class BustHandler
42
+ def initialize(session, request, body, bud)
43
+ @session = session
44
+ @request = request
45
+ @body = body
46
+ @bud = bud
47
+ end
48
+
49
+ def serve()
50
+ puts "Request: " + @request
51
+ puts "Body: " + @body.inspect if @body
52
+
53
+ for type in HTTP_VERBS
54
+ if @request =~ Regexp.new(type + " .* HTTP*")
55
+ break reqstr = @request.gsub(Regexp.new(type + " "), '').gsub(/ HTTP.*/, '')
56
+ end
57
+ end
58
+
59
+ uri = URI.parse(reqstr)
60
+ uri_params = {}
61
+ uri_params = CGI.parse(uri.query) if uri.query
62
+ table_name = uri.path[1..-1].split(".")[0] # hack; we always return JSON
63
+ # "Access-Control-Allow-Origin: *" disables same-origin policy to allow
64
+ # XMLHttpRequests from any origin
65
+ success = "HTTP/1.1 200 OK\r\nServer: Bud\r\nContent-type: application/json\r\nAccess-Control-Allow-Origin: *\r\n\r\n"
66
+ failure = "HTTP/1.1 404 Object Not Found\r\nServer: Bud\r\nAccess-Control-Allow-Origin: *\r\n\r\n"
67
+
68
+ begin
69
+ if @request =~ /GET .* HTTP*/
70
+ puts "GET shouldn't have body" if @body
71
+ # select the appropriate elements from the table
72
+ desired_elements = (eval "@bud." + table_name).find_all do |t|
73
+ uri_params.all? {|k, v| (eval "t." + k.to_s) == v[0]}
74
+ end
75
+ @session.print success
76
+ @session.print desired_elements.to_json
77
+ elsif @request =~ /POST .* HTTP*/
78
+ # instantiate a new tuple
79
+ tuple_to_insert = []
80
+ @body.each do |k, v|
81
+ index = (eval "@bud." + table_name).schema.find_index(k.to_sym)
82
+ for i in (tuple_to_insert.size..index)
83
+ tuple_to_insert << nil
84
+ end
85
+ tuple_to_insert[index] = v[0]
86
+ end
87
+ # actually insert the puppy
88
+ @bud.async_do { (eval "@bud." + table_name) << tuple_to_insert }
89
+ @session.print success
90
+ end
91
+ rescue Exception
92
+ puts "exception: #{$!}"
93
+ @session.print failure
94
+ ensure
95
+ @session.close
96
+ end
97
+
98
+ end
99
+ end
100
+
101
+ def initialize(bud, q)
102
+ # allow user-configurable port
103
+ server = TCPServer.new(bud.ip, (bud.options[:bust_port] or 8080))
104
+ # We're now ready to accept connections.
105
+ q << true
106
+
107
+ loop do
108
+ session = server.accept
109
+ request = session.gets
110
+ length = nil
111
+ body = nil
112
+ while req = session.gets
113
+ length = Integer(req.split(" ")[1]) if req.match(/^Content-Length:/)
114
+ if req.match(/^\r\n$/)
115
+ body = CGI.parse(session.read(length)) if length
116
+ break
117
+ end
118
+ end
119
+ Thread.start(session, request, body, bud) do |session, request, body, bud|
120
+ BustHandler.new(session, request, body, bud).serve()
121
+ end
122
+ end
123
+ end
124
+
125
+ end
126
+ end
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'bud'
3
+
4
+ module Idempotence #:nodoc: all
5
+ state do
6
+ table :dead, [:dead]
7
+ end
8
+
9
+ def bust_idempotent(r) (dead.include? r) ? false : dead.insert(r) end
10
+ end
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+ require 'bud'
3
+ require 'nestful'
4
+ require 'bud/bust/client/idempotence'
5
+
6
+ module RestClient
7
+ include Idempotence # :nodoc: all
8
+
9
+ state do
10
+ # complains about underspecified dataflow because it can't see the
11
+ # nested rules...
12
+ interface input, :rest_req, [:rid, :verb, :form, :url, :params]
13
+ interface output, :rest_response, [:rid, :resp, :exception]
14
+
15
+ # we don't really need to store this i suppose
16
+ scratch :rest_req_thread, [:thread]
17
+ end
18
+
19
+ bloom :rest_client do
20
+ rest_req_thread <= rest_req.map do |req|
21
+ # start up a new thread to deal with the response
22
+ [Thread.start(req, self) do |r, bud|
23
+ params = (r.params or {})
24
+ begin
25
+ case r.verb
26
+ when :get
27
+ get_hash = {:params => params}
28
+ get_hash[:format] = r.form if r.form
29
+ resp_tuple = [r.rid, Nestful.get(r.url, get_hash), false]
30
+ when :post
31
+ # not sure if this is a sensible default for format?
32
+ format = (r.form or :form)
33
+ resp_tuple = [r.rid, Nestful.post(r.url, :format => format,
34
+ :params => params), false]
35
+ else
36
+ raise "invalid verb"
37
+ end
38
+ rescue
39
+ resp_tuple = [r.rid, "#{$!}", true]
40
+ end
41
+ # insert the response
42
+ bud.async_do do
43
+ rest_response <+ [resp_tuple]
44
+ end
45
+ end] if bust_idempotent [[req.rid, req.verb, req.form, req.url, req.params,
46
+ @budtime]]
47
+ end
48
+ end
49
+ end