gizzmo 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env ruby
2
+ require 'vendor/thrift_client/simple'
3
+
4
+ module Gizzard
5
+ module Thrift
6
+ T = ThriftClient::Simple
7
+
8
+ def self.struct(*args)
9
+ T::StructType.new(*args)
10
+ end
11
+
12
+ ShardException = T.make_exception(:ShardException,
13
+ T::Field.new(:description, T::STRING, 1)
14
+ )
15
+
16
+ ShardId = T.make_struct(:ShardId,
17
+ T::Field.new(:hostname, T::STRING, 1),
18
+ T::Field.new(:table_prefix, T::STRING, 2)
19
+ )
20
+
21
+ class ShardId
22
+ def inspect
23
+ "#{hostname}/#{table_prefix}"
24
+ end
25
+
26
+ alias_method :to_unix, :inspect
27
+
28
+ def self.parse(string)
29
+ new(*string.split("/"))
30
+ end
31
+ end
32
+
33
+ ShardInfo = T.make_struct(:ShardInfo,
34
+ T::Field.new(:id, struct(ShardId), 1),
35
+ T::Field.new(:class_name, T::STRING, 2),
36
+ T::Field.new(:source_type, T::STRING, 3),
37
+ T::Field.new(:destination_type, T::STRING, 4),
38
+ T::Field.new(:busy, T::I32, 5)
39
+ )
40
+
41
+ class ShardInfo
42
+ def busy?
43
+ busy && busy > 0
44
+ end
45
+
46
+ def inspect(short = false)
47
+ "#{id.inspect}" + (busy? ? " (BUSY)" : "")
48
+ end
49
+
50
+ def to_unix
51
+ [id.to_unix, class_name, busy? ? "busy" : "unbusy"].join("\t")
52
+ end
53
+ end
54
+
55
+ LinkInfo = T.make_struct(:LinkInfo,
56
+ T::Field.new(:up_id, struct(ShardId), 1),
57
+ T::Field.new(:down_id, struct(ShardId), 2),
58
+ T::Field.new(:weight, T::I32, 3)
59
+ )
60
+
61
+ class LinkInfo
62
+ def inspect
63
+ "#{up_id.inspect} -> #{down_id.inspect}" + (weight == 1 ? "" : " <#{weight}>")
64
+ end
65
+
66
+ def to_unix
67
+ [up_id.to_unix, down_id.to_unix, weight].join("\t")
68
+ end
69
+
70
+ end
71
+
72
+ ShardMigration = T.make_struct(:ShardMigration,
73
+ T::Field.new(:source_id, struct(ShardId), 1),
74
+ T::Field.new(:destination_id, struct(ShardId), 2)
75
+ )
76
+
77
+ Forwarding = T.make_struct(:Forwarding,
78
+ T::Field.new(:table_id, T::I32, 1),
79
+ T::Field.new(:base_id, T::I64, 2),
80
+ T::Field.new(:shard_id, struct(ShardId), 3)
81
+ )
82
+
83
+ class Forwarding
84
+ #FIXME table_id is not human-readable
85
+ def inspect
86
+ "[#{table_id}] #{base_id.to_s(16)} -> #{shard_id.inspect}"
87
+ end
88
+ end
89
+
90
+ class ShardManager < T::ThriftService
91
+ def initialize(host, port, log_path, dry_run = false)
92
+ super(host, port)
93
+ @dry = dry_run
94
+ begin
95
+ @log = File.open(log_path, "a")
96
+ rescue
97
+ STDERR.puts "Error opening log file at #{log_path}. Continuing..."
98
+ end
99
+ end
100
+
101
+ def _proxy(method_name, *args)
102
+ cls = self.class.ancestors.find { |cls| cls.respond_to?(:_arg_structs) and cls._arg_structs[method_name.to_sym] }
103
+ arg_class, rv_class = cls._arg_structs[method_name.to_sym]
104
+
105
+ # Writing methods return void. Methods should never both read and write. If this assumption
106
+ # is violated in the future, dry-run will fail!!
107
+ is_writing_method = rv_class._fields.first.type == ThriftClient::Simple::VOID
108
+ if @dry && is_writing_method
109
+ puts "Skipped writing: #{printable(method_name, args)}"
110
+ else
111
+ @log.puts printable(method_name, args, true)
112
+ super(method_name, *args)
113
+ end
114
+ rescue ThriftClient::Simple::ThriftException
115
+ if @dry
116
+ puts "Skipped reading: #{printable(method_name, args)}"
117
+ else
118
+ raise
119
+ end
120
+ end
121
+
122
+ def printable(method_name, args, timestamp = false)
123
+ ts = timestamp ? "#{Time.now}\t" : ""
124
+ "#{ts}#{method_name}(#{args.map{|a| a.inspect}.join(', ')})"
125
+ end
126
+
127
+
128
+ thrift_method :create_shard, void, field(:shard, struct(ShardInfo), 1), :throws => exception(ShardException)
129
+ thrift_method :delete_shard, void, field(:id, struct(ShardId), 1)
130
+ thrift_method :get_shard, struct(ShardInfo), field(:id, struct(ShardId), 1)
131
+
132
+ thrift_method :add_link, void, field(:up_id, struct(ShardId), 1), field(:down_id, struct(ShardId), 2), field(:weight, i32, 3)
133
+ thrift_method :remove_link, void, field(:up_id, struct(ShardId), 1), field(:down_id, struct(ShardId), 2)
134
+
135
+ thrift_method :list_upward_links, list(struct(LinkInfo)), field(:id, struct(ShardId), 1)
136
+ thrift_method :list_downward_links, list(struct(LinkInfo)), field(:id, struct(ShardId), 1)
137
+
138
+ thrift_method :get_child_shards_of_class, list(struct(ShardInfo)), field(:parent_id, struct(ShardId), 1), field(:class_name, string, 2)
139
+
140
+ thrift_method :mark_shard_busy, void, field(:id, struct(ShardId), 1), field(:busy, i32, 2)
141
+ thrift_method :copy_shard, void, field(:source_id, struct(ShardId), 1), field(:destination_id, struct(ShardId), 2)
142
+
143
+ thrift_method :set_forwarding, void, field(:forwarding, struct(Forwarding), 1)
144
+ thrift_method :replace_forwarding, void, field(:old_id, struct(ShardId), 1), field(:new_id, struct(ShardId), 2)
145
+
146
+ thrift_method :get_forwarding, struct(Forwarding), field(:table_id, i32, 1), field(:base_id, i64, 2)
147
+ thrift_method :get_forwarding_for_shard, struct(Forwarding), field(:shard_id, struct(ShardId), 1)
148
+
149
+ thrift_method :get_forwardings, list(struct(Forwarding))
150
+ thrift_method :reload_forwardings, void
151
+
152
+ thrift_method :find_current_forwarding, struct(ShardInfo), field(:table_id, i32, 1), field(:id, i64, 2)
153
+
154
+ thrift_method :shards_for_hostname, list(struct(ShardInfo)), field(:hostname, string, 1)
155
+ thrift_method :get_busy_shards, list(struct(ShardInfo))
156
+
157
+ thrift_method :rebuild_schema, void
158
+ end
159
+ end
160
+ end
data/lib/gizzmo.rb ADDED
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.dirname(__FILE__)
3
+ class HelpNeededError < RuntimeError; end
4
+ require "optparse"
5
+ require "ostruct"
6
+ require "gizzard"
7
+ require "yaml"
8
+
9
+ ORIGINAL_ARGV = ARGV.dup
10
+
11
+ # Container for parsed options
12
+ global_options = OpenStruct.new
13
+ subcommand_options = OpenStruct.new
14
+
15
+ # Leftover arguments
16
+ argv = nil
17
+
18
+ begin
19
+ YAML.load_file(File.join(ENV["HOME"], ".gizzmorc")).each do |k, v|
20
+ global_options.send("#{k}=", v)
21
+ end
22
+ rescue Errno::ENOENT
23
+ # Do nothing...
24
+ rescue => e
25
+ abort "Unknown error loading ~/.gizzmorc: #{e.message}"
26
+ end
27
+
28
+ subcommands = {
29
+ 'create' => OptionParser.new do |opts|
30
+ opts.banner = "Usage: #{$0} create [options] HOST TABLE_PREFIX CLASS_NAME"
31
+
32
+ opts.on("-s", "--source-type=TYPE") do |s|
33
+ subcommand_options.source_type = s
34
+ end
35
+
36
+ opts.on("-d", "--destination-type=TYPE") do |s|
37
+ subcommand_options.destination_type = s
38
+ end
39
+ end,
40
+ 'wrap' => OptionParser.new do |opts|
41
+ opts.banner = "Usage: #{$0} wrap CLASS_NAME SHARD_ID_TO_WRAP [MORE SHARD_IDS...]"
42
+ end,
43
+ 'subtree' => OptionParser.new do |opts|
44
+ opts.banner = "Usage: #{$0} subtree SHARD_ID"
45
+ end,
46
+ 'delete' => OptionParser.new do |opts|
47
+ opts.banner = "Usage: #{$0} delete SHARD_ID_TO_DELETE [MORE SHARD_IDS]"
48
+ end,
49
+ 'unwrap' => OptionParser.new do |opts|
50
+ opts.banner = "Usage: #{$0} unwrap SHARD_ID_TO_REMOVE [MORE SHARD_IDS]"
51
+ end,
52
+ 'find' => OptionParser.new do |opts|
53
+ opts.banner = "Usage: #{$0} find [options]"
54
+
55
+ opts.on("-t", "--type=TYPE", "Return only shards of the specified TYPE") do |shard_type|
56
+ subcommand_options.shard_type = shard_type
57
+ end
58
+
59
+ opts.on("-H", "--host=HOST", "HOST of shard") do |shard_host|
60
+ subcommand_options.shard_host = shard_host
61
+ end
62
+ end,
63
+ 'links' => OptionParser.new do |opts|
64
+ opts.banner = "Usage: #{$0} links SHARD_ID [MORE SHARD_IDS...]"
65
+ end,
66
+ 'info' => OptionParser.new do |opts|
67
+ opts.banner = "Usage: #{$0} info SHARD_ID [MORE SHARD_IDS...]"
68
+ end,
69
+ 'reload' => OptionParser.new do |opts|
70
+ opts.banner = "Usage: #{$0} reload"
71
+ end,
72
+ 'addlink' => OptionParser.new do |opts|
73
+ opts.banner = "Usage: #{$0} link PARENT_SHARD_ID CHILD_SHARD_ID WEIGHT"
74
+ end,
75
+ 'unlink' => OptionParser.new do |opts|
76
+ opts.banner = "Usage: #{$0} unlink PARENT_SHARD_ID CHILD_SHARD_ID"
77
+ end
78
+ }
79
+
80
+ global = OptionParser.new do |opts|
81
+ opts.banner = "Usage: #{$0} [global-options] SUBCOMMAND [subcommand-options]"
82
+ opts.separator ""
83
+ opts.separator "Subcommands:"
84
+ subcommands.keys.compact.sort.each do |sc|
85
+ opts.separator " #{sc}"
86
+ end
87
+ opts.separator ""
88
+ opts.separator "You can type `#{$0} help SUBCOMMAND` for help on a specific subcommand."
89
+ opts.separator ""
90
+ opts.separator "Global options:"
91
+
92
+ opts.on("-H", "--host=HOSTNAME", "HOSTNAME of remote thrift service") do |host|
93
+ global_options.host = host
94
+ end
95
+
96
+ opts.on("-P", "--port=PORT", "PORT of remote thrift service") do |port|
97
+ global_options.port = port
98
+ end
99
+
100
+ opts.on("-D", "--dry-run", "") do |port|
101
+ global_options.dry = true
102
+ end
103
+
104
+ opts.on("-C", "--config=YAML_FILE", "YAML_FILE of option key/values") do |file|
105
+ YAML.load(File.open(file)).each do |k, v|
106
+ global_options.send("#{k}=", v)
107
+ end
108
+ end
109
+
110
+ opts.on("-L", "--log=LOG_FILE", "Path to LOG_FILE") do |file|
111
+ global_options.log = file
112
+ end
113
+ end
114
+
115
+ # Print banner if no args
116
+ if ARGV.length == 0
117
+ STDERR.puts global
118
+ exit 1
119
+ end
120
+
121
+ # This
122
+ def process_nested_parsers(global, subcommands)
123
+ begin
124
+ global.order!(ARGV) do |subcommand_name|
125
+ # puts args.inspect
126
+ subcommand = subcommands[subcommand_name]
127
+ argv = subcommand ? subcommand.parse!(ARGV) : ARGV
128
+ return subcommand_name, argv
129
+ end
130
+ rescue => e
131
+ STDERR.puts e.message
132
+ exit 1
133
+ end
134
+ end
135
+
136
+
137
+ subcommand_name, argv = process_nested_parsers(global, subcommands)
138
+
139
+ # Print help sub-banners
140
+ if subcommand_name == "help"
141
+ STDERR.puts subcommands[argv.shift] || global
142
+ exit 1
143
+ end
144
+
145
+ unless subcommands.include?(subcommand_name)
146
+ STDERR.puts "Subcommand not found: #{subcommand_name}"
147
+ exit 1
148
+ end
149
+
150
+ log = global_options.log || "/tmp/gizzmo.log"
151
+ service = Gizzard::Thrift::ShardManager.new(global_options.host, global_options.port, log, global_options.dry)
152
+
153
+ begin
154
+ Gizzard::Command.run(subcommand_name, service, global_options, argv, subcommand_options)
155
+ rescue HelpNeededError => e
156
+ if e.class.name != e.message
157
+ STDERR.puts("=" * 80)
158
+ STDERR.puts e.message
159
+ STDERR.puts("=" * 80)
160
+ end
161
+ STDERR.puts subcommands[subcommand_name]
162
+ exit 1
163
+ rescue ThriftClient::Simple::ThriftException => e
164
+ STDERR.puts e.message
165
+ exit 1
166
+ rescue Errno::EPIPE
167
+ # This is just us trying to puts into a closed stdout. For example, if you pipe into
168
+ # head -1, then this script will keep running after head closes. We don't care, and
169
+ # seeing the backtrace is annoying!
170
+ rescue Interrupt
171
+ exit 1
172
+ end
@@ -0,0 +1,334 @@
1
+ require 'socket'
2
+ require 'getoptlong'
3
+
4
+ class ThriftClient
5
+
6
+ # This is a simplified form of thrift, useful for clients only, and not
7
+ # making any attempt to have good performance. It's intended to be used by
8
+ # small command-line tools that don't want to install a dozen ruby files.
9
+ module Simple
10
+ VERSION_1 = 0x8001
11
+
12
+ # message types
13
+ CALL, REPLY, EXCEPTION = (1..3).to_a
14
+
15
+ # value types
16
+ STOP, VOID, BOOL, BYTE, DOUBLE, _, I16, _, I32, _, I64, STRING, STRUCT, MAP, SET, LIST = (0..15).to_a
17
+
18
+ FORMATS = {
19
+ BYTE => "c",
20
+ DOUBLE => "G",
21
+ I16 => "n",
22
+ I32 => "N",
23
+ }
24
+
25
+ SIZES = {
26
+ BYTE => 1,
27
+ DOUBLE => 8,
28
+ I16 => 2,
29
+ I32 => 4,
30
+ }
31
+
32
+ module ComplexType
33
+ module Extends
34
+ def type_id=(n)
35
+ @type_id = n
36
+ end
37
+
38
+ def type_id
39
+ @type_id
40
+ end
41
+ end
42
+
43
+ module Includes
44
+ def to_i
45
+ self.class.type_id
46
+ end
47
+
48
+ def to_s
49
+ args = self.values.map { |v| self.class.type_id == STRUCT ? v.name : v.to_s }.join(", ")
50
+ "#{self.class.name}.new(#{args})"
51
+ end
52
+ end
53
+ end
54
+
55
+ def self.make_type(type_id, name, *args)
56
+ klass = Struct.new("STT_#{name}", *args)
57
+ klass.send(:extend, ComplexType::Extends)
58
+ klass.send(:include, ComplexType::Includes)
59
+ klass.type_id = type_id
60
+ klass
61
+ end
62
+
63
+ ListType = make_type(LIST, "ListType", :element_type)
64
+ MapType = make_type(MAP, "MapType", :key_type, :value_type)
65
+ SetType = make_type(SET, "SetType", :element_type)
66
+ StructType = make_type(STRUCT, "StructType", :struct_class)
67
+
68
+ class << self
69
+ def pack_value(type, value)
70
+ case type
71
+ when BOOL
72
+ [ value ? 1 : 0 ].pack("c")
73
+ when STRING
74
+ [ value.size, value ].pack("Na*")
75
+ when I64
76
+ [ value >> 32, value & 0xffffffff ].pack("NN")
77
+ when ListType
78
+ [ type.element_type.to_i, value.size ].pack("cN") + value.map { |item| pack_value(type.element_type, item) }.join("")
79
+ when MapType
80
+ [ type.key_type.to_i, type.value_type.to_i, value.size ].pack("ccN") + value.map { |k, v| pack_value(type.key_type, k) + pack_value(type.value_type, v) }.join("")
81
+ when SetType
82
+ [ type.element_type.to_i, value.size ].pack("cN") + value.map { |item| pack_value(type.element_type, item) }.join("")
83
+ when StructType
84
+ value._pack
85
+ else
86
+ [ value ].pack(FORMATS[type])
87
+ end
88
+ end
89
+
90
+ def pack_request(method_name, arg_struct, request_id=0)
91
+ [ VERSION_1, CALL, method_name.to_s.size, method_name.to_s, request_id, arg_struct._pack ].pack("nnNa*Na*")
92
+ end
93
+
94
+ def read_value(s, type)
95
+ case type
96
+ when BOOL
97
+ s.read(1).unpack("c").first != 0
98
+ when STRING
99
+ len = s.read(4).unpack("N").first
100
+ s.read(len)
101
+ when I64
102
+ hi, lo = s.read(8).unpack("NN")
103
+ rv = (hi << 32) | lo
104
+ (rv >= (1 << 63)) ? (rv - (1 << 64)) : rv
105
+ when LIST
106
+ read_list(s)
107
+ when MAP
108
+ read_map(s)
109
+ when STRUCT
110
+ read_struct(s, UnknownStruct)
111
+ when ListType
112
+ read_list(s, type.element_type)
113
+ when MapType
114
+ read_map(s, type.key_type, type.value_type)
115
+ when StructType
116
+ read_struct(s, type.struct_class)
117
+ else
118
+ rv = s.read(SIZES[type]).unpack(FORMATS[type]).first
119
+ case type
120
+ when I16
121
+ (rv >= (1 << 15)) ? (rv - (1 << 16)) : rv
122
+ when I32
123
+ (rv >= (1 << 31)) ? (rv - (1 << 32)) : rv
124
+ else
125
+ rv
126
+ end
127
+ end
128
+ end
129
+
130
+ def read_list(s, element_type=nil)
131
+ etype, len = s.read(5).unpack("cN")
132
+ expected_type = (element_type and element_type.to_i == etype.to_i) ? element_type : etype
133
+ rv = []
134
+ len.times do
135
+ rv << read_value(s, expected_type)
136
+ end
137
+ rv
138
+ end
139
+
140
+ def read_map(s, key_type=nil, value_type=nil)
141
+ ktype, vtype, len = s.read(6).unpack("ccN")
142
+ rv = {}
143
+ expected_key_type, expected_value_type = if key_type and value_type and key_type.to_i == ktype and value_type.to_i == vtype
144
+ [ key_type, value_type ]
145
+ else
146
+ [ ktype, vtype ]
147
+ end
148
+ len.times do
149
+ key = read_value(s, expected_key_type)
150
+ value = read_value(s, expected_value_type)
151
+ rv[key] = value
152
+ end
153
+ rv
154
+ end
155
+
156
+ def read_struct(s, struct_class)
157
+ struct = struct_class.new
158
+ while true
159
+ ftype = s.read(1).unpack("c").first
160
+ return struct if ftype == STOP
161
+ fid = s.read(2).unpack("n").first
162
+
163
+ if field = struct_class._fields.find { |f| (f.fid == fid) and (f.type.to_i == ftype) }
164
+ struct[field.name] = read_value(s, field.type)
165
+ else
166
+ $stderr.puts "Warning: Unknown struct field encountered. (recieved id: #{fid})"
167
+ raise "Warning: Unknown struct field encountered. (recieved id: #{fid})"
168
+ read_value(s, ftype)
169
+ end
170
+ end
171
+ end
172
+
173
+ def read_response(s, rv_class)
174
+ version, message_type, method_name_len = s.read(8).unpack("nnN")
175
+ method_name = s.read(method_name_len)
176
+ seq_id = s.read(4).unpack("N").first
177
+ if message_type == EXCEPTION
178
+ exception = read_struct(s, ExceptionStruct)
179
+ raise ThriftException, exception.message
180
+ end
181
+ response = read_struct(s, rv_class)
182
+ raise response.ex if response.respond_to?(:ex) and response.ex
183
+ [ method_name, seq_id, response.rv ]
184
+ end
185
+ end
186
+
187
+ ## ----------------------------------------
188
+
189
+ class Field
190
+ attr_accessor :name, :type, :fid
191
+
192
+ def initialize(name, type, fid)
193
+ @name = name
194
+ @type = type
195
+ @fid = fid
196
+ end
197
+
198
+ def pack(value)
199
+ value.nil? ? "" : [ type.to_i, fid, ThriftClient::Simple.pack_value(type, value) ].pack("cna*")
200
+ end
201
+ end
202
+
203
+ class ThriftException < RuntimeError
204
+ def initialize(reason)
205
+ @reason = reason
206
+ end
207
+
208
+ def to_s
209
+ "ThriftException(#{@reason.inspect})"
210
+ end
211
+ end
212
+
213
+ module ThriftStruct
214
+ module Include
215
+ def _pack
216
+ self.class._fields.map { |f| f.pack(self[f.name]) }.join + [ STOP ].pack("c")
217
+ end
218
+ end
219
+
220
+ module Extend
221
+ def _fields
222
+ @fields
223
+ end
224
+
225
+ def _fields=(f)
226
+ @fields = f
227
+ end
228
+ end
229
+ end
230
+
231
+ def self.make_struct(name, *fields)
232
+ st_name = "ST_#{name.to_s.tr(':', '_')}"
233
+ if Struct.constants.include?(st_name)
234
+ warn "#{caller[0]}: Struct::#{st_name} is already defined; returning original class."
235
+ Struct.const_get(st_name)
236
+ else
237
+ names = fields.map { |f| f.name.to_sym }
238
+ klass = Struct.new(st_name, *names)
239
+ klass.send(:include, ThriftStruct::Include)
240
+ klass.send(:extend, ThriftStruct::Extend)
241
+ klass._fields = fields
242
+ klass
243
+ end
244
+ end
245
+
246
+ def self.make_exception(name, *fields)
247
+ struct_class = self.make_struct(name, *fields)
248
+ ex_class = Class.new(StandardError)
249
+
250
+ (class << struct_class; self end).send(:define_method, :exception_class) { ex_class }
251
+ (class << ex_class; self end).send(:define_method, :struct_class) { struct_class }
252
+
253
+ ex_class.class_eval do
254
+ attr_reader :struct
255
+
256
+ def initialize
257
+ @struct = self.class.struct_class.new
258
+ end
259
+
260
+ def self._fields
261
+ struct_class._fields
262
+ end
263
+
264
+ def to_s
265
+ method = [:message, :description].find {|m| struct.respond_to? m }
266
+ struct.send method || :to_s
267
+ end
268
+
269
+ alias message to_s
270
+
271
+ def method_missing(method, *args)
272
+ struct.send(method, *args)
273
+ end
274
+ end
275
+
276
+ ex_class
277
+ end
278
+
279
+ ExceptionStruct = make_struct(:ProtocolException, Field.new(:message, STRING, 1), Field.new(:type, I32, 2))
280
+ UnknownStruct = make_struct(:Unknown)
281
+
282
+ class ThriftService
283
+ def initialize(host, port)
284
+ @host = host
285
+ @port = port
286
+ end
287
+
288
+ def self._arg_structs
289
+ @_arg_structs = {} if @_arg_structs.nil?
290
+ @_arg_structs
291
+ end
292
+
293
+ def self.thrift_method(name, rtype, *args)
294
+ options = args.last.is_a?(Hash) ? args.pop : {}
295
+ fields = [ ThriftClient::Simple::Field.new(:rv, rtype, 0),
296
+ (options[:throws] ? ThriftClient::Simple::Field.new(:ex, options[:throws], 1) : nil)
297
+ ].compact
298
+
299
+ arg_struct = ThriftClient::Simple.make_struct("Args__#{self.name}__#{name}", *args)
300
+ rv_struct = ThriftClient::Simple.make_struct("Retval__#{self.name}__#{name}", *fields)
301
+
302
+ _arg_structs[name.to_sym] = [ arg_struct, rv_struct ]
303
+
304
+ arg_names = args.map { |a| a.name.to_s }.join(", ")
305
+ class_eval "def #{name}(#{arg_names}); _proxy(:#{name}#{args.size > 0 ? ', ' : ''}#{arg_names}); end"
306
+ end
307
+
308
+ def _proxy(method_name, *args)
309
+ cls = self.class.ancestors.find { |cls| cls.respond_to?(:_arg_structs) and cls._arg_structs[method_name.to_sym] }
310
+ arg_class, rv_class = cls._arg_structs[method_name.to_sym]
311
+ arg_struct = arg_class.new(*args)
312
+ sock = TCPSocket.new(@host, @port)
313
+ sock.write(ThriftClient::Simple.pack_request(method_name, arg_struct))
314
+ rv = ThriftClient::Simple.read_response(sock, rv_class)
315
+ sock.close
316
+ rv[2]
317
+ end
318
+
319
+ # convenience. robey is lazy.
320
+ { :field => "Field.new",
321
+ :struct => "StructType.new",
322
+ :exception => "StructType.new",
323
+ :list => "ListType.new",
324
+ :map => "MapType.new",
325
+ }.each do |new_name, old_name|
326
+ class_eval "def self.#{new_name}(*args); ThriftClient::Simple::#{old_name}(*args); end"
327
+ end
328
+
329
+ # alias exception struct
330
+
331
+ [ :void, :bool, :byte, :double, :i16, :i32, :i64, :string ].each { |sym| class_eval "def self.#{sym}; ThriftClient::Simple::#{sym.to_s.upcase}; end" }
332
+ end
333
+ end
334
+ end