td-client 0.8.67 → 0.8.68

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 13c565f827556460635d600c91a409818904e256
4
- data.tar.gz: 2ff8ec8823177a04c69dd767040fc844dcf47e9c
3
+ metadata.gz: a6f4ce3a275f3c038d731feb43dda2ec4cc477e5
4
+ data.tar.gz: b6affcd7ad8e6430af814d9459cfbabde9ae50f9
5
5
  SHA512:
6
- metadata.gz: e45453b9ce95aa5054436ce1131c66c36b35b8a85e91c7b49df632b4cf940ce8969c8325f6d5bb1d6d686529e609f7cb28cd626d60de2ebb83d30074caad94b1
7
- data.tar.gz: c0c9b534f3d0e33947387d3a73394e281e61f74b2f896220b4e9dba2a87166059d40788c8a9cc7951e8cfb39d698d987d5bc2fb09875cc87cab073a84da36888
6
+ metadata.gz: 3127bd887d7fff303cde39f92932d8d9a263cfb37201e3c299a24e53a3767390a70ce4195fe34ce9003ecd815465a0776e3e094cc1c35d3da37f181bc4273c98
7
+ data.tar.gz: d002d279379a09a63c10882503cf09f6d25d7e3d12198281c55e82881d8f1ef5a7e2ca2af3cb66856b89d8056667da7003a5172f91d424e87fba8a1c625ddcdf
data/lib/td/client.rb CHANGED
@@ -287,8 +287,18 @@ class Client
287
287
  def history(name, from=nil, to=nil)
288
288
  result = @api.history(name, from, to)
289
289
  result.map {|scheduled_at,job_id,type,status,query,start_at,end_at,result_url,priority,database|
290
- # TODO org
291
- ScheduledJob.new(self, scheduled_at, job_id, type, query, status, nil, nil, start_at, end_at, nil, result_url, nil, priority, nil, nil, database)
290
+ job_param = [job_id, type, query, status,
291
+ nil, nil, # url, debug
292
+ start_at, end_at,
293
+ nil, # cpu_time
294
+ nil, nil, # result_size, result
295
+ result_url,
296
+ nil, # hive_result_schema
297
+ priority,
298
+ nil, # retry_limit
299
+ nil, # TODO org_name
300
+ database]
301
+ ScheduledJob.new(self, scheduled_at, *job_param)
292
302
  }
293
303
  end
294
304
 
data/lib/td/client/api.rb CHANGED
@@ -1,32 +1,39 @@
1
+ require 'td/client/api_error'
1
2
  require 'td/client/version'
3
+ require 'td/client/api/access_control'
4
+ require 'td/client/api/account'
5
+ require 'td/client/api/bulk_import'
6
+ require 'td/client/api/database'
7
+ require 'td/client/api/export'
8
+ require 'td/client/api/import'
9
+ require 'td/client/api/job'
10
+ require 'td/client/api/partial_delete'
11
+ require 'td/client/api/result'
12
+ require 'td/client/api/schedule'
13
+ require 'td/client/api/server_status'
14
+ require 'td/client/api/table'
15
+ require 'td/client/api/user'
16
+
17
+ # For disabling SSLv3 connection in favor of POODLE Attack protection
18
+ require 'td/core_ext/openssl/ssl/sslcontext/set_params'
2
19
 
3
20
  module TreasureData
4
21
 
5
- class ParameterValidationError < StandardError
6
- end
7
-
8
- # Generic API error
9
- class APIError < StandardError
10
- end
11
-
12
- # 401 API errors
13
- class AuthError < APIError
14
- end
15
-
16
- # 403 API errors, used for database permissions
17
- class ForbiddenError < APIError
18
- end
19
-
20
- # 409 API errors
21
- class AlreadyExistsError < APIError
22
- end
23
-
24
- # 404 API errors
25
- class NotFoundError < APIError
26
- end
27
-
28
-
29
22
  class API
23
+ include API::AccessControl
24
+ include API::Account
25
+ include API::BulkImport
26
+ include API::Database
27
+ include API::Export
28
+ include API::Import
29
+ include API::Job
30
+ include API::PartialDelete
31
+ include API::Result
32
+ include API::Schedule
33
+ include API::ServerStatus
34
+ include API::Table
35
+ include API::User
36
+
30
37
  DEFAULT_ENDPOINT = 'api.treasure-data.com'
31
38
  DEFAULT_IMPORT_ENDPOINT = 'api-import.treasure-data.com'
32
39
 
@@ -42,6 +49,10 @@ class API
42
49
  require 'time'
43
50
  #require 'faraday' # faraday doesn't support streaming upload with httpclient yet so now disabled
44
51
  require 'httpclient'
52
+ require 'zlib'
53
+ require 'stringio'
54
+ require 'cgi'
55
+ require 'msgpack'
45
56
 
46
57
  @apikey = apikey
47
58
  @user_agent = "TD-Client-Ruby: #{TreasureData::Client::VERSION}"
@@ -195,1006 +206,17 @@ class API
195
206
 
196
207
  # for fluent-plugin-td / td command to check table existence with import onlt user
197
208
  def self.create_empty_gz_data
198
- require 'zlib'
199
- require 'stringio'
200
-
201
209
  io = StringIO.new
202
210
  Zlib::GzipWriter.new(io).close
203
211
  io.string
204
212
  end
205
213
 
206
- ####
207
- ## Account API
208
- ##
209
-
210
- def show_account
211
- code, body, res = get("/v3/account/show")
212
- if code != "200"
213
- raise_error("Show account failed", res)
214
- end
215
- js = checked_json(body, %w[account])
216
- a = js["account"]
217
- account_id = a['id'].to_i
218
- plan = a['plan'].to_i
219
- storage_size = a['storage_size'].to_i
220
- guaranteed_cores = a['guaranteed_cores'].to_i
221
- maximum_cores = a['maximum_cores'].to_i
222
- created_at = a['created_at']
223
- return [account_id, plan, storage_size, guaranteed_cores, maximum_cores, created_at]
224
- end
225
-
226
- def account_core_utilization(from, to)
227
- params = { }
228
- params['from'] = from.to_s if from
229
- params['to'] = to.to_s if to
230
- code, body, res = get("/v3/account/core_utilization", params)
231
- if code != "200"
232
- raise_error("Show account failed", res)
233
- end
234
- js = checked_json(body, %w[from to interval history])
235
- from = Time.parse(js['from']).utc
236
- to = Time.parse(js['to']).utc
237
- interval = js['interval'].to_i
238
- history = js['history']
239
- return [from, to, interval, history]
240
- end
241
-
242
-
243
- ####
244
- ## Database API
245
- ##
246
-
247
- # => [name:String]
248
- def list_databases
249
- code, body, res = get("/v3/database/list")
250
- if code != "200"
251
- raise_error("List databases failed", res)
252
- end
253
- js = checked_json(body, %w[databases])
254
- result = {}
255
- js["databases"].each {|m|
256
- name = m['name']
257
- count = m['count']
258
- created_at = m['created_at']
259
- updated_at = m['updated_at']
260
- permission = m['permission']
261
- result[name] = [count, created_at, updated_at, nil, permission] # set nil to org for API compatibiilty
262
- }
263
- return result
264
- end
265
-
266
- # => true
267
- def delete_database(db)
268
- code, body, res = post("/v3/database/delete/#{e db}")
269
- if code != "200"
270
- raise_error("Delete database failed", res)
271
- end
272
- return true
273
- end
274
-
275
- # => true
276
- def create_database(db, opts={})
277
- params = opts.dup
278
- code, body, res = post("/v3/database/create/#{e db}", params)
279
- if code != "200"
280
- raise_error("Create database failed", res)
281
- end
282
- return true
283
- end
284
-
285
-
286
- ####
287
- ## Table API
288
- ##
289
-
290
- # => {name:String => [type:Symbol, count:Integer]}
291
- def list_tables(db)
292
- code, body, res = get("/v3/table/list/#{e db}")
293
- if code != "200"
294
- raise_error("List tables failed", res)
295
- end
296
- js = checked_json(body, %w[tables])
297
- result = {}
298
- js["tables"].map {|m|
299
- name = m['name']
300
- type = (m['type'] || '?').to_sym
301
- count = (m['count'] || 0).to_i # TODO?
302
- created_at = m['created_at']
303
- updated_at = m['updated_at']
304
- last_import = m['counter_updated_at']
305
- last_log_timestamp = m['last_log_timestamp']
306
- estimated_storage_size = m['estimated_storage_size'].to_i
307
- schema = JSON.parse(m['schema'] || '[]')
308
- expire_days = m['expire_days']
309
- primary_key = m['primary_key']
310
- primary_key_type = m['primary_key_type']
311
- result[name] = [type, schema, count, created_at, updated_at, estimated_storage_size, last_import, last_log_timestamp, expire_days, primary_key, primary_key_type]
312
- }
313
- return result
314
- end
315
-
316
- def create_log_or_item_table(db, table, type)
317
- code, body, res = post("/v3/table/create/#{e db}/#{e table}/#{type}")
318
- if code != "200"
319
- raise_error("Create #{type} table failed", res)
320
- end
321
- return true
322
- end
323
- private :create_log_or_item_table
324
-
325
- # => true
326
- def create_log_table(db, table)
327
- create_table(db, table, :log)
328
- end
329
-
330
- # => true
331
- def create_item_table(db, table, primary_key, primary_key_type)
332
- params = {'primary_key' => primary_key, 'primary_key_type' => primary_key_type}
333
- create_table(db, table, :item, params)
334
- end
335
-
336
- def create_table(db, table, type, params={})
337
- schema = schema.to_s
338
- code, body, res = post("/v3/table/create/#{e db}/#{e table}/#{type}", params)
339
- if code != "200"
340
- raise_error("Create #{type} table failed", res)
341
- end
342
- return true
343
- end
344
- private :create_table
345
-
346
- # => true
347
- def swap_table(db, table1, table2)
348
- code, body, res = post("/v3/table/swap/#{e db}/#{e table1}/#{e table2}")
349
- if code != "200"
350
- raise_error("Swap tables failed", res)
351
- end
352
- return true
353
- end
354
-
355
- # => true
356
- def update_schema(db, table, schema_json)
357
- code, body, res = post("/v3/table/update-schema/#{e db}/#{e table}", {'schema'=>schema_json})
358
- if code != "200"
359
- raise_error("Create schema table failed", res)
360
- end
361
- return true
362
- end
363
-
364
- def update_expire(db, table, expire_days)
365
- code, body, res = post("/v3/table/update/#{e db}/#{e table}", {'expire_days'=>expire_days})
366
- if code != "200"
367
- raise_error("Update table expiration failed", res)
368
- end
369
- return true
370
- end
371
-
372
- # => type:Symbol
373
- def delete_table(db, table)
374
- code, body, res = post("/v3/table/delete/#{e db}/#{e table}")
375
- if code != "200"
376
- raise_error("Delete table failed", res)
377
- end
378
- js = checked_json(body, %w[])
379
- type = (js['type'] || '?').to_sym
380
- return type
381
- end
382
-
383
- def tail(db, table, count, to, from, &block)
384
- params = {'format' => 'msgpack'}
385
- params['count'] = count.to_s if count
386
- params['to'] = to.to_s if to
387
- params['from'] = from.to_s if from
388
- code, body, res = get("/v3/table/tail/#{e db}/#{e table}", params)
389
- if code != "200"
390
- raise_error("Tail table failed", res)
391
- end
392
- require 'msgpack'
393
- if block
394
- MessagePack::Unpacker.new.feed_each(body, &block)
395
- nil
396
- else
397
- result = []
398
- MessagePack::Unpacker.new.feed_each(body) {|row|
399
- result << row
400
- }
401
- return result
402
- end
403
- end
404
-
405
-
406
- ####
407
- ## Job API
408
- ##
409
-
410
- # => [(jobId:String, type:Symbol, status:String, start_at:String, end_at:String, result_url:String)]
411
- def list_jobs(from=0, to=nil, status=nil, conditions=nil)
412
- params = {}
413
- params['from'] = from.to_s if from
414
- params['to'] = to.to_s if to
415
- params['status'] = status.to_s if status
416
- params.merge!(conditions) if conditions
417
- code, body, res = get("/v3/job/list", params)
418
- if code != "200"
419
- raise_error("List jobs failed", res)
420
- end
421
- js = checked_json(body, %w[jobs])
422
- result = []
423
- js['jobs'].each {|m|
424
- job_id = m['job_id']
425
- type = (m['type'] || '?').to_sym
426
- database = m['database']
427
- status = m['status']
428
- query = m['query']
429
- start_at = m['start_at']
430
- end_at = m['end_at']
431
- cpu_time = m['cpu_time']
432
- result_size = m['result_size'] # compressed result size in msgpack.gz format
433
- result_url = m['result']
434
- priority = m['priority']
435
- retry_limit = m['retry_limit']
436
- result << [job_id, type, status, query, start_at, end_at, cpu_time,
437
- result_size, result_url, priority, retry_limit, nil, database]
438
- }
439
- return result
440
- end
441
-
442
- # => (type:Symbol, status:String, result:String, url:String, result:String)
443
- def show_job(job_id)
444
- # use v3/job/status instead of v3/job/show to poll finish of a job
445
- code, body, res = get("/v3/job/show/#{e job_id}")
446
- if code != "200"
447
- raise_error("Show job failed", res)
448
- end
449
- js = checked_json(body, %w[status])
450
- # TODO debug
451
- type = (js['type'] || '?').to_sym # TODO
452
- database = js['database']
453
- query = js['query']
454
- status = js['status']
455
- debug = js['debug']
456
- url = js['url']
457
- start_at = js['start_at']
458
- end_at = js['end_at']
459
- cpu_time = js['cpu_time']
460
- result_size = js['result_size'] # compressed result size in msgpack.gz format
461
- result = js['result'] # result target URL
462
- hive_result_schema = (js['hive_result_schema'] || '')
463
- if hive_result_schema.empty?
464
- hive_result_schema = nil
465
- else
466
- begin
467
- hive_result_schema = JSON.parse(hive_result_schema)
468
- rescue JSON::ParserError => e
469
- # this is a workaround for a Known Limitation in the Pig Engine which does not set a default, auto-generated
470
- # column name for anonymous columns (such as the ones that are generated from UDF like COUNT or SUM).
471
- # The schema will contain 'nil' for the name of those columns and that breaks the JSON parser since it violates
472
- # the JSON syntax standard.
473
- if type == :pig and hive_result_schema !~ /[\{\}]/
474
- begin
475
- # NOTE: this works because a JSON 2 dimensional array is the same as a Ruby one.
476
- # Any change in the format for the hive_result_schema output may cause a syntax error, in which case
477
- # this lame attempt at fixing the problem will fail and we will be raising the original JSON exception
478
- hive_result_schema = eval(hive_result_schema)
479
- rescue SyntaxError => ignored_e
480
- raise e
481
- end
482
- hive_result_schema.each_with_index {|col_schema, idx|
483
- if col_schema[0].nil?
484
- col_schema[0] = "_col#{idx}"
485
- end
486
- }
487
- else
488
- raise e
489
- end
490
- end
491
- end
492
- priority = js['priority']
493
- retry_limit = js['retry_limit']
494
- return [type, query, status, url, debug, start_at, end_at, cpu_time,
495
- result_size, result, hive_result_schema, priority, retry_limit, nil, database]
496
- end
497
-
498
- def job_status(job_id)
499
- code, body, res = get("/v3/job/status/#{e job_id}")
500
- if code != "200"
501
- raise_error("Get job status failed", res)
502
- end
503
-
504
- js = checked_json(body, %w[status])
505
- return js['status']
506
- end
507
-
508
- def job_result(job_id)
509
- require 'msgpack'
510
- code, body, res = get("/v3/job/result/#{e job_id}", {'format'=>'msgpack'})
511
- if code != "200"
512
- raise_error("Get job result failed", res)
513
- end
514
- result = []
515
- MessagePack::Unpacker.new.feed_each(body) {|row|
516
- result << row
517
- }
518
- return result
519
- end
520
-
521
- # block is optional and must accept 1 parameter
522
- def job_result_format(job_id, format, io=nil, &block)
523
- if io
524
- code, body, res = get("/v3/job/result/#{e job_id}", {'format'=>format}) {|res|
525
- if res.code != "200"
526
- raise_error("Get job result failed", res)
527
- end
528
-
529
- if ce = res.header['Content-Encoding']
530
- require 'zlib'
531
- res.extend(DeflateReadBodyMixin)
532
- res.gzip = true if ce == 'gzip'
533
- else
534
- res.extend(DirectReadBodyMixin)
535
- end
536
-
537
- res.extend(DirectReadBodyMixin)
538
- if ce = res.header['Content-Encoding']
539
- if ce == 'gzip'
540
- infl = Zlib::Inflate.new(Zlib::MAX_WBITS + 16)
541
- else
542
- infl = Zlib::Inflate.new
543
- end
544
- end
545
-
546
- total_compr_size = 0
547
- res.each_fragment {|fragment|
548
- total_compr_size += fragment.size
549
- # uncompressed if the 'Content-Enconding' header is set in response
550
- fragment = infl.inflate(fragment) if ce
551
- io.write(fragment)
552
- block.call(total_compr_size) if block_given?
553
- }
554
- }
555
- nil
556
- else
557
- code, body, res = get("/v3/job/result/#{e job_id}", {'format'=>format})
558
- if res.code != "200"
559
- raise_error("Get job result failed", res)
560
- end
561
- body
562
- end
563
- end
564
-
565
- # block is optional and must accept 1 argument
566
- def job_result_each(job_id, &block)
567
- require 'msgpack'
568
- get("/v3/job/result/#{e job_id}", {'format'=>'msgpack'}) {|res|
569
- if res.code != "200"
570
- raise_error("Get job result failed", res)
571
- end
572
-
573
- # default to decompressing the response since format is fixed to 'msgpack'
574
- res.extend(DeflateReadBodyMixin)
575
- res.gzip = (res.header['Content-Encoding'] == 'gzip')
576
- upkr = MessagePack::Unpacker.new
577
- res.each_fragment {|inflated_fragment|
578
- upkr.feed_each(inflated_fragment, &block)
579
- }
580
- }
581
- nil
582
- end
583
-
584
- # block is optional and must accept 1 argument
585
- def job_result_each_with_compr_size(job_id, &block)
586
- require 'zlib'
587
- require 'msgpack'
588
-
589
- get("/v3/job/result/#{e job_id}", {'format'=>'msgpack'}) {|res|
590
- if res.code != "200"
591
- raise_error("Get job result failed", res)
592
- end
593
-
594
- res.extend(DirectReadBodyMixin)
595
- if res.header['Content-Encoding'] == 'gzip'
596
- infl = Zlib::Inflate.new(Zlib::MAX_WBITS + 16)
597
- else
598
- infl = Zlib::Inflate.new
599
- end
600
- upkr = MessagePack::Unpacker.new
601
- begin
602
- total_compr_size = 0
603
- res.each_fragment {|fragment|
604
- total_compr_size += fragment.size
605
- upkr.feed_each(infl.inflate(fragment)) {|unpacked|
606
- block.call(unpacked, total_compr_size) if block_given?
607
- }
608
- }
609
- ensure
610
- infl.close
611
- end
612
- }
613
- nil
614
- end
615
-
616
- def job_result_raw(job_id, format)
617
- code, body, res = get("/v3/job/result/#{e job_id}", {'format'=>format})
618
- if code != "200"
619
- raise_error("Get job result failed", res)
620
- end
621
- return body
622
- end
623
-
624
- def kill(job_id)
625
- code, body, res = post("/v3/job/kill/#{e job_id}")
626
- if code != "200"
627
- raise_error("Kill job failed", res)
628
- end
629
- js = checked_json(body, %w[])
630
- former_status = js['former_status']
631
- return former_status
632
- end
633
-
634
- # => jobId:String
635
- def hive_query(q, db=nil, result_url=nil, priority=nil, retry_limit=nil, opts={})
636
- query(q, :hive, db, result_url, priority, retry_limit, opts)
637
- end
638
-
639
- # => jobId:String
640
- def pig_query(q, db=nil, result_url=nil, priority=nil, retry_limit=nil, opts={})
641
- query(q, :pig, db, result_url, priority, retry_limit, opts)
642
- end
643
-
644
- # => jobId:String
645
- def query(q, type=:hive, db=nil, result_url=nil, priority=nil, retry_limit=nil, opts={})
646
- params = {'query' => q}.merge(opts)
647
- params['result'] = result_url if result_url
648
- params['priority'] = priority if priority
649
- params['retry_limit'] = retry_limit if retry_limit
650
- code, body, res = post("/v3/job/issue/#{type}/#{e db}", params)
651
- if code != "200"
652
- raise_error("Query failed", res)
653
- end
654
- js = checked_json(body, %w[job_id])
655
- return js['job_id'].to_s
656
- end
657
-
658
- ####
659
- ## Export API
660
- ##
661
-
662
- # => jobId:String
663
- def export(db, table, storage_type, opts={})
664
- params = opts.dup
665
- params['storage_type'] = storage_type
666
- code, body, res = post("/v3/export/run/#{e db}/#{e table}", params)
667
- if code != "200"
668
- raise_error("Export failed", res)
669
- end
670
- js = checked_json(body, %w[job_id])
671
- return js['job_id'].to_s
672
- end
673
-
674
-
675
- ####
676
- ## Partial delete API
677
- ##
678
-
679
- def partial_delete(db, table, to, from, opts={})
680
- params = opts.dup
681
- params['to'] = to.to_s
682
- params['from'] = from.to_s
683
- code, body, res = post("/v3/table/partialdelete/#{e db}/#{e table}", params)
684
- if code != "200"
685
- raise_error("Partial delete failed", res)
686
- end
687
- js = checked_json(body, %w[job_id])
688
- return js['job_id'].to_s
689
- end
690
-
691
- ####
692
- ## Bulk import API
693
- ##
694
-
695
- # => nil
696
- def create_bulk_import(name, db, table, opts={})
697
- params = opts.dup
698
- code, body, res = post("/v3/bulk_import/create/#{e name}/#{e db}/#{e table}", params)
699
- if code != "200"
700
- raise_error("Create bulk import failed", res)
701
- end
702
- return nil
703
- end
704
-
705
- # => nil
706
- def delete_bulk_import(name, opts={})
707
- params = opts.dup
708
- code, body, res = post("/v3/bulk_import/delete/#{e name}", params)
709
- if code != "200"
710
- raise_error("Delete bulk import failed", res)
711
- end
712
- return nil
713
- end
714
-
715
- # => data:Hash
716
- def show_bulk_import(name)
717
- code, body, res = get("/v3/bulk_import/show/#{name}")
718
- if code != "200"
719
- raise_error("Show bulk import failed", res)
720
- end
721
- js = checked_json(body, %w[status])
722
- return js
723
- end
724
-
725
- # => result:[data:Hash]
726
- def list_bulk_imports(opts={})
727
- params = opts.dup
728
- code, body, res = get("/v3/bulk_import/list", params)
729
- if code != "200"
730
- raise_error("List bulk imports failed", res)
731
- end
732
- js = checked_json(body, %w[bulk_imports])
733
- return js['bulk_imports']
734
- end
735
-
736
- def list_bulk_import_parts(name, opts={})
737
- params = opts.dup
738
- code, body, res = get("/v3/bulk_import/list_parts/#{e name}", params)
739
- if code != "200"
740
- raise_error("List bulk import parts failed", res)
741
- end
742
- js = checked_json(body, %w[parts])
743
- return js['parts']
744
- end
745
-
746
- # => nil
747
- def bulk_import_upload_part(name, part_name, stream, size, opts={})
748
- code, body, res = put("/v3/bulk_import/upload_part/#{e name}/#{e part_name}", stream, size)
749
- if code[0] != ?2
750
- raise_error("Upload a part failed", res)
751
- end
752
- return nil
753
- end
754
-
755
- # => nil
756
- def bulk_import_delete_part(name, part_name, opts={})
757
- params = opts.dup
758
- code, body, res = post("/v3/bulk_import/delete_part/#{e name}/#{e part_name}", params)
759
- if code[0] != ?2
760
- raise_error("Delete a part failed", res)
761
- end
762
- return nil
763
- end
764
-
765
- # => nil
766
- def freeze_bulk_import(name, opts={})
767
- params = opts.dup
768
- code, body, res = post("/v3/bulk_import/freeze/#{e name}", params)
769
- if code != "200"
770
- raise_error("Freeze bulk import failed", res)
771
- end
772
- return nil
773
- end
774
-
775
- # => nil
776
- def unfreeze_bulk_import(name, opts={})
777
- params = opts.dup
778
- code, body, res = post("/v3/bulk_import/unfreeze/#{e name}", params)
779
- if code != "200"
780
- raise_error("Unfreeze bulk import failed", res)
781
- end
782
- return nil
783
- end
784
-
785
- # => jobId:String
786
- def perform_bulk_import(name, opts={})
787
- params = opts.dup
788
- code, body, res = post("/v3/bulk_import/perform/#{e name}", params)
789
- if code != "200"
790
- raise_error("Perform bulk import failed", res)
791
- end
792
- js = checked_json(body, %w[job_id])
793
- return js['job_id'].to_s
794
- end
795
-
796
- # => nil
797
- def commit_bulk_import(name, opts={})
798
- params = opts.dup
799
- code, body, res = post("/v3/bulk_import/commit/#{e name}", params)
800
- if code != "200"
801
- raise_error("Commit bulk import failed", res)
802
- end
803
- return nil
804
- end
805
-
806
- # => data...
807
- def bulk_import_error_records(name, opts={}, &block)
808
- params = opts.dup
809
- code, body, res = get("/v3/bulk_import/error_records/#{e name}", params)
810
- if code != "200"
811
- raise_error("Failed to get bulk import error records", res)
812
- end
813
- if body.empty?
814
- if block
815
- return nil
816
- else
817
- return []
818
- end
819
- end
820
- require 'zlib'
821
- require 'stringio'
822
- require 'msgpack'
823
- require File.expand_path('compat_gzip_reader', File.dirname(__FILE__))
824
- u = MessagePack::Unpacker.new(Zlib::GzipReader.new(StringIO.new(body)))
825
- if block
826
- begin
827
- u.each(&block)
828
- rescue EOFError
829
- end
830
- nil
831
- else
832
- result = []
833
- begin
834
- u.each {|row|
835
- result << row
836
- }
837
- rescue EOFError
838
- end
839
- return result
840
- end
841
- end
842
-
843
- ####
844
- ## Schedule API
845
- ##
846
-
847
- # => start:String
848
- def create_schedule(name, opts)
849
- params = opts.update({:type=> opts[:type] || opts['type'] || 'hive'})
850
- code, body, res = post("/v3/schedule/create/#{e name}", params)
851
- if code != "200"
852
- raise_error("Create schedule failed", res)
853
- end
854
- js = checked_json(body, %w[start])
855
- return js['start']
856
- end
857
-
858
- # => cron:String, query:String
859
- def delete_schedule(name)
860
- code, body, res = post("/v3/schedule/delete/#{e name}")
861
- if code != "200"
862
- raise_error("Delete schedule failed", res)
863
- end
864
- js = checked_json(body, %w[])
865
- return js['cron'], js["query"]
866
- end
867
-
868
- # => [(name:String, cron:String, query:String, database:String, result_url:String)]
869
- def list_schedules
870
- code, body, res = get("/v3/schedule/list")
871
- if code != "200"
872
- raise_error("List schedules failed", res)
873
- end
874
- js = checked_json(body, %w[schedules])
875
- result = []
876
- js['schedules'].each {|m|
877
- name = m['name']
878
- cron = m['cron']
879
- query = m['query']
880
- database = m['database']
881
- result_url = m['result']
882
- timezone = m['timezone']
883
- delay = m['delay']
884
- next_time = m['next_time']
885
- priority = m['priority']
886
- retry_limit = m['retry_limit']
887
- result << [name, cron, query, database, result_url, timezone, delay, next_time, priority, retry_limit, nil] # same as database
888
- }
889
- return result
890
- end
891
-
892
- def update_schedule(name, params)
893
- code, body, res = post("/v3/schedule/update/#{e name}", params)
894
- if code != "200"
895
- raise_error("Update schedule failed", res)
896
- end
897
- return nil
898
- end
899
-
900
- def history(name, from=0, to=nil)
901
- params = {}
902
- params['from'] = from.to_s if from
903
- params['to'] = to.to_s if to
904
- code, body, res = get("/v3/schedule/history/#{e name}", params)
905
- if code != "200"
906
- raise_error("List history failed", res)
907
- end
908
- js = checked_json(body, %w[history])
909
- result = []
910
- js['history'].each {|m|
911
- job_id = m['job_id']
912
- type = (m['type'] || '?').to_sym
913
- database = m['database']
914
- status = m['status']
915
- query = m['query']
916
- start_at = m['start_at']
917
- end_at = m['end_at']
918
- scheduled_at = m['scheduled_at']
919
- result_url = m['result']
920
- priority = m['priority']
921
- result << [scheduled_at, job_id, type, status, query, start_at, end_at, result_url, priority, database]
922
- }
923
- return result
924
- end
925
-
926
- def run_schedule(name, time, num)
927
- params = {}
928
- params = {'num' => num} if num
929
- code, body, res = post("/v3/schedule/run/#{e name}/#{e time}", params)
930
- if code != "200"
931
- raise_error("Run schedule failed", res)
932
- end
933
- js = checked_json(body, %w[jobs])
934
- result = []
935
- js['jobs'].each {|m|
936
- job_id = m['job_id']
937
- scheduled_at = m['scheduled_at']
938
- type = (m['type'] || '?').to_sym
939
- result << [job_id, type, scheduled_at]
940
- }
941
- return result
942
- end
943
-
944
- ####
945
- ## Import API
946
- ##
947
-
948
- # => time:Float
949
- def import(db, table, format, stream, size, unique_id=nil)
950
- if unique_id
951
- path = "/v3/table/import_with_id/#{e db}/#{e table}/#{unique_id}/#{format}"
952
- else
953
- path = "/v3/table/import/#{e db}/#{e table}/#{format}"
954
- end
955
- opts = {}
956
- if @host == DEFAULT_ENDPOINT
957
- opts[:host] = DEFAULT_IMPORT_ENDPOINT
958
- elsif @host == NEW_DEFAULT_ENDPOINT
959
- opts[:host] = NEW_DEFAULT_IMPORT_ENDPOINT
960
- end
961
- code, body, res = put(path, stream, size, opts)
962
- if code[0] != ?2
963
- raise_error("Import failed", res)
964
- end
965
- js = checked_json(body, %w[])
966
- time = js['elapsed_time'].to_f
967
- return time
968
- end
969
-
970
-
971
- ####
972
- ## Result API
973
- ##
974
-
975
- def list_result
976
- code, body, res = get("/v3/result/list")
977
- if code != "200"
978
- raise_error("List result table failed", res)
979
- end
980
- js = checked_json(body, %w[results])
981
- result = []
982
- js['results'].map {|m|
983
- result << [m['name'], m['url'], nil] # same as database
984
- }
985
- return result
986
- end
987
-
988
- # => true
989
- def create_result(name, url, opts={})
990
- params = {'url'=>url}.merge(opts)
991
- code, body, res = post("/v3/result/create/#{e name}", params)
992
- if code != "200"
993
- raise_error("Create result table failed", res)
994
- end
995
- return true
996
- end
997
-
998
- # => true
999
- def delete_result(name)
1000
- code, body, res = post("/v3/result/delete/#{e name}")
1001
- if code != "200"
1002
- raise_error("Delete result table failed", res)
1003
- end
1004
- return true
1005
- end
1006
-
1007
-
1008
- ####
1009
- ## User API
1010
- ##
1011
-
1012
- # apikey:String
1013
- def authenticate(user, password)
1014
- code, body, res = post("/v3/user/authenticate", {'user'=>user, 'password'=>password})
1015
- if code != "200"
1016
- if code == "400"
1017
- raise_error("Authentication failed", res, AuthError)
1018
- else
1019
- raise_error("Authentication failed", res)
1020
- end
1021
- end
1022
- js = checked_json(body, %w[apikey])
1023
- apikey = js['apikey']
1024
- return apikey
1025
- end
1026
-
1027
- # => [[name:String,organization:String,[user:String]]
1028
- def list_users
1029
- code, body, res = get("/v3/user/list")
1030
- if code != "200"
1031
- raise_error("List users failed", res)
1032
- end
1033
- js = checked_json(body, %w[users])
1034
- result = js["users"].map {|roleinfo|
1035
- name = roleinfo['name']
1036
- email = roleinfo['email']
1037
- [name, nil, nil, email] # set nil to org and role for API compatibility
1038
- }
1039
- return result
1040
- end
1041
-
1042
- # => true
1043
- def add_user(name, org, email, password)
1044
- params = {'organization'=>org, :email=>email, :password=>password}
1045
- code, body, res = post("/v3/user/add/#{e name}", params)
1046
- if code != "200"
1047
- raise_error("Adding user failed", res)
1048
- end
1049
- return true
1050
- end
1051
-
1052
- # => true
1053
- def remove_user(user)
1054
- code, body, res = post("/v3/user/remove/#{e user}")
1055
- if code != "200"
1056
- raise_error("Removing user failed", res)
1057
- end
1058
- return true
1059
- end
1060
-
1061
- # => true
1062
- def change_email(user, email)
1063
- params = {'email' => email}
1064
- code, body, res = post("/v3/user/email/change/#{e user}", params)
1065
- if code != "200"
1066
- raise_error("Changing email failed", res)
1067
- end
1068
- return true
1069
- end
1070
-
1071
- # => [apikey:String]
1072
- def list_apikeys(user)
1073
- code, body, res = get("/v3/user/apikey/list/#{e user}")
1074
- if code != "200"
1075
- raise_error("List API keys failed", res)
1076
- end
1077
- js = checked_json(body, %w[apikeys])
1078
- return js['apikeys']
1079
- end
1080
-
1081
- # => true
1082
- def add_apikey(user)
1083
- code, body, res = post("/v3/user/apikey/add/#{e user}")
1084
- if code != "200"
1085
- raise_error("Adding API key failed", res)
1086
- end
1087
- return true
1088
- end
1089
-
1090
- # => true
1091
- def remove_apikey(user, apikey)
1092
- params = {'apikey' => apikey}
1093
- code, body, res = post("/v3/user/apikey/remove/#{e user}", params)
1094
- if code != "200"
1095
- raise_error("Removing API key failed", res)
1096
- end
1097
- return true
1098
- end
1099
-
1100
- # => true
1101
- def change_password(user, password)
1102
- params = {'password' => password}
1103
- code, body, res = post("/v3/user/password/change/#{e user}", params)
1104
- if code != "200"
1105
- raise_error("Changing password failed", res)
1106
- end
1107
- return true
1108
- end
1109
-
1110
- # => true
1111
- def change_my_password(old_password, password)
1112
- params = {'old_password' => old_password, 'password' => password}
1113
- code, body, res = post("/v3/user/password/change", params)
1114
- if code != "200"
1115
- raise_error("Changing password failed", res)
1116
- end
1117
- return true
1118
- end
1119
-
1120
-
1121
- ####
1122
- ## Access Control API
1123
- ##
1124
-
1125
- def grant_access_control(subject, action, scope, grant_option)
1126
- params = {'subject'=>subject, 'action'=>action, 'scope'=>scope, 'grant_option'=>grant_option.to_s}
1127
- code, body, res = post("/v3/acl/grant", params)
1128
- if code != "200"
1129
- raise_error("Granting access control failed", res)
1130
- end
1131
- return true
1132
- end
1133
-
1134
- def revoke_access_control(subject, action, scope)
1135
- params = {'subject'=>subject, 'action'=>action, 'scope'=>scope}
1136
- code, body, res = post("/v3/acl/revoke", params)
1137
- if code != "200"
1138
- raise_error("Revoking access control failed", res)
1139
- end
1140
- return true
1141
- end
1142
-
1143
- # [true, [{subject:String,action:String,scope:String}]]
1144
- def test_access_control(user, action, scope)
1145
- params = {'user'=>user, 'action'=>action, 'scope'=>scope}
1146
- code, body, res = get("/v3/acl/test", params)
1147
- if code != "200"
1148
- raise_error("Testing access control failed", res)
1149
- end
1150
- js = checked_json(body, %w[permission access_controls])
1151
- perm = js["permission"]
1152
- acl = js["access_controls"].map {|roleinfo|
1153
- subject = roleinfo['subject']
1154
- action = roleinfo['action']
1155
- scope = roleinfo['scope']
1156
- [name, action, scope]
1157
- }
1158
- return perm, acl
1159
- end
1160
-
1161
- # [{subject:String,action:String,scope:String}]
1162
- def list_access_controls
1163
- code, body, res = get("/v3/acl/list")
1164
- if code != "200"
1165
- raise_error("Listing access control failed", res)
1166
- end
1167
- js = checked_json(body, %w[access_controls])
1168
- acl = js["access_controls"].map {|roleinfo|
1169
- subject = roleinfo['subject']
1170
- action = roleinfo['action']
1171
- scope = roleinfo['scope']
1172
- grant_option = roleinfo['grant_option']
1173
- [subject, action, scope, grant_option]
1174
- }
1175
- return acl
1176
- end
1177
-
1178
- ####
1179
- ## Server Status API
1180
- ##
1181
-
1182
- # => status:String
1183
- def server_status
1184
- code, body, res = get('/v3/system/server_status')
1185
- if code != "200"
1186
- return "Server is down (#{code})"
1187
- end
1188
- js = checked_json(body, %w[status])
1189
- status = js['status']
1190
- return status
1191
- end
1192
-
1193
214
  def ssl_ca_file=(ssl_ca_file)
1194
215
  @ssl_ca_file = ssl_ca_file
1195
216
  end
1196
217
 
1197
- private
218
+ private
219
+
1198
220
  module DeflateReadBodyMixin
1199
221
  attr_accessor :gzip
1200
222
 
@@ -1303,7 +325,6 @@ class API
1303
325
  body = response.body
1304
326
  unless block
1305
327
  if ce = response.header['content-encoding']
1306
- require 'zlib'
1307
328
  if ce == 'gzip'
1308
329
  infl = Zlib::Inflate.new(Zlib::MAX_WBITS + 16)
1309
330
  begin
@@ -1377,7 +398,11 @@ class API
1377
398
  retry_delay *= 2
1378
399
  retry
1379
400
  else
1380
- $stderr.puts "Retrying stopped after #{@max_cumul_retry_delay} seconds."
401
+ if @retry_post_requests
402
+ $stderr.puts "Retrying stopped after #{@max_cumul_retry_delay} seconds."
403
+ else
404
+ $stderr.puts ""
405
+ end
1381
406
  raise e
1382
407
  end
1383
408
  rescue => e
@@ -1434,7 +459,7 @@ class API
1434
459
 
1435
460
  def build_endpoint(url, host)
1436
461
  schema = @ssl ? 'https' : 'http'
1437
- "#{schema}://#{host}:#{@port}/#{@base_path + url}"
462
+ "#{schema}://#{host}:#{@port}#{@base_path + url}"
1438
463
  end
1439
464
 
1440
465
  def guard_no_sslv3
@@ -1543,9 +568,14 @@ class API
1543
568
  # TODO error
1544
569
  end
1545
570
 
1546
- def e(s)
1547
- require 'cgi'
1548
- CGI.escape(s.to_s)
571
+ if ''.respond_to?(:encode)
572
+ def e(s)
573
+ CGI.escape(s.to_s.encode("UTF-8"))
574
+ end
575
+ else
576
+ def e(s)
577
+ CGI.escape(s.to_s)
578
+ end
1549
579
  end
1550
580
 
1551
581
  def checked_json(body, required)
@@ -1568,22 +598,3 @@ class API
1568
598
  end
1569
599
 
1570
600
  end # module TreasureData
1571
-
1572
- require 'openssl'
1573
- module OpenSSL
1574
- module SSL
1575
- class SSLContext
1576
-
1577
- # For disabling SSLv3 connection in favor of POODLE Attack protection
1578
- #
1579
- # Allow 'options' customize through Thread local storage since
1580
- # Net::HTTP does not support 'options' configuration.
1581
- #
1582
- alias original_set_params set_params
1583
- def set_params(params={})
1584
- original_set_params(params)
1585
- self.options |= OP_NO_SSLv3 if Thread.current[:SET_SSL_OP_NO_SSLv3]
1586
- end
1587
- end
1588
- end
1589
- end