bud 0.0.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/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
|