zillabyte-cli 0.0.24 → 0.1.0

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 (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