dse-driver 1.0.1-java

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 43d6f472d065e4e4d980d6402fe26da4ab2f8259
4
+ data.tar.gz: 182fd750d7dc29158fe46a1e1d7063c2a41622bc
5
+ SHA512:
6
+ metadata.gz: 986c12a9a51f42ee91253bf7fe25120e04b8877fd92f4e52332892f819d21a5e9aef639b208f8cb7d53f35db3cff822c14c1291d1f3917f95a4f46dcaccda270
7
+ data.tar.gz: 4515f1d9fd062288aea8de3f755467e1c749e1a5e2284862293703c290c9246f2b897e64208971176f4cd2d51c43593e2bec330df1365ae561a0e49879d772be
data/.yardopts ADDED
@@ -0,0 +1,13 @@
1
+ --no-private
2
+ --markup markdown
3
+ --type-tag expected_errors:"Expected Errors"
4
+ --tag jira_ticket:"JIRA Ticket"
5
+ --type-tag expected_result:"Expected Result"
6
+ --tag test_assumptions:"Test Assumptions"
7
+ --tag test_category:"Test Category"
8
+
9
+ lib/**/*.rb
10
+ integration/**/*.rb
11
+ -- README
12
+
13
+ load_plugins: true
data/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # DataStax Enterprise Ruby Driver
2
+
3
+ *NOTE: The DataStax Enterprise Ruby Driver can be used solely with DataStax Enterprise. Please consult [the license](http://www.datastax.com/terms/datastax-dse-driver-license-terms).*
4
+
5
+ This driver is built on top of the [DataStax Ruby driver for Apache Cassandra](http://docs.datastax.com/en/latest-ruby-driver/ruby-driver/whatsNew.html)
6
+ and enhanced for the adaptive data management and mixed workload capabilities
7
+ provided by DSE. Therefore a lot of the underlying concepts are the same.
8
+
9
+ ## Documentation
10
+ Driver documentation can be found [here](http://docs.datastax.com/en/latest-dse-ruby-driver/ruby-driver/whatsNew.html).
11
+
12
+ In particular, you'll find our [Features](http://docs.datastax.com/en/latest-dse-ruby-driver/supplemental/features) and
13
+ [API](http://docs.datastax.com/en/latest-dse-ruby-driver/supplemental/api) sections very enlightening.
14
+
15
+ ## Feedback Requested
16
+ *Help us focus our efforts!* [Provide your input](http://goo.gl/forms/pCs8PTpHLf) on the Ruby Driver
17
+ Platform and Runtime Survey (we kept it short).
18
+
19
+ If you find an issue, please file an issue in our [public JIRA](https://datastax-oss.atlassian.net/browse/RUBY).
20
+ *Please be sure to specify the affects-version (DSE-1.X.Y).*
21
+
22
+ You can also post questions in [our forum](https://groups.google.com/a/lists.datastax.com/forum/#!forum/ruby-driver-user).
23
+
24
+ ## Features
25
+
26
+ This driver exposes the following features of DSE 5.0:
27
+
28
+ * <a href="features/graph#graph">Graph</a>
29
+ * <a href="features/authentication#authentication">Authentication</a> with nodes running DSE
30
+ * <a href="features/geospatial#geospatial-types">Geospatial types</a>
31
+
32
+ Note that this driver is fully compatible with previous versions of DataStax Enterprise.
33
+
34
+ ## Installation
35
+ The driver is named dse-driver on rubygems.org and can easily be installed with Bundler or the gem program. It will
36
+ download the appropriate Cassandra driver as well.
37
+
38
+ ## Upgrade
39
+ The driver is intended to have the same look and feel as the core driver to make upgrading from the core driver
40
+ trivial. The only change is to replace references to the <code>Cassandra</code> module with <code>Dse</code> when
41
+ creating the cluster object:
42
+
43
+ ```ruby
44
+ require 'dse'
45
+
46
+ # This returns a Dse::Cluster instance
47
+ cluster = Dse.cluster
48
+
49
+ # This returns a Dse::Session instance
50
+ session = cluster.connect
51
+ rs = session.execute('select * from system.local')
52
+ ```
53
+
54
+ ## Determining driver versions
55
+ Within a script or irb, you can determine the exact versions of the dse and core drivers by accessing the VERSION
56
+ constant of the appropriate module:
57
+
58
+ ```ruby
59
+ require 'dse'
60
+
61
+ puts "Dse Driver Version: #{Dse::VERSION}"
62
+ puts "Cassandra Driver Version: #{Cassandra::VERSION}"
63
+ ```
64
+
65
+ ## Compatibility
66
+ Although this driver exposes new features introduced in DSE 5.0, it is fully compatible and supported for use with
67
+ previous versions of DSE.
68
+
69
+ ## License
70
+ Copyright (C) 2016 DataStax Inc.
71
+
72
+ The full license terms are available at http://www.datastax.com/terms/datastax-dse-driver-license-terms
Binary file
data/lib/dse.rb ADDED
@@ -0,0 +1,104 @@
1
+ # encoding: utf-8
2
+
3
+ #--
4
+ # Copyright (C) 2016 DataStax Inc.
5
+ #
6
+ # This software can be used solely with DataStax Enterprise. Please consult the license at
7
+ # http://www.datastax.com/terms/datastax-dse-driver-license-terms
8
+ #++
9
+
10
+ require 'json'
11
+
12
+ if RUBY_ENGINE == 'jruby'
13
+ require 'challenge_evaluator'
14
+ else
15
+ require 'gss_api_context'
16
+ end
17
+
18
+ require 'cassandra'
19
+
20
+ module Dse
21
+ # Creates a {Dse::Cluster Cluster instance}, which extends {http://dsdocs30/api/cassandra/cluster Cassandra::Cluster}.
22
+ # The API is identical, except that it returns a {Dse::Session Dse::Session} (see below). It takes all of the same
23
+ # options as Cassandra.cluster and the following extra options.
24
+ #
25
+ # @option options [Dse::Graph::Options] :graph_options options for the DSE graph statement handler. Takes
26
+ # priority over other `:graph_*` options specified below.
27
+ # @option options [String] :graph_name name of graph to use in graph statements
28
+ # @option options [String] :graph_source graph traversal source
29
+ # @option options [String] :graph_language language used in graph queries
30
+ # @option options [Cassandra::CONSISTENCIES] :graph_read_consistency read consistency level for graph statements.
31
+ # Overrides the standard statement consistency level
32
+ # @option options [Cassandra::CONSISTENCIES] :graph_write_consistency write consistency level for graph statements.
33
+ # Overrides the standard statement consistency level
34
+ #
35
+ # @example Connecting to localhost
36
+ # cluster = Dse.cluster
37
+ #
38
+ # @example Configuring {Dse::Cluster}
39
+ # cluster = Dse.cluster(
40
+ # username: username,
41
+ # password: password,
42
+ # hosts: ['10.0.1.1', '10.0.1.2', '10.0.1.3']
43
+ # )
44
+ #
45
+ # @return [Dse::Cluster] a cluster instance
46
+ def self.cluster(options = {})
47
+ cluster_async(options).get
48
+ end
49
+
50
+ # Creates a {Dse::Cluster Cluster instance}.
51
+ #
52
+ # @see Dse.cluster
53
+ #
54
+ # @return [Cassandra::Future<Dse::Cluster>] a future resolving to the
55
+ # cluster instance.
56
+ def self.cluster_async(options = {})
57
+ graph_options = if !options[:graph_options].nil?
58
+ Cassandra::Util.assert_instance_of(Dse::Graph::Options, options[:graph_options])
59
+ options[:graph_options]
60
+ else
61
+ Dse::Graph::Options.new(options)
62
+ end
63
+ username = options[:username]
64
+ password = options[:password]
65
+ options[:custom_types] ||= []
66
+ options[:custom_types] << Dse::Geometry::Point << Dse::Geometry::LineString << Dse::Geometry::Polygon
67
+ options, hosts = Cassandra.validate_and_massage_options(options)
68
+
69
+ # Use the DSE plain text authenticator if we have a username and password. The above validation already
70
+ # raises an error if one is given without the other.
71
+ options[:auth_provider] = Auth::Providers::Password.new(username, password) if username && password
72
+ rescue => e
73
+ futures = options.fetch(:futures_factory) { return Cassandra::Future::Error.new(e) }
74
+ futures.error(e)
75
+ else
76
+ options[:cluster_klass] = Dse::Cluster
77
+ driver = ::Cassandra::Driver.new(options)
78
+
79
+ # Wrap the load-balancing policy that we'd otherwise run with, with a host-targeting policy.
80
+ # We do this before driver.connect because driver.connect saves off the policy in the cluster
81
+ # registry and does a few other things.
82
+
83
+ lbp = driver.load_balancing_policy
84
+ driver.load_balancing_policy = Dse::LoadBalancing::Policies::HostTargeting.new(lbp)
85
+ future = driver.connect(hosts)
86
+ future.then do |cluster|
87
+ cluster.graph_options.merge!(graph_options)
88
+ cluster
89
+ end
90
+ end
91
+ end
92
+
93
+ require 'dse/cluster'
94
+ require 'dse/util/endian_buffer'
95
+ require 'dse/geometry/line_string'
96
+ require 'dse/geometry/point'
97
+ require 'dse/geometry/polygon'
98
+ require 'dse/session'
99
+ require 'dse/version'
100
+ require 'dse/graph'
101
+ require 'dse/load_balancing/policies/host_targeting'
102
+ require 'dse/statements'
103
+ require 'dse/auth/providers/gss_api'
104
+ require 'dse/auth/providers/password'
@@ -0,0 +1,160 @@
1
+ # encoding: utf-8
2
+
3
+ #--
4
+ # Copyright (C) 2016 DataStax Inc.
5
+ #
6
+ # This software can be used solely with DataStax Enterprise. Please consult the license at
7
+ # http://www.datastax.com/terms/datastax-dse-driver-license-terms
8
+ #++
9
+
10
+ module Dse
11
+ module Auth
12
+ module Providers
13
+ # Auth provider to authenticate with Kerberos. Whenever the client connects to a DSE node,
14
+ # this provider will perform Kerberos authentication operations with it. By default, the provider
15
+ # takes the ip address of the node and uses `Socket#getnameinfo` to find its name in order to construct
16
+ # the full service address (e.g. service@host).
17
+ #
18
+ # @see #initialize
19
+ class GssApi < Cassandra::Auth::Provider
20
+ # @private
21
+ class NameInfoResolver
22
+ def resolve(host)
23
+ Socket.getnameinfo(['AF_INET', 0, host])[0]
24
+ end
25
+ end
26
+
27
+ # @private
28
+ class NoOpResolver
29
+ def resolve(host)
30
+ host
31
+ end
32
+ end
33
+
34
+ # @private
35
+ class Authenticator
36
+ # Copied from kerberosgss.h
37
+ AUTH_GSS_COMPLETE = 1
38
+
39
+ def initialize(authentication_class, host, service, principal, ticket_cache)
40
+ @authentication_class = authentication_class
41
+ @host = host
42
+ @service = service
43
+ @principal = principal
44
+ @ticket_cache = ticket_cache
45
+
46
+ if RUBY_ENGINE == 'jruby'
47
+ @sasl_client = javax.security.sasl.Sasl.createSaslClient(['GSSAPI'],
48
+ nil,
49
+ service,
50
+ host,
51
+ {javax.security.sasl.Sasl::SERVER_AUTH => 'true',
52
+ javax.security.sasl.Sasl::QOP => 'auth'},
53
+ nil)
54
+ config = Dse::Auth::Providers::ChallengeEvaluator.make_configuration(principal, ticket_cache)
55
+ login = javax.security.auth.login.LoginContext.new('DseClient', nil, nil, config)
56
+ login.login
57
+ @subject = login.getSubject
58
+ else
59
+ @gss_context = GssApiContext.new("#{@service}@#{@host}", @principal, @ticket_cache)
60
+ end
61
+ rescue => e
62
+ raise Cassandra::Errors::AuthenticationError.new(
63
+ "Failed to authenticate: #{e.message}",
64
+ nil,
65
+ nil,
66
+ nil,
67
+ nil,
68
+ nil,
69
+ nil,
70
+ :one,
71
+ 0
72
+ )
73
+ end
74
+
75
+ def initial_response
76
+ @authentication_class == 'com.datastax.bdp.cassandra.auth.DseAuthenticator' ?
77
+ 'GSSAPI' :
78
+ challenge_response('GSSAPI-START')
79
+ end
80
+
81
+ if RUBY_ENGINE == 'jruby'
82
+ def challenge_response(token)
83
+ if token == 'GSSAPI-START'
84
+ return '' unless @sasl_client.hasInitialResponse
85
+ token = ''
86
+ end
87
+
88
+ Dse::Auth::Providers::ChallengeEvaluator.evaluate(@sasl_client, @subject, token)
89
+ end
90
+ else
91
+ def challenge_response(token)
92
+ if token == 'GSSAPI-START'
93
+ response = @gss_context.step('')[1]
94
+ elsif !@is_gss_complete
95
+ # Process the challenge as a next step in authentication until we have gotten
96
+ # AUTH_GSS_COMPLETE.
97
+ rc, response = @gss_context.step(token)
98
+ @is_gss_complete = true if rc == AUTH_GSS_COMPLETE
99
+ response ||= ''
100
+ else
101
+ # Ok, we went through all initial phases of auth and now the server is giving us a message
102
+ # to decode.
103
+ data = @gss_context.unwrap(token)
104
+
105
+ raise 'Bad response from server' if data.length != 4
106
+ parsed = data.unpack('>L').first
107
+ max_length = [parsed & 0xffffff, 65536].min
108
+
109
+ # Set up a response like this:
110
+ # byte 0: the selected qop. 1==auth
111
+ # byte 1-3: the max length for any buffer sent back and forth on this connection. (big endian)
112
+ # the rest of the buffer: the authorization user name in UTF-8 - not null terminated.
113
+
114
+ user_name = @gss_context.user_name
115
+ out = [max_length | 1 << 24].pack('>L') + user_name
116
+ response = @gss_context.wrap(out)
117
+ end
118
+ response
119
+ end
120
+ end
121
+
122
+ def authentication_successful(token)
123
+ end
124
+ end
125
+
126
+ # @param service [String] name of the kerberos service; defaults to 'dse'.
127
+ # @param host_resolver [Boolean, Object] whether to use a host-resolver. By default,
128
+ # `Socket#getnameinfo` is used. To disable host-resolution, specify a `false` value. You may also
129
+ # provide a custom resolver, which is an object that implements the `resolve(host_ip)` method.
130
+ # @param principal [String] The principal whose cached credentials are used to authenticate. Defaults
131
+ # to the first principal stored in the ticket cache.
132
+ # @param ticket_cache [String] The ticket cache containing the cached credential we seek. Defaults
133
+ # *on Linux* to /tmp/krb5cc_&lt;uid&gt; (where uid is the numeric uid of the user running the
134
+ # client program). In MRI only, the `KRB5CCNAME` environment variable supercedes this. On Mac,
135
+ # the default is a symbolic reference to a ticket-cache server process.
136
+ def initialize(service = 'dse', host_resolver = true, principal = nil, ticket_cache = nil)
137
+ @service = service
138
+ @host_resolver = case host_resolver
139
+ when false
140
+ NoOpResolver.new
141
+ when true
142
+ NameInfoResolver.new
143
+ else
144
+ host_resolver
145
+ end
146
+ Cassandra::Util.assert_responds_to(:resolve, @host_resolver,
147
+ 'invalid host_resolver: it must have the :resolve method')
148
+ @principal = principal
149
+ @ticket_cache = ticket_cache
150
+ end
151
+
152
+ # @private
153
+ def create_authenticator(authentication_class, host)
154
+ Authenticator.new(authentication_class, @host_resolver.resolve(host.ip.to_s),
155
+ @service, @principal, @ticket_cache)
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,56 @@
1
+ # encoding: utf-8
2
+
3
+ #--
4
+ # Copyright (C) 2016 DataStax Inc.
5
+ #
6
+ # This software can be used solely with DataStax Enterprise. Please consult the license at
7
+ # http://www.datastax.com/terms/datastax-dse-driver-license-terms
8
+ #++
9
+
10
+ module Dse
11
+ module Auth
12
+ module Providers
13
+ # Auth provider to authenticate with username/password for DSE's built-in authentication as well as LDAP.
14
+ #
15
+ # @note No need to instantiate this class manually, use `:username` and
16
+ # `:password` options when calling {Dse.cluster} and one will be
17
+ # created automatically for you.
18
+
19
+ class Password < Cassandra::Auth::Provider
20
+ # @private
21
+ class Authenticator
22
+ def initialize(authentication_class, username, password)
23
+ @authentication_class = authentication_class
24
+ @username = username
25
+ @password = password
26
+ end
27
+
28
+ def initial_response
29
+ @authentication_class == 'com.datastax.bdp.cassandra.auth.DseAuthenticator' ?
30
+ 'PLAIN' :
31
+ challenge_response('PLAIN-START')
32
+ end
33
+
34
+ def challenge_response(token)
35
+ "\x00#{@username}\x00#{@password}"
36
+ end
37
+
38
+ def authentication_successful(token)
39
+ end
40
+ end
41
+
42
+ # @param username [String] username to use for authentication to Cassandra
43
+ # @param password [String] password to use for authentication to Cassandra
44
+ def initialize(username, password)
45
+ @username = username
46
+ @password = password
47
+ end
48
+
49
+ # @private
50
+ def create_authenticator(authentication_class, host)
51
+ Authenticator.new(authentication_class, @username, @password)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,99 @@
1
+ # encoding: utf-8
2
+
3
+ #--
4
+ # Copyright (C) 2016 DataStax Inc.
5
+ #
6
+ # This software can be used solely with DataStax Enterprise. Please consult the license at
7
+ # http://www.datastax.com/terms/datastax-dse-driver-license-terms
8
+ #++
9
+
10
+ module Dse
11
+ # Cluster represents a DSE cluster. It serves as a {Dse::Session session factory} and a collection of metadata.
12
+ # It wraps a {http://dsdocs30/api/cassandra/cluster Cassandra::Cluster} and exposes all of its functionality.
13
+ class Cluster
14
+ # @return [Dse::Graph::Options] default graph options used by queries on this cluster.
15
+ attr_reader :graph_options
16
+
17
+ # @private
18
+ def initialize(logger,
19
+ io_reactor,
20
+ executor,
21
+ control_connection,
22
+ cluster_registry,
23
+ cluster_schema,
24
+ cluster_metadata,
25
+ execution_options,
26
+ connection_options,
27
+ load_balancing_policy,
28
+ reconnection_policy,
29
+ retry_policy,
30
+ address_resolution_policy,
31
+ connector,
32
+ futures_factory,
33
+ timestamp_generator)
34
+ @delegate_cluster = Cassandra::Cluster.new(logger,
35
+ io_reactor,
36
+ executor,
37
+ control_connection,
38
+ cluster_registry,
39
+ cluster_schema,
40
+ cluster_metadata,
41
+ execution_options,
42
+ connection_options,
43
+ load_balancing_policy,
44
+ reconnection_policy,
45
+ retry_policy,
46
+ address_resolution_policy,
47
+ connector,
48
+ futures_factory,
49
+ timestamp_generator)
50
+ @graph_options = Dse::Graph::Options.new
51
+
52
+ # We need the futures factory ourselves for async error reporting and potentially for our
53
+ # own async processing independent of the C* driver.
54
+ @futures = futures_factory
55
+ end
56
+
57
+ # Delegates to {http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/api/cassandra/cluster/?local=true&nav=toc#connect_async-instance_method
58
+ # Cassandra::Cluster#connect_async}
59
+ # to connect asynchronously to a cluster, but returns a future that will resolve to a DSE session rather than
60
+ # Cassandra session.
61
+ #
62
+ # @param keyspace [String] optional keyspace to scope session to
63
+ #
64
+ # @return [Cassandra::Future<Dse::Session>]
65
+ def connect_async(keyspace = nil)
66
+ future = @delegate_cluster.connect_async(keyspace)
67
+ # We want to actually return a DSE session upon successful connection.
68
+ future.then do |cassandra_session|
69
+ Dse::Session.new(cassandra_session, @graph_options, @futures)
70
+ end
71
+ end
72
+
73
+ # Synchronous variant of {#connect_async}.
74
+ #
75
+ # @param keyspace [String] optional keyspace to scope the session to
76
+ #
77
+ # @return [Dse::Session]
78
+ def connect(keyspace = nil)
79
+ connect_async(keyspace).get
80
+ end
81
+
82
+ #### The following methods handle arbitrary delegation to the underlying cluster object. ####
83
+ protected
84
+
85
+ # @private
86
+ def method_missing(method_name, *args, &block)
87
+ # If we get here, we don't have a method of our own. Forward the request to the delegate_cluster.
88
+ # If it returns itself, we will coerce the result to return our *self* instead.
89
+
90
+ result = @delegate_cluster.send(method_name, *args, &block)
91
+ (result == @delegate_cluster) ? self : result
92
+ end
93
+
94
+ # @private
95
+ def respond_to?(method, include_private = false)
96
+ super || @delegate_cluster.respond_to?(method, include_private)
97
+ end
98
+ end
99
+ end