/
fs_master.rb
130 lines (113 loc) · 4.44 KB
/
fs_master.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
require 'rubygems'
require 'uuid'
require 'bud'
require 'bfs/bfs_client'
require 'kvs/kvs'
require 'ordering/serializer'
require 'ordering/assigner'
require 'ordering/nonce'
# FSProtocol is the basic filesystem input/output contract.
module FSProtocol
state do
interface input, :fsls, [:reqid, :path]
interface input, :fscreate, [] => [:reqid, :name, :path, :data]
interface input, :fsmkdir, [] => [:reqid, :name, :path]
interface input, :fsrm, [] => [:reqid, :name, :path]
interface output, :fsret, [:reqid, :status, :data]
end
end
# KVSFS is an implementation of FSProtocol that uses a key-value store
# for filesystem metadata. The tree structure of the FS is embedded into
# the flat KVS namespace in the following way:
# * keys are fully-qualified path names
# * directories have arrays as their values, containing the directory contents
# (file or directory names)
# hence creating a file or directory involves three KVS operations:
# * looking up the parent path
# * updating the parent path
# * creating an entry for the file or directory
module KVSFS
include FSProtocol
include BasicKVS
include TimestepNonce
state do
# in the KVS-backed implementation, we'll use the same routine for creating
# files and directories.
scratch :check_parent_exists, [:reqid, :name, :path, :mtype, :data]
scratch :check_is_empty, [:reqid, :orig_reqid, :name]
scratch :can_remove, [:reqid, :orig_reqid, :name]
end
bootstrap do
kvput << [nil, '/', gen_id, []]
end
bloom :elles do
kvget <= fsls { |l| [l.reqid, l.path] }
fsret <= (kvget_response * fsls).pairs(:reqid => :reqid) { |r, i| [r.reqid, true, r.value] }
fsret <= fsls do |l|
unless kvget_response.map{ |r| r.reqid}.include? l.reqid
[l.reqid, false, nil]
end
end
end
bloom :create do
check_parent_exists <= fscreate { |c| [c.reqid, c.name, c.path, :create, c.data] }
check_parent_exists <= fsmkdir { |m| [m.reqid, m.name, m.path, :mkdir, nil] }
check_parent_exists <= fsrm { |m| [m.reqid, m.name, m.path, :rm, nil] }
kvget <= check_parent_exists { |c| [c.reqid, c.path] }
fsret <= check_parent_exists do |c|
unless kvget_response.map{ |r| r.reqid}.include? c.reqid
puts "not found #{c.path}" or [c.reqid, false, "parent path #{c.path} for #{c.name} does not exist"]
end
end
# if the block above had no rows, dir_exists will have rows.
temp :dir_exists <= (check_parent_exists * kvget_response * nonce).combos([check_parent_exists.reqid, kvget_response.reqid])
check_is_empty <= (fsrm * nonce).pairs {|m, n| [n.ident, m.reqid, terminate_with_slash(m.path) + m.name] }
kvget <= check_is_empty {|c| [c.reqid, c.name] }
can_remove <= (kvget_response * check_is_empty).pairs([kvget_response.reqid, check_is_empty.reqid]) do |r, c|
[c.reqid, c.orig_reqid, c.name] if r.value.length == 0
end
fsret <= dir_exists do |c, r, n|
if c.mtype == :rm
unless can_remove.map{|can| can.orig_reqid}.include? c.reqid
[c.reqid, false, "directory #{} not empty"]
end
end
end
# update dir entry
# note that it is unnecessary to ensure that a file is created before its corresponding
# directory entry, as both inserts into :kvput below will co-occur in the same timestep.
kvput <= dir_exists do |c, r, n|
if c.mtype == :rm
if can_remove.map{|can| can.orig_reqid}.include? c.reqid
[ip_port, c.path, n.ident, r.value.clone.reject{|item| item == c.name}]
end
else
[ip_port, c.path, n.ident, r.value.clone.push(c.name)]
end
end
kvput <= dir_exists do |c, r, n|
case c.mtype
when :mkdir
[ip_port, terminate_with_slash(c.path) + c.name, c.reqid, []]
when :create
[ip_port, terminate_with_slash(c.path) + c.name, c.reqid, "LEAF"]
end
end
# delete entry -- if an 'rm' request,
kvdel <= dir_exists do |c, r, n|
if can_remove.map{|can| can.orig_reqid}.include? c.reqid
[terminate_with_slash(c.path) + c.name, c.reqid]
end
end
# report success if the parent directory exists (and there are no errors)
# were there errors, we'd never reach fixpoint.
fsret <= dir_exists do |c, r, n|
unless c.mtype == :rm and ! can_remove.map{|can| can.orig_reqid}.include? c.reqid
[c.reqid, true, nil]
end
end
end
def terminate_with_slash(path)
return path[-1..-1] == '/' ? path : path + '/'
end
end