aerospike 2.19.0 → 2.26.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +354 -244
  3. data/lib/aerospike/atomic/atomic.rb +1 -1
  4. data/lib/aerospike/cdt/context.rb +137 -70
  5. data/lib/aerospike/cdt/list_return_type.rb +4 -0
  6. data/lib/aerospike/cdt/map_operation.rb +6 -6
  7. data/lib/aerospike/cdt/map_policy.rb +16 -2
  8. data/lib/aerospike/cdt/map_return_type.rb +13 -1
  9. data/lib/aerospike/client.rb +137 -115
  10. data/lib/aerospike/cluster/create_connection.rb +1 -1
  11. data/lib/aerospike/cluster.rb +41 -4
  12. data/lib/aerospike/command/admin_command.rb +368 -52
  13. data/lib/aerospike/command/batch_index_command.rb +4 -8
  14. data/lib/aerospike/command/batch_index_exists_command.rb +1 -1
  15. data/lib/aerospike/command/batch_index_node.rb +1 -1
  16. data/lib/aerospike/command/batch_item.rb +1 -1
  17. data/lib/aerospike/command/command.rb +180 -123
  18. data/lib/aerospike/command/field_type.rb +25 -24
  19. data/lib/aerospike/command/login_command.rb +164 -0
  20. data/lib/aerospike/command/multi_command.rb +25 -2
  21. data/lib/aerospike/command/operate_args.rb +99 -0
  22. data/lib/aerospike/command/operate_command.rb +6 -11
  23. data/lib/aerospike/command/read_command.rb +2 -2
  24. data/lib/aerospike/connection/authenticate.rb +36 -3
  25. data/lib/aerospike/exp/exp.rb +1329 -0
  26. data/lib/aerospike/exp/exp_bit.rb +388 -0
  27. data/lib/aerospike/exp/exp_hll.rb +169 -0
  28. data/lib/aerospike/exp/exp_list.rb +403 -0
  29. data/lib/aerospike/exp/exp_map.rb +493 -0
  30. data/lib/aerospike/exp/operation.rb +56 -0
  31. data/lib/aerospike/features.rb +22 -9
  32. data/lib/aerospike/host/parse.rb +2 -2
  33. data/lib/aerospike/key.rb +10 -1
  34. data/lib/aerospike/node/refresh/info.rb +1 -1
  35. data/lib/aerospike/node/verify/name.rb +1 -1
  36. data/lib/aerospike/node/verify/partition_generation.rb +1 -1
  37. data/lib/aerospike/node/verify/peers_generation.rb +1 -1
  38. data/lib/aerospike/node/verify/rebalance_generation.rb +1 -1
  39. data/lib/aerospike/node_validator.rb +6 -1
  40. data/lib/aerospike/operation.rb +20 -22
  41. data/lib/aerospike/policy/auth_mode.rb +36 -0
  42. data/lib/aerospike/policy/client_policy.rb +4 -1
  43. data/lib/aerospike/policy/policy.rb +29 -13
  44. data/lib/aerospike/policy/query_policy.rb +35 -2
  45. data/lib/aerospike/policy/scan_policy.rb +19 -2
  46. data/lib/aerospike/privilege.rb +133 -0
  47. data/lib/aerospike/query/filter.rb +44 -32
  48. data/lib/aerospike/query/node_partitions.rb +39 -0
  49. data/lib/aerospike/query/partition_filter.rb +66 -0
  50. data/lib/aerospike/{command/roles.rb → query/partition_status.rb} +16 -19
  51. data/lib/aerospike/query/partition_tracker.rb +347 -0
  52. data/lib/aerospike/query/query_command.rb +20 -10
  53. data/lib/aerospike/query/query_executor.rb +71 -0
  54. data/lib/aerospike/query/query_partition_command.rb +267 -0
  55. data/lib/aerospike/query/recordset.rb +9 -9
  56. data/lib/aerospike/query/scan_command.rb +3 -2
  57. data/lib/aerospike/query/scan_executor.rb +71 -0
  58. data/lib/aerospike/query/scan_partition_command.rb +49 -0
  59. data/lib/aerospike/query/statement.rb +8 -1
  60. data/lib/aerospike/query/stream_command.rb +17 -0
  61. data/lib/aerospike/result_code.rb +83 -8
  62. data/lib/aerospike/role.rb +55 -0
  63. data/lib/aerospike/task/execute_task.rb +19 -16
  64. data/lib/aerospike/task/index_task.rb +1 -1
  65. data/lib/aerospike/user_role.rb +26 -1
  66. data/lib/aerospike/utils/buffer.rb +93 -29
  67. data/lib/aerospike/utils/packer.rb +7 -6
  68. data/lib/aerospike/utils/pool.rb +1 -1
  69. data/lib/aerospike/value/particle_type.rb +1 -12
  70. data/lib/aerospike/value/value.rb +35 -60
  71. data/lib/aerospike/version.rb +1 -1
  72. data/lib/aerospike.rb +156 -136
  73. metadata +24 -6
@@ -15,8 +15,8 @@
15
15
  # License for the specific language governing permissions and limitations under
16
16
  # the License.
17
17
 
18
- require 'digest'
19
- require 'base64'
18
+ require "digest"
19
+ require "base64"
20
20
 
21
21
  module Aerospike
22
22
 
@@ -36,7 +36,6 @@ module Aerospike
36
36
  # +:fail_if_not_connected+ set to true
37
37
 
38
38
  class Client
39
-
40
39
  attr_accessor :default_admin_policy
41
40
  attr_accessor :default_batch_policy
42
41
  attr_accessor :default_info_policy
@@ -48,8 +47,7 @@ module Aerospike
48
47
  attr_accessor :cluster
49
48
 
50
49
  def initialize(hosts = nil, policy: ClientPolicy.new, connect: true)
51
-
52
- hosts = ::Aerospike::Host::Parse.(hosts || ENV['AEROSPIKE_HOSTS'] || 'localhost')
50
+ hosts = ::Aerospike::Host::Parse.(hosts || ENV["AEROSPIKE_HOSTS"] || "localhost")
53
51
  policy = create_policy(policy, ClientPolicy)
54
52
  set_default_policies(policy.policies)
55
53
  @cluster = Cluster.new(policy, hosts)
@@ -249,7 +247,7 @@ module Aerospike
249
247
  end
250
248
 
251
249
  response = send_info_command(policy, str_cmd, node).upcase
252
- return if response == 'OK'
250
+ return if response == "OK"
253
251
  raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::SERVER_ERROR, "Truncate failed: #{response}")
254
252
  end
255
253
 
@@ -386,7 +384,8 @@ module Aerospike
386
384
  def operate(key, operations, options = nil)
387
385
  policy = create_policy(options, OperatePolicy, default_operate_policy)
388
386
 
389
- command = OperateCommand.new(@cluster, policy, key, operations)
387
+ args = OperateArgs.new(cluster, policy, default_write_policy, default_operate_policy, key, operations)
388
+ command = OperateCommand.new(@cluster, key, args)
390
389
  execute_command(command)
391
390
  command.record
392
391
  end
@@ -415,7 +414,7 @@ module Aerospike
415
414
  def register_udf(udf_body, server_path, language, options = nil)
416
415
  policy = create_policy(options, Policy, default_info_policy)
417
416
 
418
- content = Base64.strict_encode64(udf_body).force_encoding('binary')
417
+ content = Base64.strict_encode64(udf_body).force_encoding("binary")
419
418
  str_cmd = "udf-put:filename=#{server_path};content=#{content};"
420
419
  str_cmd << "content-len=#{content.length};udf-type=#{language};"
421
420
 
@@ -424,15 +423,15 @@ module Aerospike
424
423
 
425
424
  res = {}
426
425
  response_map.each do |k, response|
427
- vals = response.to_s.split(';')
426
+ vals = response.to_s.split(";")
428
427
  vals.each do |pair|
429
428
  k, v = pair.split("=", 2)
430
429
  res[k] = v
431
430
  end
432
431
  end
433
432
 
434
- if res['error']
435
- raise Aerospike::Exceptions::CommandRejected.new("Registration failed: #{res['error']}\nFile: #{res['file']}\nLine: #{res['line']}\nMessage: #{res['message']}")
433
+ if res["error"]
434
+ raise Aerospike::Exceptions::CommandRejected.new("Registration failed: #{res["error"]}\nFile: #{res["file"]}\nLine: #{res["line"]}\nMessage: #{res["message"]}")
436
435
  end
437
436
 
438
437
  UdfRegisterTask.new(@cluster, server_path)
@@ -454,7 +453,7 @@ module Aerospike
454
453
  response_map = @cluster.request_info(policy, str_cmd)
455
454
  _, response = response_map.first
456
455
 
457
- if response == 'ok'
456
+ if response == "ok"
458
457
  UdfRemoveTask.new(@cluster, udf_name)
459
458
  else
460
459
  raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::SERVER_ERROR, response)
@@ -466,27 +465,27 @@ module Aerospike
466
465
  def list_udf(options = nil)
467
466
  policy = create_policy(options, Policy, default_info_policy)
468
467
 
469
- str_cmd = 'udf-list'
468
+ str_cmd = "udf-list"
470
469
 
471
470
  # Send command to one node. That node will distribute it to other nodes.
472
471
  response_map = @cluster.request_info(policy, str_cmd)
473
472
  _, response = response_map.first
474
473
 
475
- vals = response.split(';')
474
+ vals = response.split(";")
476
475
 
477
476
  vals.map do |udf_info|
478
- next if udf_info.strip! == ''
477
+ next if udf_info.strip! == ""
479
478
 
480
- udf_parts = udf_info.split(',')
479
+ udf_parts = udf_info.split(",")
481
480
  udf = UDF.new
482
481
  udf_parts.each do |values|
483
- k, v = values.split('=', 2)
482
+ k, v = values.split("=", 2)
484
483
  case k
485
- when 'filename'
484
+ when "filename"
486
485
  udf.filename = v
487
- when 'hash'
486
+ when "hash"
488
487
  udf.hash = v
489
- when 'type'
488
+ when "type"
490
489
  udf.language = v
491
490
  end
492
491
  end
@@ -501,7 +500,7 @@ module Aerospike
501
500
  # udf file = <server udf dir>/<package name>.lua
502
501
  #
503
502
  # This method is only supported by Aerospike 3 servers.
504
- def execute_udf(key, package_name, function_name, args=[], options = nil)
503
+ def execute_udf(key, package_name, function_name, args = [], options = nil)
505
504
  policy = create_policy(options, WritePolicy, default_write_policy)
506
505
 
507
506
  command = ExecuteCommand.new(@cluster, policy, key, package_name, function_name, args)
@@ -514,10 +513,10 @@ module Aerospike
514
513
  result_map = record.bins
515
514
 
516
515
  # User defined functions don't have to return a value.
517
- key, obj = result_map.detect{ |k, _| k.include?('SUCCESS') }
516
+ key, obj = result_map.detect { |k, _| k.include?("SUCCESS") }
518
517
  return obj if key
519
518
 
520
- key, obj = result_map.detect{ |k, _| k.include?('FAILURE') }
519
+ key, obj = result_map.detect { |k, _| k.include?("FAILURE") }
521
520
  message = key ? obj.to_s : "Invalid UDF return value"
522
521
  raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::UDF_BAD_RESPONSE, message)
523
522
  end
@@ -530,7 +529,7 @@ module Aerospike
530
529
  #
531
530
  # This method is only supported by Aerospike 3 servers.
532
531
  # If the policy is nil, the default relevant policy will be used.
533
- def execute_udf_on_query(statement, package_name, function_name, function_args=[], options = nil)
532
+ def execute_udf_on_query(statement, package_name, function_name, function_args = [], options = nil)
534
533
  policy = create_policy(options, QueryPolicy, default_query_policy)
535
534
 
536
535
  nodes = @cluster.nodes
@@ -543,10 +542,11 @@ module Aerospike
543
542
 
544
543
  # Use a thread per node
545
544
  nodes.each do |node|
545
+ partitions = node.cluster.node_partitions(node, statement.namespace)
546
546
  Thread.new do
547
547
  Thread.current.abort_on_exception = true
548
548
  begin
549
- command = QueryCommand.new(node, policy, statement, nil)
549
+ command = QueryCommand.new(node, policy, statement, nil, partitions)
550
550
  execute_command(command)
551
551
  rescue => e
552
552
  Aerospike.logger.error(e)
@@ -558,7 +558,6 @@ module Aerospike
558
558
  ExecuteTask.new(@cluster, statement)
559
559
  end
560
560
 
561
-
562
561
  # Create secondary index.
563
562
  # This asynchronous server call will return before command is complete.
564
563
  # The user can optionally wait for command completion by using the returned
@@ -567,7 +566,8 @@ module Aerospike
567
566
  # This method is only supported by Aerospike 3 servers.
568
567
  # index_type should be :string, :numeric or :geo2dsphere (requires server version 3.7 or later)
569
568
  # collection_type should be :list, :mapkeys or :mapvalues
570
- def create_index(namespace, set_name, index_name, bin_name, index_type, collection_type = nil, options = nil)
569
+ # ctx is an optional list of context. Supported on server v6.1+.
570
+ def create_index(namespace, set_name, index_name, bin_name, index_type, collection_type = nil, options = nil, ctx: nil)
571
571
  if options.nil? && collection_type.is_a?(Hash)
572
572
  options, collection_type = collection_type, nil
573
573
  end
@@ -576,18 +576,19 @@ module Aerospike
576
576
  str_cmd = "sindex-create:ns=#{namespace}"
577
577
  str_cmd << ";set=#{set_name}" unless set_name.to_s.strip.empty?
578
578
  str_cmd << ";indexname=#{index_name};numbins=1"
579
+ str_cmd << ";context=#{CDT::Context.base64(ctx)}" unless ctx.to_a.empty?
579
580
  str_cmd << ";indextype=#{collection_type.to_s.upcase}" if collection_type
580
581
  str_cmd << ";indexdata=#{bin_name},#{index_type.to_s.upcase}"
581
582
  str_cmd << ";priority=normal"
582
583
 
583
584
  # Send index command to one node. That node will distribute the command to other nodes.
584
585
  response = send_info_command(policy, str_cmd).upcase
585
- if response == 'OK'
586
+ if response == "OK"
586
587
  # Return task that could optionally be polled for completion.
587
588
  return IndexTask.new(@cluster, namespace, index_name)
588
589
  end
589
590
 
590
- if response.start_with?('FAIL:200')
591
+ if response.start_with?("FAIL:200")
591
592
  # Index has already been created. Do not need to poll for completion.
592
593
  return IndexTask.new(@cluster, namespace, index_name, true)
593
594
  end
@@ -606,10 +607,10 @@ module Aerospike
606
607
 
607
608
  # Send index command to one node. That node will distribute the command to other nodes.
608
609
  response = send_info_command(policy, str_cmd).upcase
609
- return if response == 'OK'
610
+ return if response == "OK"
610
611
 
611
612
  # Index did not previously exist. Return without error.
612
- return if response.start_with?('FAIL:201')
613
+ return if response.start_with?("FAIL:201")
613
614
 
614
615
  raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::INDEX_GENERIC, "Drop index failed: #{response}")
615
616
  end
@@ -623,13 +624,15 @@ module Aerospike
623
624
  # Scan Operations
624
625
  #-------------------------------------------------------
625
626
 
626
- def scan_all(namespace, set_name, bin_names = nil, options = nil)
627
+ # Reads records in specified namespace and set using partition filter.
628
+ # If the policy's concurrent_nodes is specified, each server node will be read in
629
+ # parallel. Otherwise, server nodes are read sequentially.
630
+ # If partition_filter is nil, all partitions will be scanned.
631
+ # If the policy is nil, the default relevant policy will be used.
632
+ # This method is only supported by Aerospike 4.9+ servers.
633
+ def scan_partitions(partition_filter, namespace, set_name, bin_names = nil, options = nil)
627
634
  policy = create_policy(options, ScanPolicy, default_scan_policy)
628
635
 
629
- # wait until all migrations are finished
630
- # TODO: implement
631
- # @cluster.WaitUntillMigrationIsFinished(policy.timeout)
632
-
633
636
  # Retry policy must be one-shot for scans.
634
637
  # copy on write for policy
635
638
  new_policy = policy.clone
@@ -639,118 +642,95 @@ module Aerospike
639
642
  raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::SERVER_NOT_AVAILABLE, "Scan failed because cluster is empty.")
640
643
  end
641
644
 
642
- recordset = Recordset.new(policy.record_queue_size, nodes.length, :scan)
643
-
644
- if policy.concurrent_nodes
645
- # Use a thread per node
646
- nodes.each do |node|
647
- Thread.new do
648
- Thread.current.abort_on_exception = true
649
- command = ScanCommand.new(node, new_policy, namespace, set_name, bin_names, recordset)
650
- begin
651
- execute_command(command)
652
- rescue => e
653
- Aerospike.logger.error(e.backtrace.join("\n")) unless e == SCAN_TERMINATED_EXCEPTION
654
- recordset.cancel(e)
655
- ensure
656
- recordset.thread_finished
657
- end
658
- end
659
- end
660
- else
661
- Thread.new do
662
- Thread.current.abort_on_exception = true
663
- nodes.each do |node|
664
- command = ScanCommand.new(node, new_policy, namespace, set_name, bin_names, recordset)
665
- begin
666
- execute_command(command)
667
- rescue => e
668
- Aerospike.logger.error(e.backtrace.join("\n")) unless e == SCAN_TERMINATED_EXCEPTION
669
- recordset.cancel(e)
670
- ensure
671
- recordset.thread_finished
672
- end
673
- end
674
- end
645
+ tracker = Aerospike::PartitionTracker.new(policy, nodes, partition_filter)
646
+ recordset = Recordset.new(policy.record_queue_size, 1, :scan)
647
+ Thread.new do
648
+ Thread.current.abort_on_exception = true
649
+ ScanExecutor.scan_partitions(policy, @cluster, tracker, namespace, set_name, recordset, bin_names)
675
650
  end
676
651
 
677
652
  recordset
678
653
  end
679
654
 
680
- # ScanNode reads all records in specified namespace and set, from one node only.
681
- # The policy can be used to specify timeouts.
682
- def scan_node(node, namespace, set_name, bin_names = nil, options = nil)
655
+ # Reads all records in specified namespace and set for one node only.
656
+ # If the policy is nil, the default relevant policy will be used.
657
+ def scan_node_partitions(node, namespace, set_name, bin_names = nil, options = nil)
683
658
  policy = create_policy(options, ScanPolicy, default_scan_policy)
684
- # wait until all migrations are finished
685
- # TODO: implement
686
- # @cluster.WaitUntillMigrationIsFinished(policy.timeout)
687
659
 
688
660
  # Retry policy must be one-shot for scans.
689
661
  # copy on write for policy
690
662
  new_policy = policy.clone
691
- new_policy.max_retries = 0
692
663
 
693
- node = @cluster.get_node_by_name(node) unless node.is_a?(Aerospike::Node)
664
+ unless node.active?
665
+ raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::SERVER_NOT_AVAILABLE, "Scan failed because cluster is empty.")
666
+ end
694
667
 
668
+ tracker = Aerospike::PartitionTracker.new(policy, [node])
695
669
  recordset = Recordset.new(policy.record_queue_size, 1, :scan)
696
-
697
670
  Thread.new do
698
671
  Thread.current.abort_on_exception = true
699
- command = ScanCommand.new(node, new_policy, namespace, set_name, bin_names, recordset)
700
- begin
701
- execute_command(command)
702
- rescue => e
703
- Aerospike.logger.error(e.backtrace.join("\n")) unless e == SCAN_TERMINATED_EXCEPTION
704
- recordset.cancel(e)
705
- ensure
706
- recordset.thread_finished
707
- end
672
+ ScanExecutor.scan_partitions(policy, @cluster, tracker, namespace, set_name, recordset, bin_names)
708
673
  end
709
674
 
710
675
  recordset
711
676
  end
712
677
 
678
+ # Reads all records in specified namespace and set from all nodes.
679
+ # If the policy's concurrent_nodes is specified, each server node will be read in
680
+ # parallel. Otherwise, server nodes are read sequentially.
681
+ # If the policy is nil, the default relevant policy will be used.
682
+ def scan_all(namespace, set_name, bin_names = nil, options = nil)
683
+ scan_partitions(Aerospike::PartitionFilter.all, namespace, set_name, bin_names, options)
684
+ end
685
+
686
+ # ScanNode reads all records in specified namespace and set, from one node only.
687
+ # The policy can be used to specify timeouts.
688
+ def scan_node(node, namespace, set_name, bin_names = nil, options = nil)
689
+ scan_node_partitions(node, namespace, set_name, bin_names, options)
690
+ end
691
+
713
692
  #--------------------------------------------------------
714
693
  # Query functions (Supported by Aerospike 3 servers only)
715
694
  #--------------------------------------------------------
716
695
 
717
- # Query executes a query and returns a recordset.
718
- # The query executor puts records on a channel from separate goroutines.
719
- # The caller can concurrently pops records off the channel through the
720
- # record channel.
696
+ # Executes a query for specified partitions and returns a recordset.
697
+ # The query executor puts records on the queue from separate threads.
698
+ # The caller can concurrently pop records off the queue through the
699
+ # recordset.records API.
721
700
  #
722
- # This method is only supported by Aerospike 3 servers.
723
- # If the policy is nil, a default policy will be generated.
724
- def query(statement, options = nil)
701
+ # This method is only supported by Aerospike 4.9+ servers.
702
+ # If the policy is nil, the default relevant policy will be used.
703
+ def query_partitions(partition_filter, statement, options = nil)
725
704
  policy = create_policy(options, QueryPolicy, default_query_policy)
726
705
  new_policy = policy.clone
727
706
 
728
707
  nodes = @cluster.nodes
729
708
  if nodes.empty?
730
- raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::SERVER_NOT_AVAILABLE, "Scan failed because cluster is empty.")
709
+ raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::SERVER_NOT_AVAILABLE, "Query failed because cluster is empty.")
731
710
  end
732
711
 
733
- recordset = Recordset.new(policy.record_queue_size, nodes.length, :query)
734
-
735
- # Use a thread per node
736
- nodes.each do |node|
737
- Thread.new do
738
- Thread.current.abort_on_exception = true
739
- command = QueryCommand.new(node, new_policy, statement, recordset)
740
- begin
741
- execute_command(command)
742
- rescue => e
743
- Aerospike.logger.error(e.backtrace.join("\n")) unless e == QUERY_TERMINATED_EXCEPTION
744
- recordset.cancel(e)
745
- ensure
746
- recordset.thread_finished
747
- end
748
- end
712
+ # result recordset
713
+ recordset = Recordset.new(policy.record_queue_size, 1, :query)
714
+ tracker = PartitionTracker.new(policy, nodes, partition_filter)
715
+ Thread.new do
716
+ Thread.current.abort_on_exception = true
717
+ QueryExecutor.query_partitions(@cluster, policy, tracker, statement, recordset)
749
718
  end
750
719
 
751
720
  recordset
752
721
  end
753
722
 
723
+ # Query executes a query and returns a recordset.
724
+ # The query executor puts records on a channel from separate threads.
725
+ # The caller can concurrently pops records off the channel through the
726
+ # record channel.
727
+ #
728
+ # This method is only supported by Aerospike 3 servers.
729
+ # If the policy is nil, a default policy will be generated.
730
+ def query(statement, options = nil)
731
+ query_partitions(Aerospike::PartitionFilter.all, statement, options)
732
+ end
733
+
754
734
  #-------------------------------------------------------
755
735
  # User administration
756
736
  #-------------------------------------------------------
@@ -759,7 +739,7 @@ module Aerospike
759
739
  # before sending to server.
760
740
  def create_user(user, password, roles, options = nil)
761
741
  policy = create_policy(options, AdminPolicy, default_admin_policy)
762
- hash = AdminCommand.hash_password(password)
742
+ hash = LoginCommand.hash_password(password)
763
743
  command = AdminCommand.new
764
744
  command.create_user(@cluster, policy, user, hash, roles)
765
745
  end
@@ -776,7 +756,7 @@ module Aerospike
776
756
  raise Aerospike::Exceptions::Aerospike.new(INVALID_USER) unless @cluster.user && @cluster.user != ""
777
757
  policy = create_policy(options, AdminPolicy, default_admin_policy)
778
758
 
779
- hash = AdminCommand.hash_password(password)
759
+ hash = LoginCommand.hash_password(password)
780
760
  command = AdminCommand.new
781
761
 
782
762
  if user == @cluster.user
@@ -818,6 +798,50 @@ module Aerospike
818
798
  command.query_users(@cluster, policy)
819
799
  end
820
800
 
801
+ # Retrieve privileges for a given role.
802
+ def query_role(role, options = nil)
803
+ policy = create_policy(options, AdminPolicy, default_admin_policy)
804
+ command = AdminCommand.new
805
+ command.query_role(@cluster, policy, role)
806
+ end
807
+
808
+ # Retrieve all roles and their privileges.
809
+ def query_roles(options = nil)
810
+ policy = create_policy(options, AdminPolicy, default_admin_policy)
811
+ command = AdminCommand.new
812
+ command.query_roles(@cluster, policy)
813
+ end
814
+
815
+ # Create a user-defined role.
816
+ # Quotas require server security configuration "enable-quotas" to be set to true.
817
+ # Pass 0 for quota values for no limit.
818
+ def create_role(role_name, privileges = [], allowlist = [], read_quota = 0, write_quota = 0, options = nil)
819
+ policy = create_policy(options, AdminPolicy, default_admin_policy)
820
+ command = AdminCommand.new
821
+ command.create_role(@cluster, policy, role_name, privileges, allowlist, read_quota, write_quota)
822
+ end
823
+
824
+ # Remove a user-defined role.
825
+ def drop_role(role_name, options = nil)
826
+ policy = create_policy(options, AdminPolicy, default_admin_policy)
827
+ command = AdminCommand.new
828
+ command.drop_role(@cluster, policy, role_name)
829
+ end
830
+
831
+ # Grant privileges to a user-defined role.
832
+ def grant_privileges(role_name, privileges, options = nil)
833
+ policy = create_policy(options, AdminPolicy, default_admin_policy)
834
+ command = AdminCommand.new
835
+ command.grant_privileges(@cluster, policy, role_name, privileges)
836
+ end
837
+
838
+ # Revoke privileges from a user-defined role.
839
+ def revoke_privileges(role_name, privileges, options = nil)
840
+ policy = create_policy(options, AdminPolicy, default_admin_policy)
841
+ command = AdminCommand.new
842
+ command.revoke_privileges(@cluster, policy, role_name, privileges)
843
+ end
844
+
821
845
  private
822
846
 
823
847
  def set_default_policies(policies)
@@ -942,7 +966,5 @@ module Aerospike
942
966
 
943
967
  threads.each(&:join)
944
968
  end
945
-
946
969
  end # class
947
-
948
970
  end # module
@@ -32,7 +32,7 @@ module Aerospike
32
32
  ).tap do |conn|
33
33
  if cluster.credentials_given?
34
34
  # Authenticate will raise and close connection if invalid credentials
35
- Connection::Authenticate.(conn, cluster.user, cluster.password)
35
+ Connection::AuthenticateNew.(conn, cluster)
36
36
  end
37
37
  end
38
38
  end
@@ -27,9 +27,12 @@ module Aerospike
27
27
  attr_reader :features, :tls_options
28
28
  attr_reader :cluster_id, :aliases
29
29
  attr_reader :cluster_name
30
+ attr_reader :client_policy
30
31
  attr_accessor :rack_aware, :rack_id
32
+ attr_accessor :session_token, :session_expiration
31
33
 
32
34
  def initialize(policy, hosts)
35
+ @client_policy = policy
33
36
  @cluster_seeds = hosts
34
37
  @fail_if_not_connected = policy.fail_if_not_connected
35
38
  @connection_queue_size = policy.connection_queue_size
@@ -56,7 +59,7 @@ module Aerospike
56
59
  # setup auth info for cluster
57
60
  if policy.requires_authentication
58
61
  @user = policy.user
59
- @password = AdminCommand.hash_password(policy.password)
62
+ @password = LoginCommand.hash_password(policy.password)
60
63
  end
61
64
 
62
65
  initialize_tls_host_names(hosts) if tls_enabled?
@@ -78,6 +81,15 @@ module Aerospike
78
81
  !(@user.nil? || @user.empty?)
79
82
  end
80
83
 
84
+ def session_valid?
85
+ @session_token && @session_expiration && @session_expiration.to_i < Time.now.to_i
86
+ end
87
+
88
+ def reset_session_info
89
+ @session_token = nil
90
+ @session_expiration = nil
91
+ end
92
+
81
93
  def tls_enabled?
82
94
  !tls_options.nil? && tls_options[:enable] != false
83
95
  end
@@ -230,6 +242,27 @@ module Aerospike
230
242
  batch_read_node(partition, replica_policy)
231
243
  end
232
244
 
245
+ # Returns partitions pertaining to a node
246
+ def node_partitions(node, namespace)
247
+ res = []
248
+
249
+ partition_map = partitions
250
+ replica_array = partition_map[namespace]
251
+ raise Aerospike::Exceptions::InvalidNamespace("namespace not found in the partition map") if !replica_array
252
+
253
+ node_array = (replica_array.get)[0]
254
+ raise Aerospike::Exceptions::InvalidNamespace("namespace not found in the partition map") if !node_array
255
+
256
+
257
+ pid = 0
258
+ for tnode in node_array.get
259
+ res << pid if node == tnode
260
+ pid+=1
261
+ end
262
+
263
+ res
264
+ end
265
+
233
266
  # Returns a random node on the cluster
234
267
  def random_node
235
268
  # Must copy array reference for copy on write semantics to work.
@@ -415,6 +448,7 @@ module Aerospike
415
448
  cluster_config_changed = true
416
449
  end
417
450
 
451
+
418
452
  cluster_config_changed
419
453
  end
420
454
 
@@ -429,7 +463,7 @@ module Aerospike
429
463
  count = -1
430
464
  done = false
431
465
 
432
- # will run until the cluster is stablized
466
+ # will run until the cluster is stabilized
433
467
  thr = Thread.new do
434
468
  loop do
435
469
  tend
@@ -441,14 +475,17 @@ module Aerospike
441
475
  # Break if timed out
442
476
  break if done
443
477
 
444
- sleep(0.001) # sleep for a milisecond
478
+ sleep(0.001) # sleep for a millisecond
445
479
 
446
480
  count = nodes.length
447
481
  end
448
482
  end
449
483
 
450
484
  # wait for the thread to finish or timeout
451
- thr.join(@connection_timeout)
485
+ # This will give the client up to 10 times the timeout duration to find
486
+ # a host and connect successfully eventually, in case the DNS
487
+ # returns multiple IPs and some of them are not reachable.
488
+ thr.join(@connection_timeout * 10)
452
489
  done = true
453
490
  sleep(0.001)
454
491
  thr.kill if thr.alive?