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.
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,44 @@
1
+ require 'rubygems'
2
+ require 'bud'
3
+
4
+ class DepAnalysis #:nodoc: all
5
+ include Bud
6
+
7
+ state do
8
+ table :providing, [:pred, :input]
9
+ table :depends_tc, [:head, :body, :via, :neg, :temporal]
10
+ table :underspecified, [:pred, :input]
11
+
12
+ table :source, [:pred]
13
+ table :sink, [:pred]
14
+ end
15
+
16
+ def declaration
17
+ strata[0] = lambda {
18
+ source <= providing do |p|
19
+ if p.input and !depends_tc.map{|d| d.head}.include? p.pred
20
+ [p.pred]
21
+ end
22
+ end
23
+
24
+ sink <= providing do |p|
25
+ if !p.input and !depends_tc.map{|d| d.body}.include? p.pred
26
+ [p.pred]
27
+ end
28
+ end
29
+
30
+ underspecified <= providing do |p|
31
+ if p.input
32
+ unless depends_tc.map{|d| d.body if d.head != d.body}.include? p.pred
33
+ [p.pred, true]
34
+ end
35
+ else
36
+ unless depends_tc.map{|d| d.head if d.head != d.body}.include? p.pred
37
+ [p.pred, false]
38
+ end
39
+ end
40
+ end
41
+ }
42
+ end
43
+ end
44
+
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'bud'
3
+
4
+ # XXX: delivery modules are a massive hack. we need to think about
5
+ # aspect-oriented programming here, or allow users to extend the definitions of
6
+ # existing table types
7
+ module CountAtomicDelivery # :nodoc: all
8
+ state do
9
+ scratch :atomic_data_in, [:loc, :tuple]
10
+ channel :atomic_data_chan, [:@loc, :tuple]
11
+ table :atomic_data_recv, [:loc, :tuple]
12
+
13
+ scratch :atomic_count, [:loc, :cnt]
14
+ channel :atomic_count_chan,[:@loc, :cnt]
15
+ table :atomic_count_recv, [] => [:cnt]
16
+
17
+ scratch :atomic_recv_count, [:loc] => [:cnt]
18
+ scratch :atomic_data_atomic, [:loc, :tuple]
19
+ scratch :atomic_data_out, [:tuple]
20
+ end
21
+
22
+ bloom :countatomicdelivery do
23
+ atomic_count <= atomic_data_in.group([:loc], count)
24
+
25
+ atomic_data_chan <~ atomic_data_in
26
+ atomic_count_chan <~ atomic_count
27
+
28
+ atomic_count_recv <= atomic_count_chan.map {|c| [c.cnt]}
29
+ atomic_data_recv <= atomic_data_chan
30
+
31
+ atomic_recv_count <= atomic_data_recv.group([:loc], count)
32
+
33
+ # atomic_data_atomic <= join([atomic_recv_count, atomic_count_recv,
34
+ # atomic_data_recv],
35
+ # [atomic_recv_count.cnt,
36
+ # atomic_count_recv.cnt]).map{ |rc, cr, d| d }
37
+
38
+ atomic_data_atomic <= ((if atomic_recv_count.first and
39
+ atomic_count_recv.first and
40
+ atomic_recv_count.first.cnt ==
41
+ atomic_count_recv.first.cnt
42
+ atomic_data_recv
43
+ end) or [])
44
+
45
+ # Commented out due to bug #85.
46
+ # atomic_data_recv <- atomic_data_atomic
47
+ # Idempotence hack inserted due to bug #85.
48
+ atomic_data_out <= atomic_data_atomic.map {|a| [a.tuple] if depl_idempotent [[:atomic_data_out, a]]}
49
+ end
50
+ end
@@ -0,0 +1,67 @@
1
+ require 'rubygems'
2
+ require 'bud'
3
+ require 'open-uri'
4
+ require 'bud/deploy/countatomicdelivery'
5
+
6
+ class Module
7
+ def deploystrap(&block)
8
+ meth_name = "__deploystrap__#{Module.get_class_name(self)}".to_sym
9
+ define_method(meth_name, &block)
10
+ end
11
+ end
12
+
13
+ module Deployer # :nodoc: all
14
+ include CountAtomicDelivery
15
+
16
+ state do
17
+ table :node, [:uid] => [:node]
18
+ table :node_count, [] => [:num]
19
+ table :initial_data, [:uid, :pred, :data]
20
+ channel :dont_care, [:@loc]
21
+ table :dead, [:dead]
22
+ end
23
+
24
+ def depl_idempotent(r) (dead.include? r) ? false : dead.insert(r) end
25
+
26
+ def do_deploystrap
27
+ self.class.ancestors.each do |anc|
28
+ anc.instance_methods(false).each do |m|
29
+ if /^__deploystrap__/.match m
30
+ self.method(m.to_sym).call
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ def initialize(opt = {})
37
+ super
38
+ if opt[:deploy]
39
+ do_deploystrap
40
+ end
41
+ end
42
+
43
+ # Distribute the EDB to each node.
44
+ #
45
+ # XXX: this may break coordination protocols that assume the EDB is present
46
+ # before any messages are received. In order to fix this, we would probably
47
+ # need to globally synchronize to ensure that "timestamp 0" gets "fully
48
+ # evaluated" before any messages can be sent.
49
+
50
+ bloom :distribute_data do
51
+ atomic_data_in <= join([node, initial_data],
52
+ [node.uid, initial_data.uid]).map do |n, i|
53
+ [n.node, [i.pred, i.data]] if depl_idempotent [[n.node, i.pred, i.data]]
54
+ end
55
+
56
+ # Add all tuples at once.
57
+ dont_care <~ atomic_data_out.map do |a|
58
+ if depl_idempotent a
59
+ a.tuple[1].map do |d|
60
+ eval a.tuple[0].to_s + " <+ [" + d.inspect + "]"
61
+ end
62
+ [ip_port]
63
+ end
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,200 @@
1
+ require 'rubygems'
2
+ require 'AWS'
3
+ require 'pp'
4
+ require 'net/ssh'
5
+ require 'net/scp'
6
+ require 'bud'
7
+ require 'bud/deploy/deployer'
8
+
9
+ # logic to deploy Bloom programs on EC2
10
+ module EC2Deploy
11
+ include Deployer
12
+
13
+ state do
14
+ table :access_key_id, [] => [:key]
15
+ table :secret_access_key, [] => [:key]
16
+ table :image_id, [] => [:img]
17
+ table :key_name, [] => [:name]
18
+ table :ec2_key_location, [] => [:loc]
19
+ table :ec2_conn, [] => [:conn]
20
+ table :ec2_insts, [] => [:insts]
21
+ table :reservation_id, [] => [:rid]
22
+ periodic :spinup_timer, 6
23
+ scratch :the_reservation, [] => [:reservation]
24
+ scratch :the_reservation_next, [] => [:reservation]
25
+ scratch :node_up, [:node] => [:bool]
26
+ table :init_dir, [] => [:dir]
27
+ table :temp_node, [:uid, :node, :localip]
28
+ table :all_up, [:bool]
29
+ table :ruby_command, [] => [:cmd]
30
+ table :deploy_node, [:uid] => [:node]
31
+ channel :ready, [:@loc, :sender]
32
+ table :ready_tab, [:sender]
33
+ scratch :ready_count, [:num]
34
+ end
35
+
36
+ deploystrap do
37
+ # Write the IP & port to a file; we'll send this to each EC2 node.
38
+ File.open("deploy_ip_port", "w") do |f|
39
+ f.puts ip_port
40
+ end
41
+ end
42
+
43
+ bootstrap do
44
+ # The official BUD AMI.
45
+ image_id <= [["ami-f434c99d"]]
46
+ unless @options[:deploy]
47
+ # Send message to the deployer telling 'em we's up.
48
+ File.open("deploy_ip_port", "r") do |f|
49
+ ready <~ [[f.readline.chop, ip_port]]
50
+ end
51
+ end
52
+ end
53
+
54
+ bloom :spinup do
55
+ ec2_conn <= join([access_key_id, secret_access_key]).map do
56
+ if depl_idempotent [:ec2_comm]
57
+ [AWS::EC2::Base.new(:access_key_id => access_key_id[[]].key,
58
+ :secret_access_key => secret_access_key[[]].key)]
59
+ end
60
+ end
61
+
62
+ ec2_insts <= join([image_id, node_count, key_name, ec2_conn]).map do
63
+ if depl_idempotent [:ec2_insts]
64
+ print "Starting up EC2 instances"
65
+ STDOUT.flush
66
+ # First, we create the security group.
67
+ begin
68
+ ec2_conn[[]].conn.create_security_group(:group_name => "bud", :group_description => "bud")
69
+ rescue AWS::InvalidGroupDuplicate
70
+ # Group already exists; ok, maybe we created it previously.
71
+ else
72
+ # Add SSH permission.
73
+ ec2_conn[[]].conn.authorize_security_group_ingress( :group_name => "bud",
74
+ :ip_protocol => "tcp",
75
+ :from_port => 22,
76
+ :to_port => 22,
77
+ :cidr_ip => "0.0.0.0/0" )
78
+ # Add unlimited UDP permission from any node not in the security group.
79
+ # XXX: make this more restrictive?
80
+ ec2_conn[[]].conn.authorize_security_group_ingress( :group_name => "bud",
81
+ :ip_protocol => "udp",
82
+ :from_port => 0,
83
+ :to_port => 65535,
84
+ :cidr_ip => "0.0.0.0/0" )
85
+ end
86
+
87
+ # Finally, start up the instances.
88
+ [ec2_conn[[]].conn.run_instances(:image_id => image_id[[]].img,
89
+ :min_count => node_count[[]].num,
90
+ :max_count => node_count[[]].num,
91
+ :key_name => key_name[[]].name,
92
+ :security_group => "bud")]
93
+ end
94
+ end
95
+
96
+ the_reservation <= join([spinup_timer, ec2_conn, ec2_insts]).map do |t,c,i|
97
+ if depl_idempotent [[:the_reservation, t.val]] and not all_up.include? [true]
98
+ to_ret = nil
99
+ begin
100
+ to_ret = [ec2_conn[[]].conn.describe_instances()["reservationSet"]["item"].find do |i|
101
+ i["reservationId"] == ec2_insts[[]].insts["reservationId"]
102
+ end]
103
+ rescue SocketError
104
+ print "E"
105
+ else
106
+ print "."
107
+ end
108
+ STDOUT.flush
109
+ to_ret
110
+ end
111
+ end
112
+
113
+ # XXX: No upsert operator, so we have to do this.
114
+ the_reservation_next <+ the_reservation
115
+
116
+ node_up <= ((join([ec2_insts, the_reservation]).map do
117
+ if not all_up.include? [true]
118
+ the_reservation[[]].reservation["instancesSet"]["item"].map do |i|
119
+ [i, i["instanceState"]["code"] == "16"]
120
+ end
121
+ end
122
+ end)[0] or [])
123
+
124
+
125
+ all_up <+ node_up.map do
126
+ if node_up.find {|n| n.bool == false} == nil and node_up.find {|n| n.bool == true} != nil
127
+ if depl_idempotent [:nodes_all_up]
128
+ puts "done"
129
+ STDOUT.flush
130
+ [true]
131
+ end
132
+ end
133
+ end
134
+
135
+ # XXX: Fixed port 54321
136
+ temp_node <= join([all_up, the_reservation_next]).map do
137
+ break(((0..(the_reservation_next[[]].reservation["instancesSet"]["item"].size-1)).to_a.zip(the_reservation_next[[]].reservation["instancesSet"]["item"].map {|i| [i["ipAddress"], i["privateIpAddress"]]})).map {|n,ips| [n, ips[0] + ":54321", ips[1] + ":54321"]})
138
+ end
139
+
140
+ deploy_node <= join([temp_node, init_dir, ruby_command]).map do |t, i, r|
141
+ if depl_idempotent [[:node_startup, t.node]]
142
+ ip = t.node.split(":")[0]
143
+ port = t.node.split(":")[1]
144
+ print "Deploying to #{ip} (#{t.uid}/#{node_count[[]].num-1})."
145
+ STDOUT.flush
146
+
147
+ # Upload files and run commands.
148
+ ctr = 0
149
+ while ctr < 10
150
+ begin
151
+ Net::SSH.start(ip, 'ec2-user', :keys => [ec2_key_location[[]].loc],
152
+ :timeout => 5, :paranoid => false) do |session|
153
+ # Upload init_dir, and the IP and port of the deployer
154
+ session.scp.upload!("deploy_ip_port", "/home/ec2-user")
155
+ session.scp.upload!(init_dir[[]].dir, "/home/ec2-user",
156
+ :recursive => true)
157
+ # Update the Bud gem
158
+ channel = session.open_channel do |ch|
159
+ channel.request_pty do |_, success|
160
+ raise "Couldn't open a PTY on #{t.node}" if !success
161
+ end
162
+ channel.exec("sudo gem update --no-ri --no-rdoc bud")
163
+ channel.wait
164
+ end
165
+ # Run the ruby_command
166
+ session.exec!('nohup ' + ruby_command[[]].cmd + ' ' + t.localip +
167
+ ' ' + t.node + ' >metarecv.out 2>metarecv.err </dev/null &')
168
+ end
169
+ break true
170
+ rescue Exception
171
+ ctr += 1
172
+ print "."
173
+ STDOUT.flush
174
+ sleep 10
175
+ next
176
+ end
177
+ end or raise "EC2 SSH failed after 10 retries"
178
+
179
+ puts "done"
180
+ [t.uid, t.node]
181
+ end
182
+ end
183
+
184
+ end
185
+
186
+ bloom :all_nodes do
187
+ stdio <~ ready.map {|_,s| ["Ready: #{s}"]}
188
+ # Persist ready messages
189
+ ready_tab <= ready.map {|_, s| [s]}
190
+ # Compute a count of ready messages
191
+ ready_count <= ready_tab.group(nil, count)
192
+ # Copy deploy_node into node when all nodes are up
193
+ node <= join([ready_count, node_count],
194
+ [ready_count.num, node_count.num]).map do
195
+ break deploy_node
196
+ end
197
+
198
+ end
199
+
200
+ end
@@ -0,0 +1,41 @@
1
+ require 'rubygems'
2
+ require 'bud'
3
+ require 'thread'
4
+ require 'bud/deploy/deployer'
5
+
6
+ # Starts up a bunch of Bud instances locally on 127.0.0.1, with ephemeral ports.
7
+ module LocalDeploy
8
+ include Deployer
9
+
10
+ trap("CLD") {
11
+ pid = Process.wait
12
+ puts "Child pid #{pid}: terminated"
13
+ }
14
+
15
+ deploystrap do
16
+ read, write = IO.pipe
17
+ if node_count[[]]
18
+ print "Forking local processes"
19
+ (0..node_count[[]].num-1).map do |i|
20
+ Process.fork do
21
+ # Don't want to inherit our parent's random stuff.
22
+ srand
23
+ foo = self.class.new
24
+ foo.run_bg
25
+ print "."
26
+ $stdout.flush
27
+ # Processes write their port to a pipe.
28
+ write.print foo.port.to_s + "\n"
29
+ EventMachine.reactor_thread.join
30
+ end
31
+ end
32
+
33
+ # Read ports from pipe.
34
+ (0..node_count[[]].num-1).map do |i|
35
+ node << [i, "localhost:" + read.readline.rstrip]
36
+ end
37
+ puts "done"
38
+ end
39
+ end
40
+
41
+ end
data/lib/bud/errors.rb ADDED
@@ -0,0 +1,15 @@
1
+ module Bud
2
+ # Root Bud exception type.
3
+ class BudError < StandardError; end
4
+
5
+ # Raised (at runtime) when a type mismatch occurs (e.g., supplying a
6
+ # non-Enumerable object to the RHS of a Bud statement).
7
+ class BudTypeError < BudError; end
8
+
9
+ # Raised when a primary key constraint is violated.
10
+ class KeyConstraintError < BudError; end
11
+
12
+ # Raised when the input program fails to compile (e.g., due to illegal
13
+ # syntax).
14
+ class CompileError < BudError; end
15
+ end