bud 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +9 -0
- data/README +30 -0
- data/bin/budplot +134 -0
- data/bin/budvis +201 -0
- data/bin/rebl +4 -0
- data/docs/README.md +13 -0
- data/docs/bfs.md +379 -0
- data/docs/bfs.raw +251 -0
- data/docs/bfs_arch.png +0 -0
- data/docs/bloom-loop.png +0 -0
- data/docs/bust.md +83 -0
- data/docs/cheat.md +291 -0
- data/docs/deploy.md +96 -0
- data/docs/diffs +181 -0
- data/docs/getstarted.md +296 -0
- data/docs/intro.md +36 -0
- data/docs/modules.md +112 -0
- data/docs/operational.md +96 -0
- data/docs/rebl.md +99 -0
- data/docs/ruby_hooks.md +19 -0
- data/docs/visualizations.md +75 -0
- data/examples/README +1 -0
- data/examples/basics/hello.rb +12 -0
- data/examples/basics/out +1103 -0
- data/examples/basics/out.new +856 -0
- data/examples/basics/paths.rb +51 -0
- data/examples/bust/README.md +9 -0
- data/examples/bust/bustclient-example.rb +23 -0
- data/examples/bust/bustinspector.html +135 -0
- data/examples/bust/bustserver-example.rb +18 -0
- data/examples/chat/README.md +9 -0
- data/examples/chat/chat.rb +45 -0
- data/examples/chat/chat_protocol.rb +8 -0
- data/examples/chat/chat_server.rb +29 -0
- data/examples/deploy/tokenring-ec2.rb +26 -0
- data/examples/deploy/tokenring-local.rb +17 -0
- data/examples/deploy/tokenring.rb +39 -0
- data/lib/bud/aggs.rb +126 -0
- data/lib/bud/bud_meta.rb +185 -0
- data/lib/bud/bust/bust.rb +126 -0
- data/lib/bud/bust/client/idempotence.rb +10 -0
- data/lib/bud/bust/client/restclient.rb +49 -0
- data/lib/bud/collections.rb +937 -0
- data/lib/bud/depanalysis.rb +44 -0
- data/lib/bud/deploy/countatomicdelivery.rb +50 -0
- data/lib/bud/deploy/deployer.rb +67 -0
- data/lib/bud/deploy/ec2deploy.rb +200 -0
- data/lib/bud/deploy/localdeploy.rb +41 -0
- data/lib/bud/errors.rb +15 -0
- data/lib/bud/graphs.rb +405 -0
- data/lib/bud/joins.rb +300 -0
- data/lib/bud/rebl.rb +314 -0
- data/lib/bud/rewrite.rb +523 -0
- data/lib/bud/rtrace.rb +27 -0
- data/lib/bud/server.rb +43 -0
- data/lib/bud/state.rb +108 -0
- data/lib/bud/storage/tokyocabinet.rb +170 -0
- data/lib/bud/storage/zookeeper.rb +178 -0
- data/lib/bud/stratify.rb +83 -0
- data/lib/bud/viz.rb +65 -0
- data/lib/bud.rb +797 -0
- metadata +330 -0
data/lib/bud/bud_meta.rb
ADDED
@@ -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,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
|