zillabyte-cli 0.0.24 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +6 -14
  2. data/lib/#zillabyte-cli.rb# +5 -0
  3. data/lib/zillabyte/api/apps.rb +16 -132
  4. data/lib/zillabyte/api/components.rb +115 -0
  5. data/lib/zillabyte/api/flows.rb +121 -0
  6. data/lib/zillabyte/api/keys.rb +70 -0
  7. data/lib/zillabyte/api.rb +15 -2
  8. data/lib/zillabyte/auth.rb +43 -16
  9. data/lib/zillabyte/cli/#logs.rb# +12 -0
  10. data/lib/zillabyte/cli/#repl.rb# +43 -0
  11. data/lib/zillabyte/cli/apps.rb +52 -893
  12. data/lib/zillabyte/cli/auth.rb +3 -8
  13. data/lib/zillabyte/cli/base.rb +28 -7
  14. data/lib/zillabyte/cli/components.rb +245 -0
  15. data/lib/zillabyte/cli/flows.rb +549 -0
  16. data/lib/zillabyte/cli/git.rb +38 -0
  17. data/lib/zillabyte/cli/help.rb +11 -4
  18. data/lib/zillabyte/cli/keys.rb +177 -0
  19. data/lib/zillabyte/cli/query.rb +0 -1
  20. data/lib/zillabyte/cli/relations.rb +2 -1
  21. data/lib/zillabyte/cli/templates/{js → apps/js}/simple_function.js +0 -0
  22. data/lib/zillabyte/cli/templates/{js → apps/js}/zillabyte.conf.yaml +0 -0
  23. data/lib/zillabyte/cli/templates/apps/python/app.py +17 -0
  24. data/lib/zillabyte/cli/templates/{python → apps/python}/requirements.txt +0 -0
  25. data/lib/zillabyte/cli/templates/{python → apps/python}/zillabyte.conf.yaml +1 -1
  26. data/lib/zillabyte/cli/templates/{ruby → apps/ruby}/Gemfile +0 -0
  27. data/lib/zillabyte/cli/templates/{ruby → apps/ruby}/app.rb +1 -1
  28. data/lib/zillabyte/cli/templates/{ruby → apps/ruby}/zillabyte.conf.yaml +0 -0
  29. data/lib/zillabyte/cli/templates/python/{simple_function.py → #simple_function.py#} +3 -6
  30. data/lib/zillabyte/common/session.rb +3 -1
  31. data/lib/zillabyte/helpers.rb +64 -1
  32. data/lib/zillabyte/runner/app_runner.rb +226 -0
  33. data/lib/zillabyte/runner/component_operation.rb +529 -0
  34. data/lib/zillabyte/runner/component_runner.rb +244 -0
  35. data/lib/zillabyte/runner/multilang_operation.rb +1133 -0
  36. data/lib/zillabyte/runner/operation.rb +11 -0
  37. data/lib/zillabyte/runner.rb +6 -0
  38. data/lib/zillabyte-cli/version.rb +1 -1
  39. data/zillabyte-cli.gemspec +1 -0
  40. metadata +83 -52
@@ -0,0 +1,529 @@
1
+ require 'json'
2
+ require 'mkfifo'
3
+ require "zillabyte/runner"
4
+ require "zillabyte/runner/multilang_operation"
5
+ require "zillabyte/runner/component_runner"
6
+ require 'zillabyte/api/components'
7
+
8
+
9
+
10
+ # Emulate Component Operations
11
+ class Zillabyte::Runner::ComponentOperation
12
+
13
+ NEXT_MESSAGE = "{\"command\": \"next\"}\n"
14
+ END_CYCLE_MESSAGE = "{\"command\": \"end_cycle\"}\n"
15
+ ENDMARKER = "\nend\n"
16
+
17
+ # Run the operation
18
+ def self.run(node, dir, consumee, consumer_pipes, tester, meta, options = {})
19
+
20
+
21
+ @__node = node
22
+ @__name = node["name"]
23
+ @__type = node["type"]
24
+ @__dir = dir
25
+ @__consumee = consumee
26
+ @__consumer_pipes = consumer_pipes
27
+ @__tester = tester
28
+
29
+ @__meta = meta
30
+ @__options = options
31
+ @__output_type = options[:output_type]
32
+
33
+ # Each consumer of a stream gets its own queue and message passing
34
+ @__emit_queues = {}
35
+ @__consumer_pipes.each_pair do |stream, consumers|
36
+ consumers.each_key do |consumer|
37
+ @__emit_queues[stream] ||= {}
38
+ @__emit_queues[stream][consumer] = {:write_queue => [], :ready => true}
39
+ end
40
+ end
41
+
42
+ begin
43
+ case @__type
44
+ when "source"
45
+ self.run_input()
46
+ when "sink"
47
+ self.run_output()
48
+ when "component"
49
+ self.run_rpc_component()
50
+ else
51
+ Zillabyte::Runner::MultilangOperation.run(node, dir, consumee, consumer_pipes, tester, meta, options)
52
+ end
53
+ rescue => e
54
+ cdisplay e.message
55
+ cdisplay e.backtrace
56
+ end
57
+
58
+ end
59
+
60
+
61
+ # Run a component input
62
+ def self.run_input()
63
+ input = @__options[:input]
64
+
65
+ # Read input from file
66
+ if input
67
+ cdisplay "reading from file...."
68
+ csv_rows = CSV.read("#{input}")
69
+
70
+ else
71
+
72
+ stdin = @__consumee[:rd_child]
73
+ loop do
74
+
75
+ msg = stdin.gets
76
+
77
+ # Build tuple
78
+ begin
79
+ tuple = JSON.parse(msg)
80
+ rescue JSON::ParserError
81
+ cdisplay "Error: invalid JSON"
82
+ next
83
+ end
84
+
85
+ # Check input for correct fields
86
+ has_fields = true
87
+ fields = @__node['fields'].flat_map {|h| h.keys}
88
+ tuple.each_key do |field|
89
+ if !fields.include? field
90
+ cdisplay "Error: invalid schema for node"
91
+ next
92
+ end
93
+ end
94
+ tuple_json = build_tuple_json(tuple)
95
+ display_json = Hash[JSON.parse(tuple_json)["tuple"].map {|k,v| [truncate_message(k), truncate_message(v)]}].to_json
96
+ send_to_consumers(tuple_json)
97
+ end
98
+
99
+ end
100
+
101
+ end
102
+
103
+
104
+ # Send to and manage an RPC component
105
+ def self.run_rpc_component()
106
+
107
+ # Index streams and consumers by their pipes for lookup
108
+ consumer_hash = {}
109
+ @__emit_queues.each_pair do |stream, consumers|
110
+ consumers.each_key do |consumer|
111
+ read_stream = @__consumer_pipes[stream][consumer][:rd_parent]
112
+ consumer_hash[read_stream] = {:stream => stream, :consumer => consumer}
113
+ end
114
+ end
115
+
116
+ # Keep track of how many consumers to handle before exiting
117
+ consumers_running = consumer_hash.keys.length
118
+
119
+ # Begin cycle
120
+ end_cycle_received = false
121
+
122
+
123
+ # TODO multiple inputs
124
+ # The input component(singular at the moment)
125
+ read_streams = consumer_hash.keys.concat [@__consumee[:rd_child]]
126
+
127
+ # Receive and handle messages
128
+ loop do
129
+
130
+ # Read from a stream
131
+ rs = select_read_streams(read_streams)
132
+ rs.each do |r|
133
+ # Read a message
134
+ obj = read_message(r)
135
+
136
+ # Handle tuple through RPC
137
+ if obj['tuple']
138
+
139
+ display_json = Hash[obj['tuple'].map{|k, v| [truncate_message(k), truncate_message(v)]}].to_json
140
+
141
+
142
+ # Start communication with API
143
+ api = "API #{@__tester.session.api}"
144
+ caller = Zillabyte::API::Components.new(api)
145
+ component_id = @__node["id"]
146
+ output_format = @__node["output_format"]
147
+
148
+ # Send RPC call
149
+ tuple = obj['tuple']
150
+ cdisplay "sending RPC call..."
151
+ call_options = {
152
+ :rpc_inputs => [tuple],
153
+ :output_format => output_format
154
+ }
155
+
156
+ res = caller.rpc(component_id, call_options)
157
+ cdisplay "received API response : #{res}"
158
+
159
+ #TODO handle errors
160
+ if res['error']
161
+ cdisplay("error: #{res['error']}")
162
+ next
163
+ end
164
+ run_ids = res['run_ids']
165
+ status = res['status']
166
+
167
+
168
+ # Send result call
169
+ loop do
170
+
171
+ # If a call for results is not ready, save the execution ID for the next cycle
172
+ ids_in_progress = []
173
+ res = caller.get_rpc_results(component_id, {:execute_ids => run_ids})
174
+ if res['error']
175
+ cdisplay("error: #{res['error']}")
176
+ next
177
+ end
178
+
179
+
180
+ # check results
181
+ res['results'].each do |run_id, hash|
182
+
183
+ # We have results
184
+ if hash['status'] == "complete"
185
+
186
+ # Handle Tuple in here
187
+ data_streams = hash['data']
188
+
189
+ # if stream_hash.nil?
190
+ # cdisplay("error : no results!")
191
+ # next
192
+ # else
193
+ # stream_hash.each_pair
194
+ # end
195
+
196
+ # Handle each resulting tuple
197
+ data_streams.each_pair do |stream, data_tuples|
198
+ data_tuples.each do |data_tuple|
199
+ tuple_json = build_tuple_json(data_tuple)
200
+
201
+ #send or enqueue the tuple to all consumers of the stream
202
+ @__emit_queues.each_pair do |stream, consumers|
203
+ consumers.each_pair do |consumer, emitter|
204
+ if emitter[:ready]
205
+ emit_consumer_tuple(stream, consumer, tuple_json)
206
+ else
207
+ @__emit_queues[stream][consumer][:write_queue] << tuple_json
208
+ end
209
+ end
210
+ end
211
+
212
+ end
213
+ end
214
+
215
+ else
216
+ cdisplay "RPC #{run_id} has not completed... "
217
+ ids_in_progress << run_id
218
+ end
219
+ end
220
+ run_ids = ids_in_progress
221
+
222
+ # If no more IDs to run, we are done
223
+ if run_ids.empty?
224
+ break
225
+ end
226
+
227
+ # Dont spam the API
228
+ sleep(2)
229
+ end
230
+
231
+ # Ask for next tuple
232
+ write_message(@__consumee[:wr_child], NEXT_MESSAGE)
233
+
234
+
235
+ # End cycle
236
+ elsif obj['command']
237
+ case obj["command"]
238
+
239
+ # Consumer is ready for a message
240
+ when "next"
241
+ stream = consumer_hash[r][:stream]
242
+ consumer = consumer_hash[r][:consumer]
243
+
244
+ @__emit_queues[stream][consumer][:ready] = true
245
+ tuple_json = get_consumer_tuple(stream, consumer)
246
+
247
+ # End cycle for consumer if it has processed all tuples
248
+ if tuple_json.nil? && end_cycle_received
249
+ write_stream = get_write_stream(stream, consumer)
250
+ write_message(write_stream, END_CYCLE_MESSAGE)
251
+ consumers_running -= 1
252
+ if consumers_running == 0
253
+ break
254
+ end
255
+
256
+ # TODO break if last consumer
257
+ elsif !tuple_json.nil?
258
+ # Emit tuple to consumer
259
+ emit_consumer_tuple(stream, consumer, tuple_json)
260
+ emitted = true
261
+ end
262
+
263
+ when "end_cycle"
264
+ end_cycle_received = true
265
+ end
266
+ end
267
+
268
+ end
269
+
270
+ # Exit after ending consumer cycles
271
+ if consumers_running == 0
272
+ break
273
+ end
274
+ end
275
+
276
+ end
277
+
278
+ # Run a component output
279
+ def self.run_output()
280
+
281
+
282
+ output = @__options[:output]
283
+ header_written = false
284
+ read_stream = @__consumee[:rd_child]
285
+
286
+ loop do
287
+ # Read a tuple
288
+ obj = read_message(read_stream)
289
+
290
+
291
+ # Add row
292
+ if obj['tuple']
293
+
294
+ if output
295
+ filename = "#{output}.csv"
296
+ t = obj['tuple']
297
+ m = obj['meta'] || {}
298
+
299
+ # Write header
300
+ if header_written == false
301
+ keys = [t.keys, m.keys].flatten
302
+ csv_str = keys
303
+ f = File.open(filename, "w")
304
+ f.write(csv_str)
305
+ f.close()
306
+ header_written = true
307
+ end
308
+
309
+ # Write CSV row
310
+ vals = [t.values, m.values].flatten
311
+ csv_str = vals.to_csv
312
+
313
+ f = File.open(filename, "w")
314
+ f.write(csv_str)
315
+ f.close()
316
+ else
317
+
318
+ display_json = Hash[obj['tuple'].map{|k, v| [truncate_message(k), truncate_message(v)]}].to_json
319
+ cdisplay "received #{display_json}"
320
+ end
321
+
322
+ # Ready for next message
323
+ write_message(@__consumee[:wr_child], NEXT_MESSAGE)
324
+
325
+ # End cycle
326
+ elsif obj['command'] && obj['command'] == "end_cycle"
327
+ break
328
+ end
329
+ end
330
+
331
+ end
332
+
333
+
334
+ private
335
+
336
+ BUFSIZE = 8192
337
+
338
+ # Each reading pipe has a read buffer and message queue
339
+ @__read_buffers = {}
340
+ @__read_buffered_messages = {}
341
+
342
+
343
+ # Return availible reading streams
344
+ def self.select_read_streams(read_streams)
345
+
346
+ rs = []
347
+ read_streams.each do |read_stream|
348
+ @__read_buffered_messages[read_stream] ||= []
349
+ if !@__read_buffered_messages[read_stream].empty?
350
+ rs << read_stream
351
+ end
352
+ end
353
+ return rs unless rs.empty?
354
+
355
+ rs, ws, es = IO.select(read_streams, [], [])
356
+ return rs
357
+ end
358
+
359
+
360
+ # Read a JSON message
361
+ def self.read_message(read_stream)
362
+
363
+ @__read_buffers[read_stream] ||= ""
364
+ @__read_buffered_messages[read_stream] ||= []
365
+ if !@__read_buffered_messages[read_stream].empty?
366
+ obj = @__read_buffered_messages[read_stream].shift
367
+ return obj
368
+ end
369
+
370
+ # read message from stream
371
+ loop do
372
+
373
+ while !@__read_buffers[read_stream].include? ENDMARKER
374
+ segment = read_stream.sysread(BUFSIZE)
375
+ @__read_buffers[read_stream] << segment
376
+ end
377
+
378
+ read_buffer = @__read_buffers[read_stream]
379
+ if read_buffer.include? ENDMARKER
380
+ objs = read_buffer.split(ENDMARKER)
381
+ ends = read_buffer.scan(ENDMARKER)
382
+ if objs.count == ends.count # We have a full number of messages
383
+ objs.each do |obj|
384
+ begin
385
+ @__read_buffered_messages[read_stream] << JSON.parse(obj)
386
+ rescue JSON::ParserError
387
+ cdisplay "READMESSAGE: invalid JSON #{obj}"
388
+ end
389
+ end
390
+ @__read_buffers[read_stream] = ""
391
+ return @__read_buffered_messages[read_stream].shift
392
+ else
393
+
394
+ (0..ends.count-1).each do |i|
395
+ obj = objs[i]
396
+ begin
397
+ @__read_buffered_messages[read_stream] << JSON.parse(obj)
398
+ rescue JSON::ParserError
399
+ cdisplay "READMESSAGE: invalid JSON #{obj}"
400
+ end
401
+ end
402
+
403
+ @__read_buffers[read_stream] = objs[ends.count..-1].join(ENDMARKER)
404
+ return @__read_buffered_messages[read_stream].shift
405
+ end
406
+ end
407
+ end
408
+ end
409
+
410
+
411
+ # Write JSON message
412
+ def self.write_message(write_stream, msg)
413
+ write_msg = msg.strip + ENDMARKER
414
+ write_stream.write write_msg
415
+ write_stream.flush
416
+ end
417
+
418
+ # Format a message for display
419
+ def self.truncate_message(msg)
420
+ return msg if(!msg.instance_of?(String))
421
+ t_length = 50 # truncates entries to this length
422
+ m_length = msg.length
423
+ msg_out = m_length > t_length ? msg[0..t_length-3]+"..." : msg
424
+ msg_out
425
+ end
426
+
427
+
428
+ # Handshake connection to multilang
429
+ def self.handshake(write_stream, read_stream)
430
+ begin
431
+ write_message write_stream, HANDSHAKE_MESSAGE
432
+ msg = read_message(read_stream)
433
+ rescue Exception => e
434
+ cdisplay(e)
435
+ cdisplay("Error handshaking node")
436
+ raise e
437
+ end
438
+ end
439
+
440
+ # Send object to every consumer of the operation, regardless of stream
441
+ def self.send_to_consumers(json_obj)
442
+ @__consumer_pipes.each_pair do |stream, consumers|
443
+ consumers.each_pair do |consumer, pipe|
444
+ write_message(pipe[:wr_parent], json_obj)
445
+ cdisplay "emitted #{json_obj} to #{consumer}"
446
+ end
447
+ end
448
+ end
449
+
450
+
451
+ # Get the write pipe of the stream consumer
452
+ def self.get_write_stream(stream, consumer)
453
+ @__consumer_pipes[stream][consumer][:wr_parent]
454
+ end
455
+
456
+
457
+ # Get tuple for sending to consumer of stream
458
+ def self.get_consumer_tuple(stream, consumer)
459
+ @__emit_queues[stream][consumer][:write_queue].shift
460
+ end
461
+
462
+
463
+ # Emit tuple_json to the consumer of a stream
464
+ def self.emit_consumer_tuple(stream, consumer, tuple_json)
465
+ begin
466
+ display_json = Hash[JSON.parse(tuple_json)["tuple"].map {|k,v| [truncate_message(k), truncate_message(v)]}].to_json
467
+ rescue JSON::ParserError
468
+ cdisplay "Error: invalid JSON"
469
+ end
470
+ write_stream = get_write_stream(stream, consumer)
471
+ write_message(write_stream, tuple_json)
472
+ @__emit_queues[stream][consumer][:ready] = false
473
+ cdisplay "emitted tuple #{display_json} to #{consumer} "
474
+ end
475
+
476
+
477
+ # Build a tuple and format into JSON
478
+ def self.build_tuple_json(tuple, meta = nil, column_aliases = nil)
479
+ meta ||= {}
480
+ column_aliases ||= {}
481
+ values = {}
482
+ tuple.each do |k, v|
483
+ if(k == "id")
484
+ nextx
485
+ elsif(k == "confidence" or k == "since" or k == "source")
486
+ meta[k] = v
487
+ else
488
+ values[k] = v
489
+ end
490
+ end
491
+ tuple_json = {"tuple" => values, "meta" => meta, "column_aliases" => column_aliases}.to_json
492
+ return tuple_json
493
+ end
494
+
495
+
496
+ # Construct a multilang command
497
+ def self.command(arg, ignore_stderr=false)
498
+ cdisplay("could not extract meta information. missing zillabyte.conf.yml?") if @__meta.nil?
499
+
500
+ full_script = File.join(@__dir, @__meta["script"])
501
+ stderr_opt = "2> /dev/null" if ignore_stderr
502
+
503
+ case @__meta["language"]
504
+ when "ruby"
505
+ # Execute in the bundler context
506
+ cmd = "cd \"#{@__dir}\"; unset BUNDLE_GEMFILE; ZILLABYTE_HARNESS=1 bundle exec ruby \"#{full_script}\" #{arg} #{stderr_opt}"
507
+ when "python"#{
508
+ if(File.directory?("#{@__dir}/vEnv"))
509
+ cmd = "cd \"#{@__dir}\"; PYTHONPATH=~/zb1/multilang/python/Zillabyte #{@__dir}/vEnv/bin/python \"#{full_script}\" #{arg} #{stderr_opt}"
510
+ else
511
+ cmd = "cd \"#{@__dir}\"; PYTHONPATH=~/zb1/multilang/python/Zillabyte python \"#{full_script}\" #{arg} #{stderr_opt}"
512
+ end
513
+ when "js"
514
+ cmd = "cd \"#{@__dir}\"; NODE_PATH=~/zb1/multilang/js/src/lib #{Zillabyte::API::NODEJS_BIN} \"#{full_script}\" #{arg} #{stderr_opt}"
515
+ else
516
+ cdisplay("no language specified")
517
+ end
518
+ return cmd
519
+ end
520
+
521
+
522
+ # Display a colored, formatted message
523
+ def self.cdisplay(msg)
524
+
525
+ @__tester.cdisplay(@__name, msg)
526
+ end
527
+
528
+
529
+ end