diff --git a/doc/upgrade-guide/upgrade-to-4x/README.md b/doc/upgrade-guide/upgrade-to-4x/README.md index 22cb79268..f7402dfe8 100644 --- a/doc/upgrade-guide/upgrade-to-4x/README.md +++ b/doc/upgrade-guide/upgrade-to-4x/README.md @@ -23,4 +23,93 @@ The `usedHostsPerRemoteDc` parameter on the `DCAwareRoundRobinPolicy` gave the i A good write up on datacenter failover describing some of these considerations can be found [here][dc-failover-post]. -[dc-failover-post]: https://medium.com/@foundev/cassandra-local-quorum-should-stay-local-c174d555cc57 +## Creation of `ISession` instances returns immediately + +In C# driver 4.0, when you create an `ISession` instance, the initialization task will be started in the background. When a request is executed, the driver will wait for the initialization task to finish before sending the request. + +If you want to explicitely wait for the initialization to be finished, you can use one of these new methods: `ISession.ConnectAsync()` / `ISession.Connect()`. + +## Session initialization retries + +When the control connection initialization fails because it wasn't able to reach any of the contact points, the driver will retry the initialization according to the configured reconnection policy. + +While an initialization attempt is in progress, the session methods that require initialization (e.g. `session.Execute()`) block until it is finished. If the initialization fails, the methods that were blocked will throw an exception. Until a new initialization attempt begins, any call to a method that requires initialization will throw the same exception. + +## Addition of several async methods (e.g. `Metadata.AllHostsAsync()`) + +The `ICluster.Metadata` property has always blocked until the initialization task is finished but until now there weren't many issues with this behavior because the user would create a session right away which would initialize the `ICluster` instance in a blocking manner. + +Now that the initialization happens in the background, the `ICluster.Metadata` property no longer blocks but the methods on `IMetadata` block until the initialization is finished. For users who use `async/await` we added `async` variants for all methods (e.g. `Metadata.AllHostsAsync()`). + +Some methods were added that return a snapshot of the current metadata cache (e.g. `Metadata.AllHostsSnapshot()`). These methods do not block but will return empty collections if the initialization is not done: + +- `IMetadata.AllHostsSnapshot()` +- `IMetadata.AllReplicasSnapshot()` +- `IMetadata.GetReplicasSnapshot()` + +There are also some extension methods that require a session to be initialized so we have added async variants of those methods as well: + +| Existing method | New async method | Namespace | +|------------------------------|-------------------------------|-----------------------| +| `CreateBatch(this ISession)` | `ISession.CreateBatchAsync()` | `Cassandra.Data.Linq` | +| `GetState(this ISession)` | `ISession.GetStateAsync()` | `Cassandra` | + +There are also some properties that were moved to the `ClusterDescription` class. You can obtain a `ClusterDescription` instance via the `IMetadata.GetClusterDescriptionAsync()` method (or `IMetadata.GetClusterDescription()`). + +| `Metadata.ClusterName` | `ClusterDescription.ClusterName` | `Cassandra` | +| `Metadata.IsDbaas` | `ClusterDescription.IsDbaas` | `Cassandra` | + +## `Metadata` API changes + +Several methods of the `ICluster` interface were actually wrappers around methods that are implemented in the `Metadata` class. We decided to move all metadata related elements from `ICluster`/`ISession` to the new `IMetadata` interface to simplify the driver's API, it makes more sense to have metadata related methods, properties and events on the `IMetadata` interface. + +If you are using one of theses elements that were moved to the `IMetadata` interface, you can use `ICluster.Metadata` to access it. Note that some of these methods now block until the initialization is done so we added `async` variants for them (see previous section for an explanation about this). + +These are the methods, properties and events that were affected: + +| Old API | New API | +|------------------------------------|-----------------------------------| +| `ICluster.AllHosts()` | `IMetadata.AllHosts()` | +| `ICluster.GetHost()` | `IMetadata.GetHost()` | +| `ICluster.GetReplicas()` | `IMetadata.GetReplicas()` | +| `ICluster.RefreshSchema()` | `IMetadata.RefreshSchema()` | +| `ICluster.RefreshSchemaAsync()` | `IMetadata.RefreshSchemaAsync()` | +| `ICluster.HostAdded` | `IMetadata.HostAdded` | +| `ICluster.HostRemoved` | `IMetadata.HostRemoved` | +| `ISession.BinaryProtocolVersion` | `IMetadata.GetClusterDescription().ProtocolVersion` | + +Note: `ClusterDescription.ProtocolVersion` returns an `enum` instead of `int`, you can cast this `enum` to `int` if you need it (`ISession.BinaryProtocolVersion` did this internally). + +## Removal of `ISession.WaitForSchemaAgreement()` + +When a DDL request is executed, the driver will wait for schema agreement before returning control to the user. See `ProtocolOptions.MaxSchemaAgreementWaitSeconds` for more info. + +If you want to manually check for schema agreement you can use the `IMetadata.CheckSchemaAgreementAsync()` method. + +## `Metadata` no longer implements `IDisposable` + +The implementation of `IDisposable` was pretty much empty at this point so we decided to remove it. + +We also removed `Metadata.ShutDown()` for the same reason. + +## `ILoadBalancingPolicy` interface changes + +You are only affected by these changes if you implemented a custom load balancing policy in your application instead of using one of those that are provided by the driver. + +### `ILoadBalancingPolicy.Initialize()` + +The `Initialize()` method is now `InitializeAsync()` and returns a `Task`. If the implementation is not async, we recommend returning `Task.CompletedTask` or `Task.FromResult(0)`. + + `InitializeAsync()` now has a `IMetadataSnapshotProvider` parameter instead of `ICluster`. You can obtain the hosts collection and replicas using this instance (see the earlier section related to `Metadata` API changes for more information on these changes). + +### `ILoadBalancingPolicy.NewQueryPlan()` and `ILoadBalancingPolicy.Distance()` + +The `NewQueryPlan()` and `Distance()` methods now have a `ICluster` parameter. + +This is to simplify the process of implementing a custom load balancing policy. Previously, all implementations had to be stateful and threadsafe, i.e., the `cluster` object that was provided in the `Initialize()` was necessary in order to implement the `NewQueryPlan()` method. + +Now you can build a completely stateless load balancing policy (which is guaranteed to be threadsafe) by obtaining the hosts / replicas via the `ICluster` parameter in the `NewQueryPlan()` method. In this scenario you can have an implementation of the `InitializeAsync()` method that just returns `Task.CompletedTask` or `Task.FromResult(0)`. + +You can still build more complex load balancing policies that access some kind of metadata service for example by implementing the `InitializeAsync()` method. + +[dc-failover-post]: https://medium.com/@foundev/cassandra-local-quorum-should-stay-local-c174d555cc57 \ No newline at end of file diff --git a/src/Cassandra.IntegrationTests/Core/ClientTimeoutTests.cs b/src/Cassandra.IntegrationTests/Core/ClientTimeoutTests.cs index a184ca40c..7e98f74df 100644 --- a/src/Cassandra.IntegrationTests/Core/ClientTimeoutTests.cs +++ b/src/Cassandra.IntegrationTests/Core/ClientTimeoutTests.cs @@ -306,6 +306,7 @@ public void Should_Not_Leak_Connections_Test() var clusters = Enumerable.Range(0, 100).Select( b => ClusterBuilder() .AddContactPoint(_testCluster.InitialContactPoint) + .WithReconnectionPolicy(new ConstantReconnectionPolicy(120 * 1000)) .WithSocketOptions(socketOptions) .Build()).ToList(); @@ -315,7 +316,15 @@ public void Should_Not_Leak_Connections_Test() { try { - await c.ConnectAsync().ConfigureAwait(false); + var session = await c.ConnectAsync().ConfigureAwait(false); + try + { + await session.ConnectAsync().ConfigureAwait(false); + } + finally + { + await session.ShutdownAsync().ConfigureAwait(false); + } } catch (NoHostAvailableException ex) { @@ -332,15 +341,15 @@ public void Should_Not_Leak_Connections_Test() { t.Dispose(); } - + tasks = null; - GC.Collect(); - Thread.Sleep(1000); + Thread.Sleep(3000); + GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true); decimal initialMemory = GC.GetTotalMemory(true); - const int length = 100; + const int length = 200; tasks = clusters.Select(c => Task.Run(async () => { @@ -348,7 +357,15 @@ public void Should_Not_Leak_Connections_Test() { try { - await c.ConnectAsync().ConfigureAwait(false); + var session = await c.ConnectAsync().ConfigureAwait(false); + try + { + await session.ConnectAsync().ConfigureAwait(false); + } + finally + { + await session.ShutdownAsync().ConfigureAwait(false); + } } catch (NoHostAvailableException ex) { @@ -369,18 +386,15 @@ public void Should_Not_Leak_Connections_Test() tasks = null; - GC.Collect(); - Thread.Sleep(1000); + GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true); + Assert.Less(GC.GetTotalMemory(true) / initialMemory, 1.5M, "Should not exceed a 50% (1.5) more than was previously allocated"); } finally { - foreach (var c in clusters) - { - c.Dispose(); - } + Task.WhenAll(clusters.Select(c => Task.Run(() => c.ShutdownAsync()))).GetAwaiter().GetResult(); } } } diff --git a/src/Cassandra.IntegrationTests/Core/ClientWarningsTests.cs b/src/Cassandra.IntegrationTests/Core/ClientWarningsTests.cs index 574e1172b..20dcde097 100644 --- a/src/Cassandra.IntegrationTests/Core/ClientWarningsTests.cs +++ b/src/Cassandra.IntegrationTests/Core/ClientWarningsTests.cs @@ -68,13 +68,13 @@ public void Should_QueryTrace_When_Enabled() { var rs = Session.Execute(new SimpleStatement("SELECT * from system.local").EnableTracing()); Assert.NotNull(rs.Info.QueryTrace); - var hosts = Session.Cluster.AllHosts(); + var hosts = Session.Cluster.Metadata.AllHosts(); Assert.NotNull(hosts); var coordinator = hosts.FirstOrDefault(); Assert.NotNull(coordinator); Assert.AreEqual(coordinator.Address.Address, rs.Info.QueryTrace.Coordinator); Assert.Greater(rs.Info.QueryTrace.Events.Count, 0); - if (Session.BinaryProtocolVersion >= 4) + if (Session.Cluster.Metadata.GetClusterDescription().ProtocolVersion >= ProtocolVersion.V4) { Assert.NotNull(rs.Info.QueryTrace.ClientAddress); } diff --git a/src/Cassandra.IntegrationTests/Core/ClusterPeersV2SimulacronTests.cs b/src/Cassandra.IntegrationTests/Core/ClusterPeersV2SimulacronTests.cs index 803dc5f47..66050d913 100644 --- a/src/Cassandra.IntegrationTests/Core/ClusterPeersV2SimulacronTests.cs +++ b/src/Cassandra.IntegrationTests/Core/ClusterPeersV2SimulacronTests.cs @@ -36,10 +36,10 @@ public void Should_SendRequestsToAllHosts_When_PeersOnSameAddress() { const string query = "SELECT * FROM ks.table"; - Assert.AreEqual(3, Session.Cluster.AllHosts().Count); - Assert.IsTrue(Session.Cluster.AllHosts().All(h => h.IsUp)); - Assert.AreEqual(1, Session.Cluster.AllHosts().Select(h => h.Address.Address).Distinct().Count()); - Assert.AreEqual(3, Session.Cluster.AllHosts().Select(h => h.Address.Port).Distinct().Count()); + Assert.AreEqual(3, Session.Cluster.Metadata.AllHosts().Count); + Assert.IsTrue(Session.Cluster.Metadata.AllHosts().All(h => h.IsUp)); + Assert.AreEqual(1, Session.Cluster.Metadata.AllHosts().Select(h => h.Address.Address).Distinct().Count()); + Assert.AreEqual(3, Session.Cluster.Metadata.AllHosts().Select(h => h.Address.Port).Distinct().Count()); foreach (var i in Enumerable.Range(0, 10)) { @@ -47,8 +47,8 @@ public void Should_SendRequestsToAllHosts_When_PeersOnSameAddress() Session.Execute(query); } - Assert.AreEqual(3, Session.Cluster.AllHosts().Count); - Assert.IsTrue(Session.Cluster.AllHosts().All(h => h.IsUp)); + Assert.AreEqual(3, Session.Cluster.Metadata.AllHosts().Count); + Assert.IsTrue(Session.Cluster.Metadata.AllHosts().All(h => h.IsUp)); var queriesByNode = TestCluster.GetNodes().Select(n => n.GetQueries(query, QueryType.Query)); Assert.IsTrue(queriesByNode.All(queries => queries.Count >= 1)); diff --git a/src/Cassandra.IntegrationTests/Core/ClusterSimulacronTests.cs b/src/Cassandra.IntegrationTests/Core/ClusterSimulacronTests.cs index 50b88d7b1..aa8ca64cf 100644 --- a/src/Cassandra.IntegrationTests/Core/ClusterSimulacronTests.cs +++ b/src/Cassandra.IntegrationTests/Core/ClusterSimulacronTests.cs @@ -51,30 +51,33 @@ public async Task Cluster_Should_StopSendingPeersV2Requests_When_InvalidQueryIsT .ThenServerError(ServerError.Invalid, "error")); SetupNewSession(b => b.WithPoolingOptions(new PoolingOptions().SetHeartBeatInterval(3000))); + await Session.ConnectAsync().ConfigureAwait(false); var peersV2Queries = TestCluster.GetQueries("SELECT * FROM system.peers_v2"); var peersQueries = TestCluster.GetQueries("SELECT * FROM system.peers"); - await TestCluster.GetNode(Session.Cluster.Metadata.ControlConnection.Host.Address).Stop().ConfigureAwait(false); + await TestCluster.GetNode(InternalSession.InternalCluster.InternalMetadata.ControlConnection.Host.Address) + .Stop().ConfigureAwait(false); // wait until control connection reconnection is done TestHelper.RetryAssert( () => { - Assert.AreEqual(1, Session.Cluster.AllHosts().Count(h => !h.IsUp)); - Assert.IsTrue(Session.Cluster.Metadata.ControlConnection.Host.IsUp); + Assert.AreEqual(1, Session.Cluster.Metadata.AllHosts().Count(h => !h.IsUp)); + Assert.IsTrue(InternalSession.InternalCluster.InternalMetadata.ControlConnection.Host.IsUp); }, 200, 100); - await TestCluster.GetNode(Session.Cluster.Metadata.ControlConnection.Host.Address).Stop().ConfigureAwait(false); + await TestCluster.GetNode(InternalSession.InternalCluster.InternalMetadata.ControlConnection.Host.Address) + .Stop().ConfigureAwait(false); // wait until control connection reconnection is done TestHelper.RetryAssert( () => { - Assert.AreEqual(2, Session.Cluster.AllHosts().Count(h => !h.IsUp)); - Assert.IsTrue(Session.Cluster.Metadata.ControlConnection.Host.IsUp); + Assert.AreEqual(2, Session.Cluster.Metadata.AllHosts().Count(h => !h.IsUp)); + Assert.IsTrue(InternalSession.InternalCluster.InternalMetadata.ControlConnection.Host.IsUp); }, 200, 100); @@ -125,7 +128,7 @@ public void Should_Try_To_Resolve_And_Continue_With_The_Next_Contact_Point_If_It { var session = cluster.Connect(); session.Execute("select * from system.local"); - Assert.That(cluster.AllHosts().Count, Is.EqualTo(3)); + Assert.That(cluster.Metadata.AllHosts().Count, Is.EqualTo(3)); } } @@ -179,8 +182,8 @@ public void Cluster_Connect_With_Wrong_Keyspace_Name_Test() .WithDefaultKeyspace("MY_WRONG_KEYSPACE") .Build()) { - Assert.Throws(() => cluster.Connect()); - Assert.Throws(() => cluster.Connect("ANOTHER_THAT_DOES_NOT_EXIST")); + Assert.Throws(() => cluster.Connect().Connect()); + Assert.Throws(() => cluster.Connect("ANOTHER_THAT_DOES_NOT_EXIST").Connect()); } } @@ -204,10 +207,10 @@ public void Cluster_Should_Resolve_Names() { try { - cluster.Connect("system"); + cluster.Connect("system").Connect(); Assert.IsTrue( - cluster.AllHosts().Any(h => addressList.Contains(h.Address.Address)), - string.Join(";", cluster.AllHosts().Select(h => h.Address.ToString())) + " | " + TestCluster.InitialContactPoint.Address); + cluster.Metadata.AllHosts().Any(h => addressList.Contains(h.Address.Address)), + string.Join(";", cluster.Metadata.AllHosts().Select(h => h.Address.ToString())) + " | " + TestCluster.InitialContactPoint.Address); } catch (NoHostAvailableException ex) { @@ -215,36 +218,7 @@ public void Cluster_Should_Resolve_Names() } } } - - [Test] - public void RepeatedClusterConnectCallsAfterTimeoutErrorThrowCachedInitErrorException() - { - TestCluster.DisableConnectionListener(type: "reject_startup"); - var timeoutMessage = "Cluster initialization was aborted after timing out. This mechanism is put in place to" + - " avoid blocking the calling thread forever. This usually caused by a networking issue" + - " between the client driver instance and the cluster. You can increase this timeout via " + - "the SocketOptions.ConnectTimeoutMillis config setting. This can also be related to deadlocks " + - "caused by mixing synchronous and asynchronous code."; - var cachedError = "An error occured during the initialization of the cluster instance. Further initialization attempts " + - "for this cluster instance will never succeed and will return this exception instead. The InnerException property holds " + - "a reference to the exception that originally caused the initialization error."; - using (var cluster = CreateClusterAndWaitUntilConnectException( - b => b - .WithSocketOptions( - new SocketOptions() - .SetConnectTimeoutMillis(500) - .SetMetadataAbortTimeout(500)), - out var ex)) - { - Assert.AreEqual(typeof(TimeoutException), ex.GetType()); - Assert.AreEqual(timeoutMessage, ex.Message); - var ex2 = Assert.Throws(() => cluster.Connect("sample_ks")); - Assert.AreEqual(cachedError, ex2.Message); - Assert.AreEqual(typeof(TimeoutException), ex2.InnerException.GetType()); - Assert.AreEqual(timeoutMessage, ex2.InnerException.Message); - } - } - + [Test] public void RepeatedClusterConnectCallsAfterTimeoutErrorEventuallyThrowNoHostException() { @@ -257,12 +231,11 @@ public void RepeatedClusterConnectCallsAfterTimeoutErrorEventuallyThrowNoHostExc .SetMetadataAbortTimeout(500)), out var ex)) { - Assert.AreEqual(typeof(TimeoutException), ex.GetType()); + Assert.AreEqual(typeof(InitializationTimeoutException), ex.GetType()); TestHelper.RetryAssert( () => { - var ex2 = Assert.Throws(() => cluster.Connect("sample_ks")); - Assert.AreEqual(typeof(NoHostAvailableException), ex2.InnerException.GetType()); + var ex2 = Assert.Throws(() => cluster.Connect("sample_ks").Connect()); }, 1000, 30); @@ -274,12 +247,20 @@ public void RepeatedClusterConnectCallsAfterNoHostErrorDontThrowCachedInitErrorE { TestCluster.DisableConnectionListener(type: "reject_startup"); using (var cluster = CreateClusterAndWaitUntilConnectException( - b => b.WithSocketOptions(new SocketOptions().SetConnectTimeoutMillis(1).SetReadTimeoutMillis(1)), + b => b + .WithReconnectionPolicy(new ConstantReconnectionPolicy(5000)) + .WithSocketOptions(new SocketOptions().SetConnectTimeoutMillis(1).SetReadTimeoutMillis(1)), out _)) { - var ex = Assert.Throws(() => cluster.Connect()); - var ex2 = Assert.Throws(() => cluster.Connect("sample_ks")); - Assert.AreNotSame(ex, ex2); + var ex = Assert.Throws(() => cluster.Connect().Connect()); + var ex2 = Assert.Throws(() => cluster.Connect("sample_ks").Connect()); + Assert.AreSame(ex, ex2); + Task.Delay(5000).GetAwaiter().GetResult(); + TestHelper.RetryAssert(() => + { + var ex3 = Assert.Throws(() => cluster.Connect("sample_ks3").Connect()); + Assert.AreNotSame(ex2, ex3); + }, 100, 20); } } @@ -295,7 +276,7 @@ private ICluster CreateClusterAndWaitUntilConnectException(Action b, ou cluster = builder.Build(); try { - tempEx = Assert.Catch(() => cluster.Connect()); + tempEx = Assert.Catch(() => cluster.Connect().Connect()); } catch (Exception) { diff --git a/src/Cassandra.IntegrationTests/Core/ClusterTests.cs b/src/Cassandra.IntegrationTests/Core/ClusterTests.cs index 5906e2408..c8e9a33a5 100644 --- a/src/Cassandra.IntegrationTests/Core/ClusterTests.cs +++ b/src/Cassandra.IntegrationTests/Core/ClusterTests.cs @@ -24,6 +24,7 @@ using Cassandra.SessionManagement; using Cassandra.IntegrationTests.TestClusterManagement; using Cassandra.IntegrationTests.TestClusterManagement.Simulacron; +using Cassandra.Tasks; using Cassandra.Tests; using NUnit.Framework; @@ -115,7 +116,7 @@ public async Task Cluster_Should_Honor_MaxProtocolVersion_Set(bool asyncConnect) .AddContactPoint(_testCluster.InitialContactPoint) .Build()) { - Assert.AreEqual(Cluster.MaxProtocolVersion, clusterDefault.Configuration.ProtocolOptions.MaxProtocolVersion); + Assert.AreEqual(Configuration.MaxProtocolVersion, clusterDefault.Configuration.ProtocolOptions.MaxProtocolVersion); // MaxProtocolVersion set var clusterMax = ClusterBuilder() @@ -126,9 +127,9 @@ public async Task Cluster_Should_Honor_MaxProtocolVersion_Set(bool asyncConnect) await Connect(clusterMax, asyncConnect, session => { if (TestClusterManager.CheckCassandraVersion(false, Version.Parse("2.1"), Comparison.LessThan)) - Assert.AreEqual(2, session.BinaryProtocolVersion); + Assert.AreEqual(2, session.Cluster.Metadata.GetClusterDescription().ProtocolVersion); else - Assert.AreEqual(3, session.BinaryProtocolVersion); + Assert.AreEqual(3, session.Cluster.Metadata.GetClusterDescription().ProtocolVersion); }).ConfigureAwait(false); // Arbitary MaxProtocolVersion set, will negotiate down upon connect @@ -163,7 +164,7 @@ public async Task Should_Add_And_Query_Newly_Bootstrapped_Node() { await Connect(cluster, false, session => { - Assert.AreEqual(1, cluster.AllHosts().Count); + Assert.AreEqual(1, cluster.Metadata.AllHosts().Count); _realCluster.BootstrapNode(2); Trace.TraceInformation("Node bootstrapped"); var newNodeAddress = _realCluster.ClusterIpPrefix + 2; @@ -172,8 +173,8 @@ await Connect(cluster, false, session => { Assert.True(TestUtils.IsNodeReachable(newNodeIpAddress)); //New node should be part of the metadata - Assert.AreEqual(2, cluster.AllHosts().Count); - var host = cluster.AllHosts().FirstOrDefault(h => h.Address.Address.Equals(newNodeIpAddress)); + Assert.AreEqual(2, cluster.Metadata.AllHosts().Count); + var host = cluster.Metadata.AllHosts().FirstOrDefault(h => h.Address.Address.Equals(newNodeIpAddress)); Assert.IsNotNull(host); }, 2000, @@ -200,7 +201,7 @@ public async Task Should_Remove_Decommissioned_Node() { await Connect(cluster, false, session => { - Assert.AreEqual(numberOfNodes, cluster.AllHosts().Count); + Assert.AreEqual(numberOfNodes, cluster.Metadata.AllHosts().Count); if (TestClusterManager.SupportsDecommissionForcefully()) { _realCluster.DecommissionNodeForcefully(numberOfNodes); @@ -217,7 +218,7 @@ await Connect(cluster, false, session => decommisionedNode = _realCluster.ClusterIpPrefix + 2; Assert.False(TestUtils.IsNodeReachable(IPAddress.Parse(decommisionedNode))); //New node should be part of the metadata - Assert.AreEqual(1, cluster.AllHosts().Count); + Assert.AreEqual(1, cluster.Metadata.AllHosts().Count); }, 100, 100); var queried = false; for (var i = 0; i < 10; i++) @@ -234,25 +235,24 @@ await Connect(cluster, false, session => } } - private class TestLoadBalancingPolicy : ILoadBalancingPolicy + private class TestLoadBalancingPolicy : ILoadBalancingPolicy { - private ICluster _cluster; public Host ControlConnectionHost { get; private set; } - public void Initialize(ICluster cluster) + public Task InitializeAsync(IMetadataSnapshotProvider metadata) { - _cluster = cluster; - ControlConnectionHost = ((IInternalCluster)cluster).GetControlConnection().Host; + ControlConnectionHost = ((Metadata)metadata).InternalMetadata.ControlConnection.Host; + return TaskHelper.Completed; } - public HostDistance Distance(Host host) + public HostDistance Distance(IMetadataSnapshotProvider metadata, Host host) { return HostDistance.Local; } - public IEnumerable NewQueryPlan(string keyspace, IStatement query) + public IEnumerable NewQueryPlan(ICluster cluster, string keyspace, IStatement query) { - return _cluster.AllHosts(); + return cluster.Metadata.AllHosts(); } } } diff --git a/src/Cassandra.IntegrationTests/Core/ConnectionSimulacronTests.cs b/src/Cassandra.IntegrationTests/Core/ConnectionSimulacronTests.cs index 89fcf5d2f..6044e4273 100644 --- a/src/Cassandra.IntegrationTests/Core/ConnectionSimulacronTests.cs +++ b/src/Cassandra.IntegrationTests/Core/ConnectionSimulacronTests.cs @@ -51,8 +51,11 @@ public async Task Should_ThrowOperationTimedOut_When_ServerAppliesTcpBackPressur .SetStreamMode(streamMode) .SetDefunctReadTimeoutThreshold(int.MaxValue))); + await Session.ConnectAsync().ConfigureAwait(false); + var clusterDescription = await Session.Cluster.Metadata.GetClusterDescriptionAsync().ConfigureAwait(false); + var maxRequestsPerConnection = Session.Cluster.Configuration - .GetOrCreatePoolingOptions(Session.Cluster.Metadata.ControlConnection.ProtocolVersion) + .GetOrCreatePoolingOptions(clusterDescription.ProtocolVersion) .GetMaxRequestsPerConnection(); var tenKbBuffer = new byte[10240]; @@ -60,7 +63,7 @@ public async Task Should_ThrowOperationTimedOut_When_ServerAppliesTcpBackPressur // send number of requests = max pending var requests = - Enumerable.Repeat(0, maxRequestsPerConnection * Session.Cluster.AllHosts().Count) + Enumerable.Repeat(0, maxRequestsPerConnection * Session.Cluster.Metadata.AllHosts().Count) .Select(i => Session.ExecuteAsync(new SimpleStatement("INSERT INTO table1 (id) VALUES (?)", tenKbBuffer))).ToList(); var taskAll = Task.WhenAll(requests); @@ -88,8 +91,6 @@ public async Task Should_ThrowOperationTimedOut_When_ServerAppliesTcpBackPressur [Test] public async Task Should_RetryOnNextNodes_When_ANodeIsPaused(bool streamMode) { - var pausedNode = TestCluster.GetNode(2); - SetupNewSession(b => b.WithPoolingOptions( new PoolingOptions() @@ -101,9 +102,13 @@ public async Task Should_RetryOnNextNodes_When_ANodeIsPaused(bool streamMode) .SetStreamMode(streamMode) .SetDefunctReadTimeoutThreshold(int.MaxValue))); + await Session.ConnectAsync().ConfigureAwait(false); + var clusterDescription = await Session.Cluster.Metadata.GetClusterDescriptionAsync().ConfigureAwait(false); + var pausedNode = TestCluster.GetNode(2); + var maxRequestsPerConnection = Session.Cluster.Configuration - .GetOrCreatePoolingOptions(Session.Cluster.Metadata.ControlConnection.ProtocolVersion) + .GetOrCreatePoolingOptions(clusterDescription.ProtocolVersion) .GetMaxRequestsPerConnection(); var tenKbBuffer = new byte[10240]; @@ -112,7 +117,7 @@ public async Task Should_RetryOnNextNodes_When_ANodeIsPaused(bool streamMode) // send number of requests = max pending var requests = - Enumerable.Repeat(0, maxRequestsPerConnection * Session.Cluster.AllHosts().Count) + Enumerable.Repeat(0, maxRequestsPerConnection * Session.Cluster.Metadata.AllHosts().Count) .Select(i => Session.ExecuteAsync(new SimpleStatement("INSERT INTO table1 (id) VALUES (?)", tenKbBuffer))).ToList(); var pools = InternalSession.GetPools().ToList(); @@ -146,8 +151,6 @@ public async Task Should_RetryOnNextNodes_When_ANodeIsPaused(bool streamMode) [Test] public async Task Should_ContinueRoutingTrafficToNonPausedNodes_When_ANodeIsPaused(bool streamMode) { - var pausedNode = TestCluster.GetNode(2); - const string profileName = "running-nodes"; SetupNewSession(b => @@ -165,9 +168,13 @@ public async Task Should_ContinueRoutingTrafficToNonPausedNodes_When_ANodeIsPaus new TestDisallowListLbp( Cassandra.Policies.NewDefaultLoadBalancingPolicy("dc1")))))); + await Session.ConnectAsync().ConfigureAwait(false); + var clusterDescription = await Session.Cluster.Metadata.GetClusterDescriptionAsync().ConfigureAwait(false); + var pausedNode = TestCluster.GetNode(2); + var maxRequestsPerConnection = Session.Cluster.Configuration - .GetOrCreatePoolingOptions(Session.Cluster.Metadata.ControlConnection.ProtocolVersion) + .GetOrCreatePoolingOptions(clusterDescription.ProtocolVersion) .GetMaxRequestsPerConnection(); var tenKbBuffer = new byte[10240]; @@ -176,7 +183,7 @@ public async Task Should_ContinueRoutingTrafficToNonPausedNodes_When_ANodeIsPaus // send number of requests = max pending var requests = - Enumerable.Repeat(0, maxRequestsPerConnection * Session.Cluster.AllHosts().Count) + Enumerable.Repeat(0, maxRequestsPerConnection * Session.Cluster.Metadata.AllHosts().Count) .Select(i => Session.ExecuteAsync(new SimpleStatement("INSERT INTO table1 (id) VALUES (?)", tenKbBuffer))).ToList(); try @@ -242,8 +249,11 @@ public async Task Should_KeepOperationsInWriteQueue_When_ServerAppliesTcpBackPre .SetReadTimeoutMillis(360000) .SetStreamMode(streamMode))); + await Session.ConnectAsync().ConfigureAwait(false); + var clusterDescription = await Session.Cluster.Metadata.GetClusterDescriptionAsync().ConfigureAwait(false); + var maxRequestsPerConnection = Session.Cluster.Configuration - .GetOrCreatePoolingOptions(Session.Cluster.Metadata.ControlConnection.ProtocolVersion) + .GetOrCreatePoolingOptions(clusterDescription.ProtocolVersion) .GetMaxRequestsPerConnection(); var tenKbBuffer = new byte[10240]; @@ -296,7 +306,7 @@ public async Task Should_KeepOperationsInWriteQueue_When_ServerAppliesTcpBackPre Assert.Greater(moreFailedRequests.Count, 1); Assert.AreEqual(moreRequests.Count, moreFailedRequests.Count); - Assert.GreaterOrEqual(connections.Sum(c => c.InFlight), maxRequestsPerConnection * Session.Cluster.AllHosts().Count); + Assert.GreaterOrEqual(connections.Sum(c => c.InFlight), maxRequestsPerConnection * InternalSession.Cluster.Metadata.AllHosts().Count); // ReSharper disable once PossibleNullReferenceException Assert.IsTrue(moreFailedRequests.All(t => t.IsFaulted && ((NoHostAvailableException)t.Exception.InnerException).Errors.All(e => e.Value is BusyPoolException))); @@ -338,7 +348,7 @@ public async Task Should_KeepOperationsInWriteQueue_When_ServerAppliesTcpBackPre } private async Task AssertRetryUntilWriteQueueStabilizesAsync( - IEnumerable connections, int? maxPerConnection = null, int msPerRetry = 1000, int maxRetries = 30) + IEnumerable connections, int? maxPerConnection = null, int msPerRetry = 5000, int maxRetries = 30) { foreach (var connection in connections) { @@ -376,19 +386,19 @@ public TestDisallowListLbp(ILoadBalancingPolicy parent, params IPEndPoint[] disa _disallowed = disallowed; } - public void Initialize(ICluster cluster) + public Task InitializeAsync(IMetadataSnapshotProvider metadata) { - _parent.Initialize(cluster); + return _parent.InitializeAsync(metadata); } - public HostDistance Distance(Host host) + public HostDistance Distance(IMetadataSnapshotProvider metadata, Host host) { - return _parent.Distance(host); + return _parent.Distance(metadata, host); } - public IEnumerable NewQueryPlan(string keyspace, IStatement query) + public IEnumerable NewQueryPlan(ICluster cluster, string keyspace, IStatement query) { - var plan = _parent.NewQueryPlan(keyspace, query); + var plan = _parent.NewQueryPlan(cluster, keyspace, query); return plan.Where(h => !_disallowed.Contains(h.Address)); } } diff --git a/src/Cassandra.IntegrationTests/Core/ControlConnectionSimulatorTests.cs b/src/Cassandra.IntegrationTests/Core/ControlConnectionSimulatorTests.cs index d353703ce..fd3db1056 100644 --- a/src/Cassandra.IntegrationTests/Core/ControlConnectionSimulatorTests.cs +++ b/src/Cassandra.IntegrationTests/Core/ControlConnectionSimulatorTests.cs @@ -54,7 +54,7 @@ public void Should_Downgrade_To_Protocol_VX_With_Versions(ProtocolVersion versio { var session = cluster.Connect(); Parallel.For(0, 10, _ => session.Execute("SELECT * FROM system.local")); - Assert.AreEqual(version, cluster.InternalRef.GetControlConnection().ProtocolVersion); + Assert.AreEqual(version, cluster.InternalRef.InternalMetadata.ProtocolVersion); } else { @@ -83,7 +83,7 @@ public void Should_Not_Downgrade_Protocol_Version(ProtocolVersion version, param { var session = cluster.Connect(); Parallel.For(0, 10, _ => session.Execute("SELECT * FROM system.local")); - Assert.AreEqual(version, cluster.InternalRef.GetControlConnection().ProtocolVersion); + Assert.AreEqual(version, cluster.InternalRef.InternalMetadata.ProtocolVersion); } } diff --git a/src/Cassandra.IntegrationTests/Core/ControlConnectionTests.cs b/src/Cassandra.IntegrationTests/Core/ControlConnectionTests.cs index 34fe225d0..3b0276c2d 100644 --- a/src/Cassandra.IntegrationTests/Core/ControlConnectionTests.cs +++ b/src/Cassandra.IntegrationTests/Core/ControlConnectionTests.cs @@ -19,12 +19,15 @@ using System.Net; using Cassandra.Connections; using Cassandra.Connections.Control; +using Cassandra.Helpers; using Cassandra.IntegrationTests.TestBase; using Cassandra.ProtocolEvents; using NUnit.Framework; using Cassandra.IntegrationTests.TestClusterManagement; using Cassandra.SessionManagement; +using Cassandra.Tasks; using Cassandra.Tests; +using Cassandra.Tests.Connections.TestHelpers; using Moq; namespace Cassandra.IntegrationTests.Core @@ -34,19 +37,22 @@ public class ControlConnectionTests : TestGlobals { private const int InitTimeout = 2000; private ITestCluster _testCluster; + private IClusterInitializer _clusterInitializer; [OneTimeSetUp] public void SetupFixture() { _testCluster = TestClusterManager.CreateNew(); + _clusterInitializer = Mock.Of(); + Mock.Get(_clusterInitializer).Setup(c => c.PostInitializeAsync()).Returns(TaskHelper.Completed); } [Test] public void Should_Use_Maximum_Protocol_Version_Supported() { - var cc = NewInstance(); - cc.InitAsync().Wait(InitTimeout); - Assert.AreEqual(GetProtocolVersion(), cc.ProtocolVersion); + var cc = NewInstance(ProtocolVersion.MaxSupported, out var config); + cc.InitAsync(_clusterInitializer).Wait(InitTimeout); + Assert.AreEqual(GetProtocolVersion(), config.SerializerManager.CurrentProtocolVersion); cc.Dispose(); } @@ -59,9 +65,9 @@ public void Should_Use_Maximum_Protocol_Version_Provided() //protocol 2 is not supported in Cassandra 3.0+ version = ProtocolVersion.V3; } - var cc = NewInstance(version); - cc.InitAsync().Wait(InitTimeout); - Assert.AreEqual(version, cc.ProtocolVersion); + var cc = NewInstance(version, out var config); + cc.InitAsync(_clusterInitializer).Wait(InitTimeout); + Assert.AreEqual(version, config.SerializerManager.CurrentProtocolVersion); cc.Dispose(); } @@ -70,9 +76,9 @@ public void Should_Downgrade_The_Protocol_Version() { //Use a higher protocol version var version = (ProtocolVersion)(GetProtocolVersion() + 1); - var cc = NewInstance(version); - cc.InitAsync().Wait(InitTimeout); - Assert.AreEqual(version - 1, cc.ProtocolVersion); + var cc = NewInstance(version, out var config); + cc.InitAsync(_clusterInitializer).Wait(InitTimeout); + Assert.AreEqual(version - 1, config.SerializerManager.CurrentProtocolVersion); } [Test, TestCassandraVersion(3, 0)] @@ -80,33 +86,25 @@ public void Should_Downgrade_The_Protocol_Version_With_Higher_Version_Than_Suppo { // Use a non-existent higher cassandra protocol version var version = (ProtocolVersion)0x0f; - var cc = NewInstance(version); - cc.InitAsync().Wait(InitTimeout); - Assert.AreEqual(ProtocolVersion.V4, cc.ProtocolVersion); + var cc = NewInstance(version, out var config); + cc.InitAsync(_clusterInitializer).Wait(InitTimeout); + Assert.AreEqual(ProtocolVersion.V4, config.SerializerManager.CurrentProtocolVersion); } - private ControlConnection NewInstance( - ProtocolVersion version = ProtocolVersion.MaxSupported, - Configuration config = null, - Metadata metadata = null) + private ControlConnection NewInstance(ProtocolVersion version, out Configuration config) { - config = config ?? new Configuration(); - if (metadata == null) - { - metadata = new Metadata(config); - metadata.AddHost(new IPEndPoint(IPAddress.Parse(_testCluster.InitialContactPoint), ProtocolOptions.DefaultPort)); - } + config = new Configuration(); + config.SerializerManager.ChangeProtocolVersion(version); + var internalMetadata = new FakeInternalMetadata(config); + internalMetadata.AddHost(new IPEndPoint(IPAddress.Parse(_testCluster.InitialContactPoint), ProtocolOptions.DefaultPort)); var cc = new ControlConnection( Mock.Of(), - GetEventDebouncer(config), - version, config, - metadata, + internalMetadata, new List { new IpLiteralContactPoint(IPAddress.Parse(_testCluster.InitialContactPoint), config.ProtocolOptions, config.ServerNameResolver ) }); - metadata.ControlConnection = cc; return cc; } diff --git a/src/Cassandra.IntegrationTests/Core/MetadataTests.cs b/src/Cassandra.IntegrationTests/Core/MetadataTests.cs index 8ea03bc76..6fe685538 100644 --- a/src/Cassandra.IntegrationTests/Core/MetadataTests.cs +++ b/src/Cassandra.IntegrationTests/Core/MetadataTests.cs @@ -24,6 +24,7 @@ using System.Text; using System.Threading; using Cassandra.IntegrationTests.TestBase; +using Cassandra.SessionManagement; using SortOrder = Cassandra.DataCollectionMetadata.SortOrder; namespace Cassandra.IntegrationTests.Core @@ -53,7 +54,7 @@ public void KeyspacesMetadataUpToDateViaCassandraEvents() Assert.Greater(initialLength, 0); //GetReplicas should yield the primary replica when the Keyspace is not found - Assert.AreEqual(1, cluster.GetReplicas("ks2", new byte[] {0, 0, 0, 1}).Count); + Assert.AreEqual(1, cluster.Metadata.GetReplicas("ks2", new byte[] {0, 0, 0, 1}).Count); const string createKeyspaceQuery = "CREATE KEYSPACE {0} WITH replication = {{ 'class' : '{1}', {2} }}"; session.Execute(string.Format(createKeyspaceQuery, "ks1", "SimpleStrategy", "'replication_factor' : 1")); @@ -70,7 +71,7 @@ public void KeyspacesMetadataUpToDateViaCassandraEvents() Assert.NotNull(ks2); Assert.AreEqual(ks2.Replication["replication_factor"], 3); //GetReplicas should yield the 2 replicas (rf=3 but cluster=2) when the Keyspace is found - Assert.AreEqual(2, cluster.GetReplicas("ks2", new byte[] {0, 0, 0, 1}).Count); + Assert.AreEqual(2, cluster.Metadata.GetReplicas("ks2", new byte[] {0, 0, 0, 1}).Count); var ks3 = cluster.Metadata.GetKeyspace("ks3"); Assert.NotNull(ks3); Assert.AreEqual(ks3.Replication["dc1"], 1); @@ -84,17 +85,20 @@ public void MetadataMethodReconnects() ITestCluster testCluster = TestClusterManager.GetNonShareableTestCluster(2); var cluster = testCluster.Cluster; //The control connection is connected to host 1 - Assert.AreEqual(1, TestHelper.GetLastAddressByte(cluster.Metadata.ControlConnection.EndPoint.GetHostIpEndPointWithFallback())); + Assert.AreEqual(1, TestHelper.GetLastAddressByte( + cluster.InternalRef.InternalMetadata.ControlConnection.EndPoint.GetHostIpEndPointWithFallback())); testCluster.StopForce(1); Thread.Sleep(10000); //The control connection is still connected to host 1 - Assert.AreEqual(1, TestHelper.GetLastAddressByte(cluster.Metadata.ControlConnection.EndPoint.GetHostIpEndPointWithFallback())); + Assert.AreEqual(1, TestHelper.GetLastAddressByte( + cluster.InternalRef.InternalMetadata.ControlConnection.EndPoint.GetHostIpEndPointWithFallback())); var t = cluster.Metadata.GetTable("system", "local"); Assert.NotNull(t); //The control connection should be connected to host 2 - Assert.AreEqual(2, TestHelper.GetLastAddressByte(cluster.Metadata.ControlConnection.EndPoint.GetHostIpEndPointWithFallback())); + Assert.AreEqual(2, TestHelper.GetLastAddressByte( + cluster.InternalRef.InternalMetadata.ControlConnection.EndPoint.GetHostIpEndPointWithFallback())); } [Test] @@ -113,7 +117,7 @@ public void HostDownViaMetadataEvents() //The control connection is connected to host 1 //All host are up - Assert.True(cluster.AllHosts().All(h => h.IsUp)); + Assert.True(cluster.Metadata.AllHosts().All(h => h.IsUp)); testCluster.StopForce(2); var counter = 0; @@ -121,13 +125,13 @@ public void HostDownViaMetadataEvents() //No query to avoid getting a socket exception while (counter++ < maxWait) { - if (cluster.AllHosts().Any(h => TestHelper.GetLastAddressByte(h) == 2 && !h.IsUp)) + if (cluster.Metadata.AllHosts().Any(h => TestHelper.GetLastAddressByte(h) == 2 && !h.IsUp)) { break; } Thread.Sleep(1000); } - Assert.True(cluster.AllHosts().Any(h => TestHelper.GetLastAddressByte(h) == 2 && !h.IsUp)); + Assert.True(cluster.Metadata.AllHosts().Any(h => TestHelper.GetLastAddressByte(h) == 2 && !h.IsUp)); Assert.AreNotEqual(counter, maxWait, "Waited but it was never notified via events"); Assert.True(downEventFired); } @@ -157,7 +161,8 @@ public void MetadataHostsEventTest(bool useControlConnectionHost) } }; //The host not used by the control connection - int hostToKill = TestHelper.GetLastAddressByte(cluster.Metadata.ControlConnection.EndPoint.GetHostIpEndPointWithFallback()); + int hostToKill = TestHelper.GetLastAddressByte( + cluster.InternalRef.InternalMetadata.ControlConnection.EndPoint.GetHostIpEndPointWithFallback()); if (!useControlConnectionHost) { hostToKill = hostToKill == 1 ? 2 : 1; @@ -165,18 +170,19 @@ public void MetadataHostsEventTest(bool useControlConnectionHost) testCluster.Stop(hostToKill); Thread.Sleep(10000); TestHelper.Invoke(() => session.Execute("SELECT key from system.local"), 10); - Assert.True(cluster.AllHosts().Any(h => TestHelper.GetLastAddressByte(h) == hostToKill && !h.IsUp)); + Assert.True(cluster.Metadata.AllHosts().Any(h => TestHelper.GetLastAddressByte(h) == hostToKill && !h.IsUp)); Assert.True(downEventFired); testCluster.Start(hostToKill); Thread.Sleep(20000); TestHelper.Invoke(() => session.Execute("SELECT key from system.local"), 10); - Assert.True(cluster.AllHosts().All(h => h.IsConsiderablyUp)); + Assert.True(cluster.Metadata.AllHosts().All(h => h.IsConsiderablyUp)); //When the host of the control connection is used //It can result that event UP is not fired as it is not received by the control connection (it reconnected but missed the event) Assert.True(upEventFired || useControlConnectionHost); } - private void CheckPureMetadata(Cluster cluster, ISession session, string tableName, string keyspaceName, TableOptions tableOptions = null) + private void CheckPureMetadata( + Cluster cluster, ISession session, string tableName, string keyspaceName, TableOptions tableOptions = null) { // build create table cql tableName = TestUtils.GetUniqueTableName().ToLower(); @@ -219,7 +225,7 @@ private void CheckPureMetadata(Cluster cluster, ISession session, string tableNa stringBuilder.Append("))" + opt + ";"); QueryTools.ExecuteSyncNonQuery(session, stringBuilder.ToString()); - TestUtils.WaitForSchemaAgreement(session.Cluster); + TestUtils.WaitForSchemaAgreement(cluster); var table = cluster.Metadata.GetTable(keyspaceName, tableName); Assert.AreEqual(tableName, table.Name); @@ -544,7 +550,7 @@ public void Should_Retrieve_Host_Cassandra_Version() var testCluster = TestClusterManager.GetNonShareableTestCluster(2, DefaultMaxClusterCreateRetries, true, false); using (var cluster = ClusterBuilder().AddContactPoint(testCluster.InitialContactPoint).Build()) { - CollectionAssert.DoesNotContain(cluster.Metadata.Hosts.Select(h => h.CassandraVersion), null); + CollectionAssert.DoesNotContain(cluster.InternalRef.InternalMetadata.Hosts.Select(h => h.CassandraVersion), null); } } diff --git a/src/Cassandra.IntegrationTests/Core/PoolShortTests.cs b/src/Cassandra.IntegrationTests/Core/PoolShortTests.cs index 51d4d1932..7f5413d60 100644 --- a/src/Cassandra.IntegrationTests/Core/PoolShortTests.cs +++ b/src/Cassandra.IntegrationTests/Core/PoolShortTests.cs @@ -67,7 +67,7 @@ public void StopForce_With_Inflight_Requests(bool useStreamMode) Assert.AreEqual(2, t.Result.Length, "The 2 hosts must have been used"); // Wait for all connections to be opened Thread.Sleep(1000); - var hosts = cluster.AllHosts().ToArray(); + var hosts = cluster.Metadata.AllHosts().ToArray(); TestHelper.WaitUntil(() => hosts.Sum(h => session .GetOrCreateConnectionPool(h, HostDistance.Local) @@ -150,7 +150,7 @@ public async Task MarkHostDown_PartialPoolConnection() using (var cluster = builder.Build()) { var session = (IInternalSession)cluster.Connect(); - var allHosts = cluster.AllHosts(); + var allHosts = cluster.Metadata.AllHosts(); TestHelper.WaitUntil(() => allHosts.Sum(h => session @@ -264,7 +264,7 @@ public async Task PoolingOptions_Create_Based_On_Protocol(ProtocolVersion protoc .Build()) { var session = (IInternalSession)cluster.Connect(); - var allHosts = cluster.AllHosts(); + var allHosts = cluster.Metadata.AllHosts(); var host = allHosts.First(); var pool = session.GetOrCreateConnectionPool(host, HostDistance.Local); @@ -319,7 +319,7 @@ public async Task ControlConnection_Should_Reconnect_To_Up_Host() using (var cluster = builder.AddContactPoint(testCluster.InitialContactPoint).Build()) { var session = (Session)cluster.Connect(); - var allHosts = cluster.AllHosts(); + var allHosts = cluster.Metadata.AllHosts(); Assert.AreEqual(3, allHosts.Count); await TestHelper.TimesLimit(() => session.ExecuteAsync(new SimpleStatement("SELECT * FROM system.local")), 100, 16).ConfigureAwait(false); @@ -330,27 +330,28 @@ await TestHelper.RetryAssertAsync(async () => Assert.AreEqual(4, (await testCluster.GetConnectedPortsAsync().ConfigureAwait(false)).Count); }, 100, 200).ConfigureAwait(false); - var ccAddress = cluster.InternalRef.GetControlConnection().EndPoint.GetHostIpEndPointWithFallback(); + var cc = cluster.InternalRef.InternalMetadata.ControlConnection; + var ccAddress = cc.EndPoint.GetHostIpEndPointWithFallback(); Assert.NotNull(ccAddress); var simulacronNode = testCluster.GetNode(ccAddress); // Disable new connections to the first host await simulacronNode.Stop().ConfigureAwait(false); - TestHelper.WaitUntil(() => !cluster.GetHost(ccAddress).IsUp); + TestHelper.WaitUntil(() => !cluster.Metadata.GetHost(ccAddress).IsUp); - Assert.False(cluster.GetHost(ccAddress).IsUp); + Assert.False(cluster.Metadata.GetHost(ccAddress).IsUp); - TestHelper.WaitUntil(() => !cluster.InternalRef.GetControlConnection().EndPoint.GetHostIpEndPointWithFallback().Address.Equals(ccAddress.Address)); - Assert.NotNull(cluster.InternalRef.GetControlConnection().EndPoint.GetHostIpEndPointWithFallback()); - Assert.AreNotEqual(ccAddress.Address, cluster.InternalRef.GetControlConnection().EndPoint.GetHostIpEndPointWithFallback().Address); + TestHelper.WaitUntil(() => !cc.EndPoint.GetHostIpEndPointWithFallback().Address.Equals(ccAddress.Address)); + Assert.NotNull(cc.EndPoint.GetHostIpEndPointWithFallback()); + Assert.AreNotEqual(ccAddress.Address, cc.EndPoint.GetHostIpEndPointWithFallback().Address); // Previous host is still DOWN - Assert.False(cluster.GetHost(ccAddress).IsUp); + Assert.False(cluster.Metadata.GetHost(ccAddress).IsUp); // New host is UP - ccAddress = cluster.InternalRef.GetControlConnection().EndPoint.GetHostIpEndPointWithFallback(); - Assert.True(cluster.GetHost(ccAddress).IsUp); + ccAddress = cc.EndPoint.GetHostIpEndPointWithFallback(); + Assert.True(cluster.Metadata.GetHost(ccAddress).IsUp); } } @@ -368,7 +369,7 @@ public async Task ControlConnection_Should_Reconnect_After_Failed_Attemps() using (var cluster = builder.AddContactPoint(testCluster.InitialContactPoint).Build()) { var session = (Session)cluster.Connect(); - var allHosts = cluster.AllHosts(); + var allHosts = cluster.Metadata.AllHosts(); Assert.AreEqual(3, allHosts.Count); await TestHelper.TimesLimit(() => session.ExecuteAsync(new SimpleStatement("SELECT * FROM system.local")), 100, 16).ConfigureAwait(false); @@ -385,7 +386,7 @@ await TestHelper.RetryAssertAsync(async () => // Disable all connections await testCluster.DisableConnectionListener().ConfigureAwait(false); - var ccAddress = cluster.InternalRef.GetControlConnection().EndPoint.GetHostIpEndPointWithFallback(); + var ccAddress = cluster.InternalRef.InternalMetadata.ControlConnection.EndPoint.GetHostIpEndPointWithFallback(); // Drop all connections to hosts foreach (var connection in serverConnections) @@ -393,20 +394,20 @@ await TestHelper.RetryAssertAsync(async () => await testCluster.DropConnection(connection).ConfigureAwait(false); } - TestHelper.WaitUntil(() => !cluster.GetHost(ccAddress).IsUp); + TestHelper.WaitUntil(() => !cluster.Metadata.GetHost(ccAddress).IsUp); // All host should be down by now - TestHelper.WaitUntil(() => cluster.AllHosts().All(h => !h.IsUp)); + TestHelper.WaitUntil(() => cluster.Metadata.AllHosts().All(h => !h.IsUp)); - Assert.False(cluster.GetHost(ccAddress).IsUp); + Assert.False(cluster.Metadata.GetHost(ccAddress).IsUp); // Allow new connections to be created await testCluster.EnableConnectionListener().ConfigureAwait(false); - TestHelper.WaitUntil(() => cluster.AllHosts().All(h => h.IsUp)); + TestHelper.WaitUntil(() => cluster.Metadata.AllHosts().All(h => h.IsUp)); - ccAddress = cluster.InternalRef.GetControlConnection().EndPoint.GetHostIpEndPointWithFallback(); - Assert.True(cluster.GetHost(ccAddress).IsUp); + ccAddress = cluster.InternalRef.InternalMetadata.ControlConnection.EndPoint.GetHostIpEndPointWithFallback(); + Assert.True(cluster.Metadata.GetHost(ccAddress).IsUp); // Once all connections are created, the control connection should be usable await TestHelper.RetryAssertAsync(async () => @@ -418,7 +419,7 @@ await TestHelper.RetryAssertAsync(async () => TestHelper.RetryAssert(() => { - Assert.DoesNotThrowAsync(() => cluster.InternalRef.GetControlConnection().QueryAsync("SELECT * FROM system.local")); + Assert.DoesNotThrowAsync(() => cluster.InternalRef.InternalMetadata.ControlConnection.QueryAsync("SELECT * FROM system.local")); }, 100, 100); } } @@ -443,7 +444,7 @@ public async Task Should_Use_Next_Host_When_First_Host_Is_Busy() testCluster.PrimeFluent(b => b.WhenQuery(query).ThenVoidSuccess().WithDelayInMs(3000)); var session = await cluster.ConnectAsync().ConfigureAwait(false); - var hosts = cluster.AllHosts().ToArray(); + var hosts = cluster.Metadata.AllHosts().ToArray(); // Wait until all connections to first host are created await TestHelper.WaitUntilAsync(() => @@ -495,7 +496,7 @@ public async Task Should_Throw_NoHostAvailableException_When_All_Host_Are_Busy() testCluster.PrimeFluent(b => b.WhenQuery(query).ThenVoidSuccess().WithDelayInMs(3000)); var session = await cluster.ConnectAsync().ConfigureAwait(false); - var hosts = cluster.AllHosts().ToArray(); + var hosts = cluster.Metadata.AllHosts().ToArray(); await TestHelper.TimesLimit(() => session.ExecuteAsync(new SimpleStatement("SELECT key FROM system.local")), 100, 16).ConfigureAwait(false); @@ -562,8 +563,8 @@ public async Task Should_Use_Single_Host_When_Configured_At_Statement_Level() using (var cluster = builder.AddContactPoint(testCluster.InitialContactPoint).Build()) { var session = await cluster.ConnectAsync().ConfigureAwait(false); - var firstHost = cluster.AllHosts().First(); - var lastHost = cluster.AllHosts().Last(); + var firstHost = cluster.Metadata.AllHosts().First(); + var lastHost = cluster.Metadata.AllHosts().Last(); // The test load-balancing policy targets always the first host await TestHelper.TimesLimit(async () => @@ -591,15 +592,15 @@ public void Should_Throw_NoHostAvailableException_When_Targeting_Single_Ignored_ const string query = "SELECT * FROM system.local"; // Mark the last host as ignored var lbp = new TestHelper.CustomLoadBalancingPolicy( - (cluster, ks, stmt) => cluster.AllHosts(), - (cluster, host) => host.Equals(cluster.AllHosts().Last()) ? HostDistance.Ignored : HostDistance.Local); + (cluster, ks, stmt) => cluster.Metadata.AllHosts(), + (metadata, host) => host.Equals(metadata.AllHostsSnapshot().Last()) ? HostDistance.Ignored : HostDistance.Local); var builder = ClusterBuilder().WithLoadBalancingPolicy(lbp); using (var testCluster = SimulacronCluster.CreateNew(new SimulacronOptions { Nodes = "3" })) using (var cluster = builder.AddContactPoint(testCluster.InitialContactPoint).Build()) { var session = cluster.Connect(); - var lastHost = cluster.AllHosts().Last(); + var lastHost = cluster.Metadata.AllHosts().Last(); // Use the last host var statement = new SimpleStatement(query).SetHost(lastHost); @@ -620,7 +621,7 @@ public async Task Should_Throw_NoHostAvailableException_When_Targeting_Single_Ho using (var cluster = builder.AddContactPoint(testCluster.InitialContactPoint).Build()) { var session = await cluster.ConnectAsync().ConfigureAwait(false); - var lastHost = cluster.AllHosts().Last(); + var lastHost = cluster.Metadata.AllHosts().Last(); // 1 for the control connection and 1 connection per each host await TestHelper.RetryAssertAsync(async () => @@ -650,7 +651,7 @@ await TestHelper.RetryAssertAsync(async () => TestHelper.RetryAssert(() => { - var openConnections = session.GetState().GetOpenConnections(session.Cluster.GetHost(simulacronNode.IpEndPoint)); + var openConnections = session.GetState().GetOpenConnections(session.Cluster.Metadata.GetHost(simulacronNode.IpEndPoint)); Assert.AreEqual(0, openConnections); }, 100, 200); @@ -684,7 +685,7 @@ public void The_Query_Plan_Should_Contain_A_Single_Host_When_Targeting_Single_Ho testCluster.PrimeFluent(b => b.WhenQuery(query).ThenOverloaded("Test overloaded error")); var session = cluster.Connect(); - var host = cluster.AllHosts().Last(); + var host = cluster.Metadata.AllHosts().Last(); var statement = new SimpleStatement(query).SetHost(host).SetIdempotence(true); @@ -705,7 +706,7 @@ public async Task Should_Not_Use_The_LoadBalancingPolicy_When_Targeting_Single_H var lbp = new TestHelper.CustomLoadBalancingPolicy((cluster, ks, stmt) => { Interlocked.Increment(ref queryPlanCounter); - return cluster.AllHosts(); + return cluster.Metadata.AllHosts(); }); var builder = ClusterBuilder().WithLoadBalancingPolicy(lbp); @@ -714,7 +715,7 @@ public async Task Should_Not_Use_The_LoadBalancingPolicy_When_Targeting_Single_H using (var cluster = builder.AddContactPoint(testCluster.InitialContactPoint).Build()) { var session = await cluster.ConnectAsync().ConfigureAwait(false); - var host = cluster.AllHosts().Last(); + var host = cluster.Metadata.AllHosts().Last(); Interlocked.Exchange(ref queryPlanCounter, 0); await TestHelper.TimesLimit(() => diff --git a/src/Cassandra.IntegrationTests/Core/PoolTests.cs b/src/Cassandra.IntegrationTests/Core/PoolTests.cs index fd50c0ffd..ca6322bd3 100644 --- a/src/Cassandra.IntegrationTests/Core/PoolTests.cs +++ b/src/Cassandra.IntegrationTests/Core/PoolTests.cs @@ -146,7 +146,7 @@ public void FailoverThenReconnect() { actions.Add(selectAction); //Check that the control connection is using first host - StringAssert.StartsWith(nonShareableTestCluster.ClusterIpPrefix + "1", nonShareableTestCluster.Cluster.Metadata.ControlConnection.EndPoint.GetHostIpEndPointWithFallback().ToString()); + StringAssert.StartsWith(nonShareableTestCluster.ClusterIpPrefix + "1", nonShareableTestCluster.Cluster.InternalRef.InternalMetadata.ControlConnection.EndPoint.GetHostIpEndPointWithFallback().ToString()); //Kill some nodes //Including the one used by the control connection @@ -198,7 +198,7 @@ public void FailoverThenReconnect() Assert.Contains(nonShareableTestCluster.ClusterIpPrefix + "3:" + DefaultCassandraPort, queriedHosts); Assert.Contains(nonShareableTestCluster.ClusterIpPrefix + "4:" + DefaultCassandraPort, queriedHosts); //Check that the control connection is still using last host - StringAssert.StartsWith(nonShareableTestCluster.ClusterIpPrefix + "4", nonShareableTestCluster.Cluster.Metadata.ControlConnection.EndPoint.GetHostIpEndPointWithFallback().ToString()); + StringAssert.StartsWith(nonShareableTestCluster.ClusterIpPrefix + "4", nonShareableTestCluster.Cluster.InternalRef.InternalMetadata.ControlConnection.EndPoint.GetHostIpEndPointWithFallback().ToString()); } } } @@ -218,7 +218,7 @@ public void ReconnectionAttemptedOnlyOnce() .WithReconnectionPolicy(new ConstantReconnectionPolicy(reconnectionDelay)) .Build(); var connectionAttempts = 0; - cluster.Metadata.Hosts.Down += h => + cluster.InternalRef.InternalMetadata.Hosts.Down += h => { //Every time there is a connection attempt, it is marked as down connectionAttempts++; @@ -275,12 +275,12 @@ public void AddressTranslatorIsCalledPerEachPeer() cluster.Connect(); //2 peers translated Assert.AreEqual(2, invokedEndPoints.Count); - Assert.True(cluster.AllHosts().All(h => h.IsUp)); + Assert.True(cluster.Metadata.AllHosts().All(h => h.IsUp)); testCluster.Stop(3); //Wait for the C* event to notify the control connection Thread.Sleep(30000); //Should be down - Assert.False(cluster.AllHosts().First(h => TestHelper.GetLastAddressByte(h) == 3).IsUp); + Assert.False(cluster.Metadata.AllHosts().First(h => TestHelper.GetLastAddressByte(h) == 3).IsUp); //Should have been translated Assert.AreEqual(3, invokedEndPoints.Count); diff --git a/src/Cassandra.IntegrationTests/Core/PrepareLongTests.cs b/src/Cassandra.IntegrationTests/Core/PrepareLongTests.cs index eb59b94d5..9a590b957 100644 --- a/src/Cassandra.IntegrationTests/Core/PrepareLongTests.cs +++ b/src/Cassandra.IntegrationTests/Core/PrepareLongTests.cs @@ -36,7 +36,7 @@ public void PreparedStatement_Is_Usable_After_Node_Restart_When_Connecting_Provi // Connect using a keyspace var session = cluster.Connect("system"); var ps = session.Prepare("SELECT key FROM local"); - var host = cluster.AllHosts().First(); + var host = cluster.Metadata.AllHosts().First(); var row = session.Execute(ps.Bind()).First(); Assert.NotNull(row.GetValue("key")); diff --git a/src/Cassandra.IntegrationTests/Core/PrepareSimulatorTests.cs b/src/Cassandra.IntegrationTests/Core/PrepareSimulatorTests.cs index 0adfa9a7e..c1caf20fa 100644 --- a/src/Cassandra.IntegrationTests/Core/PrepareSimulatorTests.cs +++ b/src/Cassandra.IntegrationTests/Core/PrepareSimulatorTests.cs @@ -80,7 +80,7 @@ public void Should_Prepare_On_First_Node() Assert.AreEqual(Query, ps.Cql); var firstRow = session.Execute(ps.Bind()).FirstOrDefault(); Assert.NotNull(firstRow); - var node = simulacronCluster.GetNode(cluster.AllHosts().First().Address); + var node = simulacronCluster.GetNode(cluster.Metadata.AllHosts().First().Address); // Executed on first node Assert.AreEqual(1, node.GetQueries(Query, QueryType.Prepare).Count); // Only executed on the first node @@ -135,8 +135,8 @@ public void Should_Failover_When_First_Node_Fails() .WithLoadBalancingPolicy(new TestHelper.OrderedLoadBalancingPolicy()).Build()) { var session = cluster.Connect(); - var firstHost = cluster.AllHosts().First(); - foreach (var h in cluster.AllHosts()) + var firstHost = cluster.Metadata.AllHosts().First(); + foreach (var h in cluster.Metadata.AllHosts()) { var node = simulacronCluster.GetNode(h.Address); node.Prime(h == firstHost ? PrepareSimulatorTests.IsBootstrappingPrime : QueryPrime()); @@ -157,8 +157,8 @@ public void Should_Prepare_On_All_Ignoring_Individual_Failures() .WithLoadBalancingPolicy(new TestHelper.OrderedLoadBalancingPolicy()).Build()) { var session = cluster.Connect(); - var secondHost = cluster.AllHosts().Skip(1).First(); - foreach (var h in cluster.AllHosts()) + var secondHost = cluster.Metadata.AllHosts().Skip(1).First(); + foreach (var h in cluster.Metadata.AllHosts()) { var node = simulacronCluster.GetNode(h.Address); node.Prime(h == secondHost ? PrepareSimulatorTests.IsBootstrappingPrime : QueryPrime()); @@ -181,8 +181,8 @@ public void Should_Failover_When_First_Node_Timeouts() .WithLoadBalancingPolicy(new TestHelper.OrderedLoadBalancingPolicy()).Build()) { var session = cluster.Connect(); - var firstHost = cluster.AllHosts().First(); - foreach (var h in cluster.AllHosts()) + var firstHost = cluster.Metadata.AllHosts().First(); + foreach (var h in cluster.Metadata.AllHosts()) { var node = simulacronCluster.GetNode(h.Address); node.Prime(QueryPrime(h == firstHost ? 10000 : 0)); @@ -212,11 +212,11 @@ public async Task Should_Reprepare_On_Up_Node() // It should have been prepared once on the node we are about to stop Assert.AreEqual(1, node.GetQueries(Query, QueryType.Prepare).Count); await node.Stop().ConfigureAwait(false); - await TestHelper.WaitUntilAsync(() => cluster.AllHosts().Any(h => !h.IsUp)).ConfigureAwait(false); - Assert.AreEqual(1, cluster.AllHosts().Count(h => !h.IsUp)); + await TestHelper.WaitUntilAsync(() => cluster.Metadata.AllHosts().Any(h => !h.IsUp)).ConfigureAwait(false); + Assert.AreEqual(1, cluster.Metadata.AllHosts().Count(h => !h.IsUp)); await node.Start().ConfigureAwait(false); - await TestHelper.WaitUntilAsync(() => cluster.AllHosts().All(h => h.IsUp)).ConfigureAwait(false); - Assert.AreEqual(0, cluster.AllHosts().Count(h => !h.IsUp)); + await TestHelper.WaitUntilAsync(() => cluster.Metadata.AllHosts().All(h => h.IsUp)).ConfigureAwait(false); + Assert.AreEqual(0, cluster.Metadata.AllHosts().Count(h => !h.IsUp)); TestHelper.WaitUntil(() => node.GetQueries(Query, QueryType.Prepare).Count == 2); // It should be prepared 2 times Assert.AreEqual(2, node.GetQueries(Query, QueryType.Prepare).Count); @@ -249,8 +249,8 @@ public async Task Should_ReprepareOnUpNodeAfterSetKeyspace_With_SessionKeyspace( var node = simulacronCluster.GetNodes().Skip(1).First(); await node.Stop().ConfigureAwait(false); - await TestHelper.WaitUntilAsync(() => cluster.AllHosts().Any(h => !h.IsUp)).ConfigureAwait(false); - Assert.AreEqual(1, cluster.AllHosts().Count(h => !h.IsUp)); + await TestHelper.WaitUntilAsync(() => cluster.Metadata.AllHosts().Any(h => !h.IsUp)).ConfigureAwait(false); + Assert.AreEqual(1, cluster.Metadata.AllHosts().Count(h => !h.IsUp)); // still only 1 USE and Prepare requests Assert.AreEqual(1, node.GetQueries($"USE \"{PrepareSimulatorTests.Keyspace}\"").Count); @@ -261,8 +261,8 @@ public async Task Should_ReprepareOnUpNodeAfterSetKeyspace_With_SessionKeyspace( await node.Start().ConfigureAwait(false); // wait until node is up - await TestHelper.WaitUntilAsync(() => cluster.AllHosts().All(h => h.IsUp)).ConfigureAwait(false); - Assert.AreEqual(0, cluster.AllHosts().Count(h => !h.IsUp)); + await TestHelper.WaitUntilAsync(() => cluster.Metadata.AllHosts().All(h => h.IsUp)).ConfigureAwait(false); + Assert.AreEqual(0, cluster.Metadata.AllHosts().Count(h => !h.IsUp)); // wait until driver reprepares the statement TestHelper.WaitUntil(() => diff --git a/src/Cassandra.IntegrationTests/Core/PreparedStatementsTests.cs b/src/Cassandra.IntegrationTests/Core/PreparedStatementsTests.cs index 905da0b12..34e9fe589 100644 --- a/src/Cassandra.IntegrationTests/Core/PreparedStatementsTests.cs +++ b/src/Cassandra.IntegrationTests/Core/PreparedStatementsTests.cs @@ -25,6 +25,7 @@ using System.Collections; using System.Threading; using Cassandra.IntegrationTests.TestBase; +using Cassandra.SessionManagement; using Cassandra.Tests; namespace Cassandra.IntegrationTests.Core @@ -182,7 +183,7 @@ public void PreparedStatement_With_Changing_Schema() var selectPs1 = session1.Prepare("SELECT * FROM table1"); var selectPs2 = session2.Prepare("SELECT * FROM table1"); - var protocolVersion = (ProtocolVersion)session1.BinaryProtocolVersion; + var protocolVersion = session1.Cluster.Metadata.GetClusterDescription().ProtocolVersion; if (protocolVersion.SupportsResultMetadataId()) { originalResultMetadataId = selectPs1.ResultMetadata.ResultMetadataId; @@ -841,7 +842,7 @@ public void Session_Prepare_With_Keyspace_Defined_On_Protocol_Greater_Than_4(boo } Assert.AreEqual("system", ps.Keyspace); - for (var i = 0; i < Cluster.AllHosts().Count; i++) + for (var i = 0; i < Cluster.Metadata.AllHosts().Count; i++) { var boundStatement = ps.Bind(); Assert.AreEqual("system", boundStatement.Keyspace); @@ -875,7 +876,7 @@ await TestHelper.TimesLimit(async () => var rs = await Session.ExecuteAsync(boundStatement).ConfigureAwait(false); Assert.NotNull(rs.First().GetValue("key")); return rs; - }, Cluster.AllHosts().Count, Cluster.AllHosts().Count).ConfigureAwait(false); + }, Cluster.Metadata.AllHosts().Count, Cluster.Metadata.AllHosts().Count).ConfigureAwait(false); } [Test] @@ -966,7 +967,7 @@ public void Prepared_SelectOne() tweet_id int PRIMARY KEY, numb double, label text);", tableName)); - TestUtils.WaitForSchemaAgreement(Session.Cluster); + TestUtils.WaitForSchemaAgreement(InternalSession.InternalCluster); } catch (AlreadyExistsException) { @@ -1146,7 +1147,7 @@ public void InsertingSingleValuePrepared(Type tp, object value = null) value {1} );", tableName, cassandraDataTypeName)); - TestUtils.WaitForSchemaAgreement(Session.Cluster); + TestUtils.WaitForSchemaAgreement(InternalSession.InternalCluster); var toInsert = new List(1); object val = Randomm.RandomVal(tp); @@ -1174,17 +1175,17 @@ public void InsertingSingleValuePrepared(Type tp, object value = null) private void CreateTable(string tableName) { - CreateTable(Session, tableName); + CreateTable(InternalSession, tableName); } - private void CreateTable(ISession session, string tableName) + private void CreateTable(IInternalSession session, string tableName) { QueryTools.ExecuteSyncNonQuery(session, $@"CREATE TABLE {tableName}( id int PRIMARY KEY, label text, number int );"); - TestUtils.WaitForSchemaAgreement(session.Cluster); + TestUtils.WaitForSchemaAgreement(session.InternalCluster); } } } diff --git a/src/Cassandra.IntegrationTests/Core/ReconnectionTests.cs b/src/Cassandra.IntegrationTests/Core/ReconnectionTests.cs index e3f6b138e..f08e14ef0 100644 --- a/src/Cassandra.IntegrationTests/Core/ReconnectionTests.cs +++ b/src/Cassandra.IntegrationTests/Core/ReconnectionTests.cs @@ -86,7 +86,7 @@ public void Reconnection_Attempted_Multiple_Times() //Another session to have multiple pools var dummySession = cluster.Connect(); TestHelper.Invoke(() => dummySession.Execute("SELECT * FROM system.local"), 10); - var host = cluster.AllHosts().First(); + var host = cluster.Metadata.AllHosts().First(); var upCounter = 0; var downCounter = 0; cluster.Metadata.HostsEvent += (sender, e) => @@ -157,7 +157,7 @@ public void Reconnection_Attempted_Multiple_Times_On_Multiple_Nodes() //Another session to have multiple pools var dummySession = cluster.Connect(); TestHelper.Invoke(() => dummySession.Execute("SELECT * FROM system.local"), 10); - var hosts = cluster.AllHosts().ToList(); + var hosts = cluster.Metadata.AllHosts().ToList(); var host1 = hosts[0]; var host2 = hosts[1]; var upCounter = new ConcurrentDictionary(); @@ -235,7 +235,7 @@ public void Executions_After_Reconnection_Resizes_Pool() var session2 = (IInternalSession)cluster.Connect(); TestHelper.Invoke(() => session1.Execute("SELECT * FROM system.local"), 10); TestHelper.Invoke(() => session2.Execute("SELECT * FROM system.local"), 10); - var hosts = cluster.AllHosts().ToList(); + var hosts = cluster.Metadata.AllHosts().ToList(); var host1 = hosts[0]; var host2 = hosts[1]; var upCounter = new ConcurrentDictionary(); @@ -309,7 +309,7 @@ public void Should_UseNewHostInQueryPlans_When_HostIsDecommissionedAndJoinsAgain session.Execute("CREATE TABLE test_table (id text, PRIMARY KEY (id))"); // Assert that there are 2 pools, one for each host - var hosts = session.Cluster.AllHosts().ToList(); + var hosts = session.Cluster.Metadata.AllHosts().ToList(); var pool1 = session.GetExistingPool(hosts[0].Address); Assert.AreEqual(2, pool1.OpenConnections); var pool2 = session.GetExistingPool(hosts[1].Address); @@ -352,7 +352,7 @@ public void Should_UseNewHostInQueryPlans_When_HostIsDecommissionedAndJoinsAgain // Assert that there are 2 hosts TestHelper.RetryAssert(() => { - Assert.AreEqual(2, cluster.AllHosts().Count); + Assert.AreEqual(2, cluster.Metadata.AllHosts().Count); }, 1000, 180); // Assert that queries use both hosts again @@ -402,12 +402,12 @@ public void Should_UpdateHosts_When_HostIpChanges() { var session = (IInternalSession)cluster.Connect(); session.CreateKeyspaceIfNotExists("ks1"); - var hosts = session.Cluster.AllHosts().OrderBy(h => h.Address.ToString()).ToArray(); + var hosts = session.Cluster.Metadata.AllHosts().OrderBy(h => h.Address.ToString()).ToArray(); Assert.AreEqual(3, hosts.Length); Assert.AreEqual(3, session.GetPools().Count()); Assert.IsNotNull(session.GetExistingPool(oldEndPoint)); Assert.IsTrue(hosts.Select(h => h.Address.Address.ToString()).SequenceEqual(addresses)); - var tokenMap = session.Cluster.Metadata.TokenToReplicasMap; + var tokenMap = session.InternalCluster.InternalMetadata.TokenToReplicasMap; var oldHostTokens = tokenMap.GetByKeyspace("ks1") .Where(kvp => kvp.Value.First().Address.Address.ToString().Equals(oldIp)); @@ -424,20 +424,20 @@ public void Should_UpdateHosts_When_HostIpChanges() () => { Assert.AreEqual(1, - session.Cluster.AllHosts() + session.Cluster.Metadata.AllHosts() .Count(h => h.Address.Address.ToString().Equals($"{_realCluster.Value.ClusterIpPrefix}4")), - string.Join(";", session.Cluster.AllHosts().Select(h => h.Address.Address.ToString()))); - Assert.AreNotSame(tokenMap, session.Cluster.Metadata.TokenToReplicasMap); + string.Join(";", session.Cluster.Metadata.AllHosts().Select(h => h.Address.Address.ToString()))); + Assert.AreNotSame(tokenMap, session.InternalCluster.InternalMetadata.TokenToReplicasMap); }, 500, 100); - var newTokenMap = session.Cluster.Metadata.TokenToReplicasMap; + var newTokenMap = session.InternalCluster.InternalMetadata.TokenToReplicasMap; var newHostTokens = newTokenMap.GetByKeyspace("ks1") .Where(kvp => kvp.Value.First().Address.Address.ToString().Equals(newIp)).ToList(); - hosts = session.Cluster.AllHosts().ToArray(); + hosts = session.Cluster.Metadata.AllHosts().ToArray(); Assert.AreEqual(3, hosts.Length); Assert.IsTrue(hosts.Select(h => h.Address.Address.ToString()).SequenceEqual(newAddresses)); Assert.IsTrue(newHostTokens.Select(h => h.Key).SequenceEqual(oldHostTokens.Select(h => h.Key))); diff --git a/src/Cassandra.IntegrationTests/Core/SchemaMetadataTests.cs b/src/Cassandra.IntegrationTests/Core/SchemaMetadataTests.cs index da6ba842f..ec36d847d 100644 --- a/src/Cassandra.IntegrationTests/Core/SchemaMetadataTests.cs +++ b/src/Cassandra.IntegrationTests/Core/SchemaMetadataTests.cs @@ -737,7 +737,7 @@ public void CassandraVersion_Should_Be_Obtained_From_Host_Metadata(bool metadata var cluster = GetNewTemporaryCluster(builder => builder.WithMetadataSyncOptions(new MetadataSyncOptions().SetMetadataSyncEnabled(metadataSync))); var _ = cluster.Connect(); - foreach (var host in Cluster.AllHosts()) + foreach (var host in Cluster.Metadata.AllHosts()) { Assert.NotNull(host.CassandraVersion); Assert.Greater(host.CassandraVersion, new Version(1, 2)); diff --git a/src/Cassandra.IntegrationTests/Core/SessionStateTests.cs b/src/Cassandra.IntegrationTests/Core/SessionStateTests.cs index 0571f6542..fca173996 100644 --- a/src/Cassandra.IntegrationTests/Core/SessionStateTests.cs +++ b/src/Cassandra.IntegrationTests/Core/SessionStateTests.cs @@ -60,7 +60,7 @@ public async Task Session_GetState_Should_Return_A_Snapshot_Of_The_Pools_State() .WithPoolingOptions(poolingOptions) .Build()) { - var session = cluster.Connect(); + var session = await cluster.ConnectAsync().ConfigureAwait(false); var counter = 0; ISessionState state = null; // Warmup @@ -79,13 +79,13 @@ await TestHelper.TimesLimit(async () => }, 280, 100).ConfigureAwait(false); Assert.NotNull(state); var stringState = state.ToString(); - CollectionAssert.AreEquivalent(cluster.AllHosts(), state.GetConnectedHosts()); - foreach (var host in cluster.AllHosts()) + CollectionAssert.AreEquivalent(cluster.Metadata.AllHosts(), state.GetConnectedHosts()); + foreach (var host in cluster.Metadata.AllHosts()) { Assert.AreEqual(2, state.GetOpenConnections(host)); StringAssert.Contains($"\"{host.Address}\": {{", stringState); } - var totalInFlight = cluster.AllHosts().Sum(h => state.GetInFlightQueries(h)); + var totalInFlight = cluster.Metadata.AllHosts().Sum(h => state.GetInFlightQueries(h)); Assert.Greater(totalInFlight, 0); Assert.LessOrEqual(totalInFlight, limit); } @@ -104,7 +104,7 @@ public void Session_GetState_Should_Return_Zero_After_Cluster_Disposal() session = cluster.Connect(); state = session.GetState(); Assert.AreNotEqual(SessionState.Empty(), state); - hosts = cluster.AllHosts(); + hosts = cluster.Metadata.AllHosts(); } state = session.GetState(); Assert.NotNull(hosts); diff --git a/src/Cassandra.IntegrationTests/Core/SessionTests.cs b/src/Cassandra.IntegrationTests/Core/SessionTests.cs index a2b1e8c01..d5301ee95 100644 --- a/src/Cassandra.IntegrationTests/Core/SessionTests.cs +++ b/src/Cassandra.IntegrationTests/Core/SessionTests.cs @@ -171,7 +171,7 @@ public void Session_Keyspace_Create_Case_Sensitive() } [Test] - public void Should_Create_The_Right_Amount_Of_Connections() + public async Task Should_Create_The_Right_Amount_Of_Connections() { var localCluster1 = GetNewTemporaryCluster( builder => builder @@ -180,7 +180,7 @@ public void Should_Create_The_Right_Amount_Of_Connections() .SetCoreConnectionsPerHost(HostDistance.Local, 3))); var localSession1 = (IInternalSession)localCluster1.Connect(); - var hosts1 = localCluster1.AllHosts().ToList(); + var hosts1 = (await localCluster1.Metadata.AllHostsAsync().ConfigureAwait(false)).ToList(); Assert.AreEqual(3, hosts1.Count); //Execute multiple times a query on the newly created keyspace for (var i = 0; i < 12; i++) @@ -188,7 +188,7 @@ public void Should_Create_The_Right_Amount_Of_Connections() localSession1.Execute("SELECT * FROM system.local"); } - Thread.Sleep(2000); + await Task.Delay(2000).ConfigureAwait(false); var pool11 = localSession1.GetOrCreateConnectionPool(hosts1[0], HostDistance.Local); var pool12 = localSession1.GetOrCreateConnectionPool(hosts1[1], HostDistance.Local); Assert.That(pool11.OpenConnections, Is.EqualTo(3)); @@ -200,7 +200,7 @@ public void Should_Create_The_Right_Amount_Of_Connections() .Build()) { var localSession2 = (IInternalSession)localCluster2.Connect(); - var hosts2 = localCluster2.AllHosts().ToList(); + var hosts2 = (await localCluster2.Metadata.AllHostsAsync().ConfigureAwait(false)).ToList(); Assert.AreEqual(3, hosts2.Count); //Execute multiple times a query on the newly created keyspace for (var i = 0; i < 6; i++) @@ -208,7 +208,7 @@ public void Should_Create_The_Right_Amount_Of_Connections() localSession2.Execute("SELECT * FROM system.local"); } - Thread.Sleep(2000); + await Task.Delay(2000).ConfigureAwait(false); var pool21 = localSession2.GetOrCreateConnectionPool(hosts2[0], HostDistance.Local); var pool22 = localSession2.GetOrCreateConnectionPool(hosts2[1], HostDistance.Local); Assert.That(pool21.OpenConnections, Is.EqualTo(1)); @@ -237,12 +237,14 @@ public async Task Session_With_Host_Changing_Distance() var counter = 0; using (var localCluster = builder.Build()) { - var localSession = (IInternalSession)localCluster.Connect(); - var remoteHost = localCluster.AllHosts().First(h => TestHelper.GetLastAddressByte(h) == 2); + var localSession = (IInternalSession) await localCluster.ConnectAsync().ConfigureAwait(false); + var remoteHost = + (await localCluster.Metadata.AllHostsAsync().ConfigureAwait(false)) + .First(h => TestHelper.GetLastAddressByte(h) == 2); var stopWatch = new Stopwatch(); var distanceReset = 0; TestHelper.Invoke(() => localSession.Execute("SELECT key FROM system.local"), 10); - var hosts = localCluster.AllHosts().ToArray(); + var hosts = localCluster.Metadata.AllHosts().ToArray(); var pool1 = localSession.GetOrCreateConnectionPool(hosts[0], HostDistance.Local); var pool2 = localSession.GetOrCreateConnectionPool(hosts[1], HostDistance.Local); var tcs = new TaskCompletionSource(); @@ -306,23 +308,19 @@ public void SetRemoteHost(Host h) _ignoredHost = h; } - public void Initialize(ICluster cluster) + public Task InitializeAsync(IMetadataSnapshotProvider metadata) { - _childPolicy.Initialize(cluster); + return _childPolicy.InitializeAsync(metadata); } - public HostDistance Distance(Host host) + public HostDistance Distance(IMetadataSnapshotProvider metadata, Host host) { - if (host == _ignoredHost) - { - return HostDistance.Ignored; - } - return HostDistance.Local; + return host == _ignoredHost ? HostDistance.Ignored : HostDistance.Local; } - public IEnumerable NewQueryPlan(string keyspace, IStatement query) + public IEnumerable NewQueryPlan(ICluster cluster, string keyspace, IStatement query) { - return _childPolicy.NewQueryPlan(keyspace, query); + return _childPolicy.NewQueryPlan(cluster, keyspace, query); } } @@ -336,7 +334,7 @@ public async Task Session_Disposed_On_Cluster() var session1 = cluster.Connect(); var session2 = cluster.Connect(); var isDown = 0; - foreach (var host in cluster.AllHosts()) + foreach (var host in await cluster.Metadata.AllHostsAsync().ConfigureAwait(false)) { host.Down += _ => Interlocked.Increment(ref isDown); } @@ -347,10 +345,10 @@ public async Task Session_Disposed_On_Cluster() session1.Dispose(); // All nodes should be up - Assert.AreEqual(cluster.AllHosts().Count, cluster.AllHosts().Count(h => h.IsUp)); + Assert.AreEqual(cluster.Metadata.AllHosts().Count, cluster.Metadata.AllHosts().Count(h => h.IsUp)); // And session2 should be queryable await TestHelper.TimesLimit(() => session2.ExecuteAsync(new SimpleStatement(query)), 100, 32).ConfigureAwait(false); - Assert.AreEqual(cluster.AllHosts().Count, cluster.AllHosts().Count(h => h.IsUp)); + Assert.AreEqual(cluster.Metadata.AllHosts().Count, cluster.Metadata.AllHosts().Count(h => h.IsUp)); cluster.Dispose(); Assert.AreEqual(0, Volatile.Read(ref isDown)); } diff --git a/src/Cassandra.IntegrationTests/Core/SpeculativeExecutionLongTests.cs b/src/Cassandra.IntegrationTests/Core/SpeculativeExecutionLongTests.cs index 209fbd64d..e4f2e07e9 100644 --- a/src/Cassandra.IntegrationTests/Core/SpeculativeExecutionLongTests.cs +++ b/src/Cassandra.IntegrationTests/Core/SpeculativeExecutionLongTests.cs @@ -38,8 +38,11 @@ public class SpeculativeExecutionLongTests : TestGlobals private ITestCluster _testCluster; private ISession GetSession( - ISpeculativeExecutionPolicy speculativeExecutionPolicy = null, bool warmup = true, - ILoadBalancingPolicy lbp = null, PoolingOptions pooling = null) + ISpeculativeExecutionPolicy speculativeExecutionPolicy = null, + bool warmup = true, + ILoadBalancingPolicy lbp = null, + PoolingOptions pooling = null, + ProtocolVersion maxProtocolVersion = ProtocolVersion.MaxSupported) { if (_testCluster == null) { @@ -50,7 +53,8 @@ private ISession GetSession( .WithSpeculativeExecutionPolicy(speculativeExecutionPolicy) .WithLoadBalancingPolicy(lbp ?? Cassandra.Policies.DefaultLoadBalancingPolicy) .WithRetryPolicy(DowngradingConsistencyRetryPolicy.Instance) - .WithSocketOptions(new SocketOptions().SetReadTimeoutMillis(0)); + .WithSocketOptions(new SocketOptions().SetReadTimeoutMillis(0)) + .WithMaxProtocolVersion(maxProtocolVersion); if (pooling != null) { builder.WithPoolingOptions(pooling); @@ -85,42 +89,33 @@ public void TestFixtureTearDown() [Test, TestTimeout(120000)] public void SpeculativeExecution_Pause_Using_All_Stream_Ids() { - var maxProtocolVersion = Cluster.MaxProtocolVersion; _testCluster = TestClusterManager.GetNonShareableTestCluster(2, 1, true, false); - Cluster.MaxProtocolVersion = 2; - try + var pooling = PoolingOptions.Create(); + var session = GetSession(new ConstantSpeculativeExecutionPolicy(50L, 1), true, null, pooling, ProtocolVersion.V2); + const int pauseThreshold = 140 * 2; + var tasks = new List>(); + var semaphore = new SemaphoreSlim(150 * 2); + for (var i = 0; i < 512; i++) { - var pooling = PoolingOptions.Create(); - var session = GetSession(new ConstantSpeculativeExecutionPolicy(50L, 1), true, null, pooling); - const int pauseThreshold = 140 * 2; - var tasks = new List>(); - var semaphore = new SemaphoreSlim(150 * 2); - for (var i = 0; i < 512; i++) + //Pause after the stream ids are in use for the connections + if (i == pauseThreshold) { - //Pause after the stream ids are in use for the connections - if (i == pauseThreshold) - { - _testCluster.PauseNode(1); - } - semaphore.Wait(); - tasks.Add(session - .ExecuteAsync(new SimpleStatement(QueryLocal).SetIdempotence(true)) - .ContinueSync(rs => - { - semaphore.Release(); - return rs.Info.QueriedHost.Address; - })); + _testCluster.PauseNode(1); } - Task.WaitAll(tasks.Select(t => (Task)t).ToArray()); - _testCluster.ResumeNode(1); - //There shouldn't be any query using node1 as coordinator passed the threshold. - Assert.AreEqual(0, tasks.Skip(pauseThreshold).Count(t => t.Result.Equals(_addressNode1))); - Thread.Sleep(1000); - } - finally - { - Cluster.MaxProtocolVersion = maxProtocolVersion; + semaphore.Wait(); + tasks.Add(session + .ExecuteAsync(new SimpleStatement(QueryLocal).SetIdempotence(true)) + .ContinueSync(rs => + { + semaphore.Release(); + return rs.Info.QueriedHost.Address; + })); } + Task.WaitAll(tasks.Select(t => (Task)t).ToArray()); + _testCluster.ResumeNode(1); + //There shouldn't be any query using node1 as coordinator passed the threshold. + Assert.AreEqual(0, tasks.Skip(pauseThreshold).Count(t => t.Result.Equals(_addressNode1))); + Thread.Sleep(1000); } /// diff --git a/src/Cassandra.IntegrationTests/Core/SpeculativeExecutionShortTests.cs b/src/Cassandra.IntegrationTests/Core/SpeculativeExecutionShortTests.cs index 13cb87442..532337be9 100644 --- a/src/Cassandra.IntegrationTests/Core/SpeculativeExecutionShortTests.cs +++ b/src/Cassandra.IntegrationTests/Core/SpeculativeExecutionShortTests.cs @@ -21,7 +21,9 @@ using System.Net; using System.Threading; using System.Threading.Tasks; + using Cassandra.IntegrationTests.TestClusterManagement.Simulacron; +using Cassandra.Tasks; using Cassandra.Tests; using NUnit.Framework; @@ -67,7 +69,7 @@ private ISession GetSession( public SpeculativeExecutionShortTests() : base(false, new SimulacronOptions { Nodes = "2" }, false) { } - + public override void SetUp() { base.SetUp(); @@ -111,7 +113,7 @@ public async Task SpeculativeExecution_Should_Not_Execute_On_Next_Node_When_Not_ TestCluster.GetNode(1).PrimeFluent( b => b.WhenQuery(QueryLocal) - .ThenRowsSuccess(new[] {"key"}, r => r.WithRow("local")).WithDelayInMs(1000)); + .ThenRowsSuccess(new[] { "key" }, r => r.WithRow("local")).WithDelayInMs(1000)); var rs = await session.ExecuteAsync(new SimpleStatement(QueryLocal).SetIdempotence(false)).ConfigureAwait(false); @@ -154,15 +156,17 @@ public ICollection ScheduledMoreThanOnce get { return _scheduledMore.Values; } } - public void Dispose() + public Task ShutdownAsync() { + return TaskHelper.Completed; } - public void Initialize(ICluster cluster) + public Task InitializeAsync(IMetadataSnapshotProvider metadata) { + return TaskHelper.Completed; } - public ISpeculativeExecutionPlan NewPlan(string keyspace, IStatement statement) + public ISpeculativeExecutionPlan NewPlan(ICluster cluster, string keyspace, IStatement statement) { return new LoggedSpeculativeExecutionPlan(this); } @@ -192,7 +196,6 @@ public long NextExecution(Host lastQueried) private class OrderedLoadBalancingPolicy : ILoadBalancingPolicy { private readonly string[] _addresses; - private ICluster _cluster; private int _hostYielded; public int HostYielded @@ -205,19 +208,19 @@ public OrderedLoadBalancingPolicy(params string[] addresses) _addresses = addresses; } - public void Initialize(ICluster cluster) + public Task InitializeAsync(IMetadataSnapshotProvider metadata) { - _cluster = cluster; + return TaskHelper.Completed; } - public HostDistance Distance(Host host) + public HostDistance Distance(IMetadataSnapshotProvider metadata, Host host) { return HostDistance.Local; } - public IEnumerable NewQueryPlan(string keyspace, IStatement query) + public IEnumerable NewQueryPlan(ICluster cluster, string keyspace, IStatement query) { - var hosts = _cluster.AllHosts().ToArray(); + var hosts = cluster.Metadata.AllHostsSnapshot().ToArray(); foreach (var addr in _addresses) { var host = hosts.Single(h => h.Address.Address.ToString() == addr); diff --git a/src/Cassandra.IntegrationTests/Core/TypeSerializersTests.cs b/src/Cassandra.IntegrationTests/Core/TypeSerializersTests.cs index 1ed1ec862..d35104945 100644 --- a/src/Cassandra.IntegrationTests/Core/TypeSerializersTests.cs +++ b/src/Cassandra.IntegrationTests/Core/TypeSerializersTests.cs @@ -189,7 +189,10 @@ public void Should_Use_Primitive_TypeSerializers_For_Partition_Key() { var statement = ps.Bind(v); Assert.NotNull(statement.RoutingKey); - CollectionAssert.AreEqual(new BigDecimalSerializer().Serialize((ushort)session.BinaryProtocolVersion, v), statement.RoutingKey.RawRoutingKey); + CollectionAssert.AreEqual( + new BigDecimalSerializer().Serialize( + (ushort)session.Cluster.Metadata.GetClusterDescription().ProtocolVersion, v), + statement.RoutingKey.RawRoutingKey); session.Execute(statement); var row = session.Execute(new SimpleStatement("SELECT * FROM tbl_decimal_key WHERE id = ?", v)).First(); Assert.AreEqual(row.GetValue("id"), v); diff --git a/src/Cassandra.IntegrationTests/Core/UdfTests.cs b/src/Cassandra.IntegrationTests/Core/UdfTests.cs index 75e353a48..87ea82183 100644 --- a/src/Cassandra.IntegrationTests/Core/UdfTests.cs +++ b/src/Cassandra.IntegrationTests/Core/UdfTests.cs @@ -21,6 +21,7 @@ using System.Threading.Tasks; using Cassandra.IntegrationTests.TestBase; using Cassandra.IntegrationTests.TestClusterManagement; +using Cassandra.SessionManagement; using Cassandra.Tests; using NUnit.Framework; @@ -32,7 +33,7 @@ public class UdfTests : TestGlobals private ITestCluster _testCluster; private readonly List _clusters = new List(); - private ICluster GetCluster(bool metadataSync) + private IInternalCluster GetCluster(bool metadataSync) { var cluster = ClusterBuilder() .AddContactPoint(_testCluster.InitialContactPoint) @@ -202,9 +203,9 @@ public void GetFunction_Should_Return_Most_Up_To_Date_Metadata_Via_Events(bool m var cluster2 = GetCluster(metadataSync); var session2 = cluster.Connect("ks_udf"); session.Execute("CREATE OR REPLACE FUNCTION stringify(i int) RETURNS NULL ON NULL INPUT RETURNS text LANGUAGE java AS 'return Integer.toString(i);'"); - cluster2.RefreshSchema("ks_udf"); + cluster2.Metadata.RefreshSchema("ks_udf"); Task.Delay(500).GetAwaiter().GetResult(); // wait for events to be processed - var _ = cluster2.Metadata.KeyspacesSnapshot // cache + var _ = cluster2.InternalMetadata.KeyspacesSnapshot // cache .Single(kvp => kvp.Key == "ks_udf") .Value .GetFunction("stringify", new[] { "int" }); @@ -226,7 +227,7 @@ public void GetFunction_Should_Return_Most_Up_To_Date_Metadata_Via_Events(bool m else { Task.Delay(2000).GetAwaiter().GetResult(); - func = cluster2.Metadata.KeyspacesSnapshot + func = cluster2.InternalMetadata.KeyspacesSnapshot .Single(kvp => kvp.Key == "ks_udf") .Value .GetFunction("stringify", new[] { "int" }); @@ -291,9 +292,9 @@ public void GetAggregate_Should_Return_Most_Up_To_Date_Metadata_Via_Events(bool var cluster2 = GetCluster(metadataSync); var session2 = cluster2.Connect("ks_udf"); session.Execute("CREATE OR REPLACE AGGREGATE ks_udf.sum2(int) SFUNC plus STYPE int INITCOND 0"); - cluster2.RefreshSchema("ks_udf"); + cluster2.Metadata.RefreshSchema("ks_udf"); Task.Delay(500).GetAwaiter().GetResult(); // wait for events to be processed - var _ = cluster2.Metadata.KeyspacesSnapshot // cache + var _ = cluster2.InternalMetadata.KeyspacesSnapshot // cache .Single(kvp => kvp.Key == "ks_udf") .Value .GetAggregate("sum2", new[] { "int" }); @@ -315,7 +316,7 @@ public void GetAggregate_Should_Return_Most_Up_To_Date_Metadata_Via_Events(bool else { Task.Delay(2000).GetAwaiter().GetResult(); - aggregate = cluster2.Metadata.KeyspacesSnapshot + aggregate = cluster2.InternalMetadata.KeyspacesSnapshot .Single(kvp => kvp.Key == "ks_udf") .Value .GetAggregate("sum2", new[] { "int" }); diff --git a/src/Cassandra.IntegrationTests/DataStax/Cloud/CloudIntegrationTests.cs b/src/Cassandra.IntegrationTests/DataStax/Cloud/CloudIntegrationTests.cs index 68216d541..bb5250b62 100644 --- a/src/Cassandra.IntegrationTests/DataStax/Cloud/CloudIntegrationTests.cs +++ b/src/Cassandra.IntegrationTests/DataStax/Cloud/CloudIntegrationTests.cs @@ -59,7 +59,7 @@ public async Task Should_ContinueQuerying_When_ANodeGoesDown() .WithReconnectionPolicy(new ConstantReconnectionPolicy(40)) .WithQueryOptions(new QueryOptions().SetDefaultIdempotence(true))).ConfigureAwait(false); - Assert.IsTrue(session.Cluster.AllHosts().All(h => h.IsUp)); + Assert.IsTrue((await session.Cluster.Metadata.AllHostsAsync().ConfigureAwait(false)).All(h => h.IsUp)); var restarted = true; var t = Task.Run(async () => { @@ -72,10 +72,10 @@ public async Task Should_ContinueQuerying_When_ANodeGoesDown() TestHelper.RetryAssert( () => { - var dict = Session.Cluster.Metadata.TokenToReplicasMap.GetByKeyspace("system_distributed"); + var dict = session.InternalCluster.InternalMetadata.TokenToReplicasMap.GetByKeyspace("system_distributed"); Assert.AreEqual(3, dict.First().Value.Count); - Assert.AreEqual(3, Session.Cluster.AllHosts().Count); - Assert.IsTrue(Session.Cluster.AllHosts().All(h => h.IsUp)); + Assert.AreEqual(3, session.Cluster.Metadata.AllHosts().Count); + Assert.IsTrue(session.Cluster.Metadata.AllHosts().All(h => h.IsUp)); }, 20, 500); @@ -168,7 +168,7 @@ public async Task Should_MatchSystemLocalInformationOfEachNode() { var rs = await session.ExecuteAsync(new SimpleStatement("SELECT * FROM system.local")).ConfigureAwait(false); var row = rs.First(); - var host = session.Cluster.GetHost(new IPEndPoint(rs.Info.QueriedHost.Address, rs.Info.QueriedHost.Port)); + var host = await session.Cluster.Metadata.GetHostAsync(new IPEndPoint(rs.Info.QueriedHost.Address, rs.Info.QueriedHost.Port)).ConfigureAwait(false); Assert.IsNotNull(host); queriedHosts.Add(rs.Info.QueriedHost.Address); Assert.AreEqual(host.HostId, row.GetValue("host_id")); diff --git a/src/Cassandra.IntegrationTests/Linq/LinqMethods/Delete.cs b/src/Cassandra.IntegrationTests/Linq/LinqMethods/Delete.cs index 6e08b00eb..5b76451f7 100644 --- a/src/Cassandra.IntegrationTests/Linq/LinqMethods/Delete.cs +++ b/src/Cassandra.IntegrationTests/Linq/LinqMethods/Delete.cs @@ -132,7 +132,7 @@ public void Delete_NoSuchRecord() Assert.AreEqual(1, listOfExecutionParameters.Count); var parameter = Convert.FromBase64String((string)listOfExecutionParameters.Single().Single()); - var actualParameter = (string) Session.Cluster.Metadata.ControlConnection.Serializer.GetCurrentSerializer().Deserialize(parameter, 0, parameter.Length, ColumnTypeCode.Text, null); + var actualParameter = (string) Session.Cluster.Configuration.SerializerManager.GetCurrentSerializer().Deserialize(parameter, 0, parameter.Length, ColumnTypeCode.Text, null); Assert.AreNotEqual(entityToDelete.StringType, actualParameter); Assert.IsTrue(actualParameter.StartsWith(entityToDelete.StringType)); Assert.IsTrue(actualParameter.Length > entityToDelete.StringType.Length); diff --git a/src/Cassandra.IntegrationTests/Linq/LinqTable/CreateTable.cs b/src/Cassandra.IntegrationTests/Linq/LinqTable/CreateTable.cs index 7307f4f5b..16066b198 100644 --- a/src/Cassandra.IntegrationTests/Linq/LinqTable/CreateTable.cs +++ b/src/Cassandra.IntegrationTests/Linq/LinqTable/CreateTable.cs @@ -546,7 +546,7 @@ public void CreateTable_With_Frozen_Tuple() new StubTableColumn("t", StubColumnKind.Regular, DataType.Frozen(DataType.Tuple(DataType.BigInt, DataType.BigInt, DataType.Text))) }); - SessionCluster.RefreshSchema(_uniqueKsName, "tbl_frozen_tuple"); + SessionCluster.Metadata.RefreshSchema(_uniqueKsName, "tbl_frozen_tuple"); var tableMeta = SessionCluster.Metadata.GetTable(_uniqueKsName, "tbl_frozen_tuple"); Assert.NotNull(tableMeta); @@ -590,7 +590,7 @@ public void CreateTable_With_Counter_Static() new StubTableColumn("id2", StubColumnKind.ClusteringKey, DataType.Text) }); - SessionCluster.RefreshSchema(_uniqueKsName, "tbl_with_counter_static"); + SessionCluster.Metadata.RefreshSchema(_uniqueKsName, "tbl_with_counter_static"); var tableMeta = SessionCluster.Metadata.GetTable(_uniqueKsName, "tbl_with_counter_static"); Assert.AreEqual(4, tableMeta.TableColumns.Length); diff --git a/src/Cassandra.IntegrationTests/MetadataTests/TokenMapSchemaChangeMetadataSyncTests.cs b/src/Cassandra.IntegrationTests/MetadataTests/TokenMapSchemaChangeMetadataSyncTests.cs index 8b9121dcf..33c573750 100644 --- a/src/Cassandra.IntegrationTests/MetadataTests/TokenMapSchemaChangeMetadataSyncTests.cs +++ b/src/Cassandra.IntegrationTests/MetadataTests/TokenMapSchemaChangeMetadataSyncTests.cs @@ -65,19 +65,20 @@ public void TokenMap_Should_NotUpdateExistingTokenMap_When_KeyspaceIsCreated() .Build()) { var newSession = newCluster.Connect(); - var oldTokenMap = newCluster.Metadata.TokenToReplicasMap; - Assert.AreEqual(3, newCluster.Metadata.Hosts.Count); + newSession.Connect(); + var oldTokenMap = newCluster.InternalRef.InternalMetadata.TokenToReplicasMap; + Assert.AreEqual(3, newCluster.InternalRef.InternalMetadata.Hosts.Count); - Assert.IsNull(newCluster.Metadata.TokenToReplicasMap.GetByKeyspace(keyspaceName)); + Assert.IsNull(newCluster.InternalRef.InternalMetadata.TokenToReplicasMap.GetByKeyspace(keyspaceName)); var createKeyspaceCql = $"CREATE KEYSPACE {keyspaceName} WITH replication = {{'class': 'SimpleStrategy', 'replication_factor' : 3}}"; newSession.Execute(createKeyspaceCql); TestUtils.WaitForSchemaAgreement(newCluster); newSession.ChangeKeyspace(keyspaceName); - Assert.IsNull(newCluster.Metadata.TokenToReplicasMap.GetByKeyspace(keyspaceName)); - Assert.AreEqual(1, newCluster.Metadata.GetReplicas(keyspaceName, Encoding.UTF8.GetBytes("123")).Count); - Assert.IsTrue(object.ReferenceEquals(newCluster.Metadata.TokenToReplicasMap, oldTokenMap)); + Assert.IsNull(newCluster.InternalRef.InternalMetadata.TokenToReplicasMap.GetByKeyspace(keyspaceName)); + Assert.AreEqual(1, newCluster.InternalRef.InternalMetadata.GetReplicas(keyspaceName, Encoding.UTF8.GetBytes("123")).Count); + Assert.IsTrue(object.ReferenceEquals(newCluster.InternalRef.InternalMetadata.TokenToReplicasMap, oldTokenMap)); } } @@ -99,10 +100,10 @@ public void TokenMap_Should_NotUpdateExistingTokenMap_When_KeyspaceIsRemoved() var removeKeyspaceCql = $"DROP KEYSPACE {keyspaceName}"; newSession.Execute(removeKeyspaceCql); TestUtils.WaitForSchemaAgreement(newCluster); - var oldTokenMap = newCluster.Metadata.TokenToReplicasMap; - Assert.IsNull(newCluster.Metadata.TokenToReplicasMap.GetByKeyspace(keyspaceName)); + var oldTokenMap = newCluster.InternalRef.InternalMetadata.TokenToReplicasMap; + Assert.IsNull(newCluster.InternalRef.InternalMetadata.TokenToReplicasMap.GetByKeyspace(keyspaceName)); Assert.AreEqual(1, newCluster.Metadata.GetReplicas(keyspaceName, Encoding.UTF8.GetBytes("123")).Count); - Assert.IsTrue(object.ReferenceEquals(newCluster.Metadata.TokenToReplicasMap, oldTokenMap)); + Assert.IsTrue(object.ReferenceEquals(newCluster.InternalRef.InternalMetadata.TokenToReplicasMap, oldTokenMap)); } } @@ -121,27 +122,28 @@ public void TokenMap_Should_NotUpdateExistingTokenMap_When_KeyspaceIsChanged() .Build()) { var newSession = newCluster.Connect(keyspaceName); + newSession.Connect(); TestHelper.RetryAssert(() => { - var replicas = newCluster.Metadata.TokenToReplicasMap.GetByKeyspace(keyspaceName); + var replicas = newCluster.InternalRef.InternalMetadata.TokenToReplicasMap.GetByKeyspace(keyspaceName); Assert.IsNull(replicas); Assert.AreEqual(1, newCluster.Metadata.GetReplicas(keyspaceName, Encoding.UTF8.GetBytes("123")).Count); }); - Assert.AreEqual(3, newCluster.Metadata.Hosts.Count(h => h.IsUp)); - var oldTokenMap = newCluster.Metadata.TokenToReplicasMap; + Assert.AreEqual(3, newCluster.InternalRef.InternalMetadata.Hosts.Count(h => h.IsUp)); + var oldTokenMap = newCluster.InternalRef.InternalMetadata.TokenToReplicasMap; var alterKeyspaceCql = $"ALTER KEYSPACE {keyspaceName} WITH replication = {{'class': 'SimpleStrategy', 'replication_factor' : 2}}"; newSession.Execute(alterKeyspaceCql); TestUtils.WaitForSchemaAgreement(newCluster); TestHelper.RetryAssert(() => { - var replicas = newCluster.Metadata.TokenToReplicasMap.GetByKeyspace(keyspaceName); + var replicas = newCluster.InternalRef.InternalMetadata.TokenToReplicasMap.GetByKeyspace(keyspaceName); Assert.IsNull(replicas); Assert.AreEqual(1, newCluster.Metadata.GetReplicas(keyspaceName, Encoding.UTF8.GetBytes("123")).Count); }); - Assert.AreEqual(3, newCluster.Metadata.Hosts.Count(h => h.IsUp)); - Assert.IsTrue(object.ReferenceEquals(newCluster.Metadata.TokenToReplicasMap, oldTokenMap)); + Assert.AreEqual(3, newCluster.InternalRef.InternalMetadata.Hosts.Count(h => h.IsUp)); + Assert.IsTrue(object.ReferenceEquals(newCluster.InternalRef.InternalMetadata.TokenToReplicasMap, oldTokenMap)); } } @@ -163,24 +165,25 @@ public async Task TokenMap_Should_RefreshTokenMapForSingleKeyspace_When_RefreshS .WithQueryTimeout(60000) .Build()) { - var newSession = newCluster.Connect(keyspaceName); - var oldTokenMap = newCluster.Metadata.TokenToReplicasMap; - var replicas = newCluster.Metadata.TokenToReplicasMap.GetByKeyspace(keyspaceName); + var newSession = await newCluster.ConnectAsync(keyspaceName).ConfigureAwait(false); + await newSession.ConnectAsync().ConfigureAwait(false); + var oldTokenMap = newCluster.InternalRef.InternalMetadata.TokenToReplicasMap; + var replicas = newCluster.InternalRef.InternalMetadata.TokenToReplicasMap.GetByKeyspace(keyspaceName); Assert.IsNull(replicas); Assert.AreEqual(1, newCluster.Metadata.GetReplicas(keyspaceName, Encoding.UTF8.GetBytes("123")).Count); - Assert.AreEqual(3, newCluster.Metadata.Hosts.Count(h => h.IsUp)); + Assert.AreEqual(3, newCluster.InternalRef.InternalMetadata.Hosts.Count(h => h.IsUp)); Assert.AreEqual(1, newCluster.Metadata.GetReplicas(keyspaceName, Encoding.UTF8.GetBytes("123")).Count); - await newCluster.RefreshSchemaAsync(keyspaceName).ConfigureAwait(false); + await newCluster.Metadata.RefreshSchemaAsync(keyspaceName).ConfigureAwait(false); - Assert.AreEqual(1, newCluster.Metadata.KeyspacesSnapshot.Length); + Assert.AreEqual(1, newCluster.InternalRef.InternalMetadata.KeyspacesSnapshot.Length); - replicas = newCluster.Metadata.TokenToReplicasMap.GetByKeyspace(keyspaceName); - Assert.AreEqual(newCluster.Metadata.Hosts.Sum(h => h.Tokens.Count()), replicas.Count); + replicas = newCluster.InternalRef.InternalMetadata.TokenToReplicasMap.GetByKeyspace(keyspaceName); + Assert.AreEqual(newCluster.InternalRef.InternalMetadata.Hosts.Sum(h => h.Tokens.Count()), replicas.Count); Assert.AreEqual(3, newCluster.Metadata.GetReplicas(keyspaceName, Encoding.UTF8.GetBytes("123")).Count); - Assert.AreEqual(3, newCluster.Metadata.Hosts.Count(h => h.IsUp)); - Assert.IsTrue(object.ReferenceEquals(newCluster.Metadata.TokenToReplicasMap, oldTokenMap)); + Assert.AreEqual(3, newCluster.InternalRef.InternalMetadata.Hosts.Count(h => h.IsUp)); + Assert.IsTrue(object.ReferenceEquals(newCluster.InternalRef.InternalMetadata.TokenToReplicasMap, oldTokenMap)); } } @@ -202,28 +205,29 @@ public async Task TokenMap_Should_RefreshTokenMapForAllKeyspaces_When_RefreshSch .WithQueryTimeout(60000) .Build()) { - var newSession = newCluster.Connect(keyspaceName); - var replicas = newCluster.Metadata.TokenToReplicasMap.GetByKeyspace(keyspaceName); + var newSession = await newCluster.ConnectAsync(keyspaceName).ConfigureAwait(false); + await newSession.ConnectAsync().ConfigureAwait(false); + var replicas = newCluster.InternalRef.InternalMetadata.TokenToReplicasMap.GetByKeyspace(keyspaceName); Assert.IsNull(replicas); Assert.AreEqual(1, newCluster.Metadata.GetReplicas(keyspaceName, Encoding.UTF8.GetBytes("123")).Count); - var oldTokenMap = newCluster.Metadata.TokenToReplicasMap; - Assert.AreEqual(3, newCluster.Metadata.Hosts.Count(h => h.IsUp)); + var oldTokenMap = newCluster.InternalRef.InternalMetadata.TokenToReplicasMap; + Assert.AreEqual(3, newCluster.InternalRef.InternalMetadata.Hosts.Count(h => h.IsUp)); Assert.AreEqual(1, newCluster.Metadata.GetReplicas(keyspaceName, Encoding.UTF8.GetBytes("123")).Count); - await newCluster.RefreshSchemaAsync().ConfigureAwait(false); + await newCluster.Metadata.RefreshSchemaAsync().ConfigureAwait(false); - Assert.GreaterOrEqual(newCluster.Metadata.KeyspacesSnapshot.Length, 2); + Assert.GreaterOrEqual(newCluster.InternalRef.InternalMetadata.KeyspacesSnapshot.Length, 2); - replicas = newCluster.Metadata.TokenToReplicasMap.GetByKeyspace(keyspaceName); - Assert.AreEqual(newCluster.Metadata.Hosts.Sum(h => h.Tokens.Count()), replicas.Count); + replicas = newCluster.InternalRef.InternalMetadata.TokenToReplicasMap.GetByKeyspace(keyspaceName); + Assert.AreEqual(newCluster.InternalRef.InternalMetadata.Hosts.Sum(h => h.Tokens.Count()), replicas.Count); Assert.AreEqual(3, newCluster.Metadata.GetReplicas(keyspaceName, Encoding.UTF8.GetBytes("123")).Count); - replicas = newCluster.Metadata.TokenToReplicasMap.GetByKeyspace(keyspaceName1); - Assert.AreEqual(newCluster.Metadata.Hosts.Sum(h => h.Tokens.Count()), replicas.Count); + replicas = newCluster.InternalRef.InternalMetadata.TokenToReplicasMap.GetByKeyspace(keyspaceName1); + Assert.AreEqual(newCluster.InternalRef.InternalMetadata.Hosts.Sum(h => h.Tokens.Count()), replicas.Count); Assert.AreEqual(3, newCluster.Metadata.GetReplicas(keyspaceName, Encoding.UTF8.GetBytes("123")).Count); - Assert.AreEqual(3, newCluster.Metadata.Hosts.Count(h => h.IsUp)); - Assert.IsFalse(object.ReferenceEquals(newCluster.Metadata.TokenToReplicasMap, oldTokenMap)); + Assert.AreEqual(3, newCluster.InternalRef.InternalMetadata.Hosts.Count(h => h.IsUp)); + Assert.IsFalse(object.ReferenceEquals(newCluster.InternalRef.InternalMetadata.TokenToReplicasMap, oldTokenMap)); } } } diff --git a/src/Cassandra.IntegrationTests/MetadataTests/TokenMapSchemaChangeTests.cs b/src/Cassandra.IntegrationTests/MetadataTests/TokenMapSchemaChangeTests.cs index c390e2587..7f8897359 100644 --- a/src/Cassandra.IntegrationTests/MetadataTests/TokenMapSchemaChangeTests.cs +++ b/src/Cassandra.IntegrationTests/MetadataTests/TokenMapSchemaChangeTests.cs @@ -37,11 +37,12 @@ public void TokenMap_Should_UpdateExistingTokenMap_When_KeyspaceIsCreated() TestUtils.WaitForSchemaAgreement(Cluster); var keyspaceName = TestUtils.GetUniqueKeyspaceName().ToLower(); var newSession = GetNewTemporarySession(); - var newCluster = newSession.Cluster; - var oldTokenMap = newCluster.Metadata.TokenToReplicasMap; - Assert.AreEqual(3, newCluster.Metadata.Hosts.Count); + newSession.Connect(); + var newCluster = newSession.InternalCluster; + var oldTokenMap = newCluster.InternalMetadata.TokenToReplicasMap; + Assert.AreEqual(3, newCluster.InternalMetadata.Hosts.Count); - Assert.IsNull(newCluster.Metadata.TokenToReplicasMap.GetByKeyspace(keyspaceName)); + Assert.IsNull(newCluster.InternalMetadata.TokenToReplicasMap.GetByKeyspace(keyspaceName)); var createKeyspaceCql = $"CREATE KEYSPACE {keyspaceName} WITH replication = {{'class': 'SimpleStrategy', 'replication_factor' : 3}}"; newSession.Execute(createKeyspaceCql); @@ -53,11 +54,11 @@ public void TokenMap_Should_UpdateExistingTokenMap_When_KeyspaceIsCreated() IReadOnlyDictionary> replicas = null; Assert.DoesNotThrow(() => { - replicas = newCluster.Metadata.TokenToReplicasMap.GetByKeyspace(keyspaceName); + replicas = newCluster.InternalMetadata.TokenToReplicasMap.GetByKeyspace(keyspaceName); }); - Assert.AreEqual(newCluster.Metadata.Hosts.Sum(h => h.Tokens.Count()), replicas.Count); + Assert.AreEqual(newCluster.InternalMetadata.Hosts.Sum(h => h.Tokens.Count()), replicas.Count); }); - Assert.IsTrue(object.ReferenceEquals(newCluster.Metadata.TokenToReplicasMap, oldTokenMap)); + Assert.IsTrue(object.ReferenceEquals(newCluster.InternalMetadata.TokenToReplicasMap, oldTokenMap)); } [Test] @@ -69,16 +70,16 @@ public void TokenMap_Should_UpdateExistingTokenMap_When_KeyspaceIsRemoved() TestUtils.WaitForSchemaAgreement(Cluster); var newSession = GetNewTemporarySession(); - var newCluster = newSession.Cluster; + var newCluster = newSession.InternalCluster; var removeKeyspaceCql = $"DROP KEYSPACE {keyspaceName}"; newSession.Execute(removeKeyspaceCql); TestUtils.WaitForSchemaAgreement(newCluster); - var oldTokenMap = newCluster.Metadata.TokenToReplicasMap; + var oldTokenMap = newCluster.InternalMetadata.TokenToReplicasMap; TestHelper.RetryAssert(() => { - Assert.IsNull(newCluster.Metadata.TokenToReplicasMap.GetByKeyspace(keyspaceName)); + Assert.IsNull(newCluster.InternalMetadata.TokenToReplicasMap.GetByKeyspace(keyspaceName)); }); - Assert.IsTrue(object.ReferenceEquals(newCluster.Metadata.TokenToReplicasMap, oldTokenMap)); + Assert.IsTrue(object.ReferenceEquals(newCluster.InternalMetadata.TokenToReplicasMap, oldTokenMap)); } [Test] @@ -90,28 +91,28 @@ public void TokenMap_Should_UpdateExistingTokenMap_When_KeyspaceIsChanged() TestUtils.WaitForSchemaAgreement(Cluster); var newSession = GetNewTemporarySession(keyspaceName); - var newCluster = newSession.Cluster; + var newCluster = newSession.InternalCluster; TestHelper.RetryAssert(() => { - var replicas = newCluster.Metadata.TokenToReplicasMap.GetByKeyspace(keyspaceName); - Assert.AreEqual(newCluster.Metadata.Hosts.Sum(h => h.Tokens.Count()), replicas.Count); + var replicas = newCluster.InternalMetadata.TokenToReplicasMap.GetByKeyspace(keyspaceName); + Assert.AreEqual(newCluster.InternalMetadata.Hosts.Sum(h => h.Tokens.Count()), replicas.Count); Assert.AreEqual(3, newCluster.Metadata.GetReplicas(keyspaceName, Encoding.UTF8.GetBytes("123")).Count); }); - Assert.AreEqual(3, newCluster.Metadata.Hosts.Count(h => h.IsUp)); - var oldTokenMap = newCluster.Metadata.TokenToReplicasMap; + Assert.AreEqual(3, newCluster.InternalMetadata.Hosts.Count(h => h.IsUp)); + var oldTokenMap = newCluster.InternalMetadata.TokenToReplicasMap; var alterKeyspaceCql = $"ALTER KEYSPACE {keyspaceName} WITH replication = {{'class': 'SimpleStrategy', 'replication_factor' : 2}}"; newSession.Execute(alterKeyspaceCql); TestUtils.WaitForSchemaAgreement(newCluster); TestHelper.RetryAssert(() => { - var replicas = newCluster.Metadata.TokenToReplicasMap.GetByKeyspace(keyspaceName); - Assert.AreEqual(newCluster.Metadata.Hosts.Sum(h => h.Tokens.Count()), replicas.Count); + var replicas = newCluster.InternalMetadata.TokenToReplicasMap.GetByKeyspace(keyspaceName); + Assert.AreEqual(newCluster.InternalMetadata.Hosts.Sum(h => h.Tokens.Count()), replicas.Count); Assert.AreEqual(2, newCluster.Metadata.GetReplicas(keyspaceName, Encoding.UTF8.GetBytes("123")).Count); }); - Assert.AreEqual(3, newCluster.Metadata.Hosts.Count(h => h.IsUp)); - Assert.IsTrue(object.ReferenceEquals(newCluster.Metadata.TokenToReplicasMap, oldTokenMap)); + Assert.AreEqual(3, newCluster.InternalMetadata.Hosts.Count(h => h.IsUp)); + Assert.IsTrue(object.ReferenceEquals(newCluster.InternalMetadata.TokenToReplicasMap, oldTokenMap)); } } } \ No newline at end of file diff --git a/src/Cassandra.IntegrationTests/MetadataTests/TokenMapTopologyChangeTests.cs b/src/Cassandra.IntegrationTests/MetadataTests/TokenMapTopologyChangeTests.cs index 92760652d..5dc08b6e6 100644 --- a/src/Cassandra.IntegrationTests/MetadataTests/TokenMapTopologyChangeTests.cs +++ b/src/Cassandra.IntegrationTests/MetadataTests/TokenMapTopologyChangeTests.cs @@ -21,6 +21,7 @@ using Cassandra.IntegrationTests.TestBase; using Cassandra.IntegrationTests.TestClusterManagement; +using Cassandra.SessionManagement; using Cassandra.Tests; using NUnit.Framework; @@ -31,8 +32,8 @@ namespace Cassandra.IntegrationTests.MetadataTests public class TokenMapTopologyChangeTests : TestGlobals { private ITestCluster TestCluster { get; set; } - private ICluster ClusterObjSync { get; set; } - private ICluster ClusterObjNotSync { get; set; } + private IInternalCluster ClusterObjSync { get; set; } + private IInternalCluster ClusterObjNotSync { get; set; } [Test] public void TokenMap_Should_RebuildTokenMap_When_NodeIsDecommissioned() @@ -74,8 +75,8 @@ public void TokenMap_Should_RebuildTokenMap_When_NodeIsDecommissioned() TestHelper.RetryAssert(() => { - Assert.AreEqual(3, ClusterObjSync.Metadata.Hosts.Count); - Assert.AreEqual(3, ClusterObjNotSync.Metadata.Hosts.Count); + Assert.AreEqual(3, ClusterObjSync.InternalMetadata.Hosts.Count); + Assert.AreEqual(3, ClusterObjNotSync.InternalMetadata.Hosts.Count); replicasSync = ClusterObjSync.Metadata.GetReplicas(keyspaceName, Encoding.UTF8.GetBytes("123")); replicasNotSync = ClusterObjNotSync.Metadata.GetReplicas(keyspaceName, Encoding.UTF8.GetBytes("123")); @@ -84,8 +85,8 @@ public void TokenMap_Should_RebuildTokenMap_When_NodeIsDecommissioned() Assert.AreEqual(1, replicasNotSync.Count); }, 100, 150); - var oldTokenMapNotSync = ClusterObjNotSync.Metadata.TokenToReplicasMap; - var oldTokenMapSync = ClusterObjSync.Metadata.TokenToReplicasMap; + var oldTokenMapNotSync = ClusterObjNotSync.InternalMetadata.TokenToReplicasMap; + var oldTokenMapSync = ClusterObjSync.InternalMetadata.TokenToReplicasMap; if (TestClusterManager.SupportsDecommissionForcefully()) { @@ -100,8 +101,8 @@ public void TokenMap_Should_RebuildTokenMap_When_NodeIsDecommissioned() TestHelper.RetryAssert(() => { - Assert.AreEqual(2, ClusterObjSync.Metadata.Hosts.Count, "ClusterObjSync.Metadata.Hosts.Count"); - Assert.AreEqual(2, ClusterObjNotSync.Metadata.Hosts.Count, "ClusterObjNotSync.Metadata.Hosts.Count"); + Assert.AreEqual(2, ClusterObjSync.InternalMetadata.Hosts.Count, "ClusterObjSync.Metadata.Hosts.Count"); + Assert.AreEqual(2, ClusterObjNotSync.InternalMetadata.Hosts.Count, "ClusterObjNotSync.Metadata.Hosts.Count"); replicasSync = ClusterObjSync.Metadata.GetReplicas(keyspaceName, Encoding.UTF8.GetBytes("123")); replicasNotSync = ClusterObjNotSync.Metadata.GetReplicas(keyspaceName, Encoding.UTF8.GetBytes("123")); @@ -109,18 +110,18 @@ public void TokenMap_Should_RebuildTokenMap_When_NodeIsDecommissioned() Assert.AreEqual(2, replicasSync.Count, "replicasSync.Count"); Assert.AreEqual(1, replicasNotSync.Count, "replicasNotSync.Count"); - Assert.IsFalse(object.ReferenceEquals(ClusterObjNotSync.Metadata.TokenToReplicasMap, oldTokenMapNotSync)); - Assert.IsFalse(object.ReferenceEquals(ClusterObjSync.Metadata.TokenToReplicasMap, oldTokenMapSync)); + Assert.IsFalse(object.ReferenceEquals(ClusterObjNotSync.InternalMetadata.TokenToReplicasMap, oldTokenMapNotSync)); + Assert.IsFalse(object.ReferenceEquals(ClusterObjSync.InternalMetadata.TokenToReplicasMap, oldTokenMapSync)); }, 1000, 360); - oldTokenMapNotSync = ClusterObjNotSync.Metadata.TokenToReplicasMap; - oldTokenMapSync = ClusterObjSync.Metadata.TokenToReplicasMap; + oldTokenMapNotSync = ClusterObjNotSync.InternalMetadata.TokenToReplicasMap; + oldTokenMapSync = ClusterObjSync.InternalMetadata.TokenToReplicasMap; this.TestCluster.BootstrapNode(4); TestHelper.RetryAssert(() => { - Assert.AreEqual(3, ClusterObjSync.Metadata.Hosts.Count); - Assert.AreEqual(3, ClusterObjNotSync.Metadata.Hosts.Count); + Assert.AreEqual(3, ClusterObjSync.InternalMetadata.Hosts.Count); + Assert.AreEqual(3, ClusterObjNotSync.InternalMetadata.Hosts.Count); replicasSync = ClusterObjSync.Metadata.GetReplicas(keyspaceName, Encoding.UTF8.GetBytes("123")); replicasNotSync = ClusterObjNotSync.Metadata.GetReplicas(keyspaceName, Encoding.UTF8.GetBytes("123")); @@ -128,8 +129,8 @@ public void TokenMap_Should_RebuildTokenMap_When_NodeIsDecommissioned() Assert.AreEqual(3, replicasSync.Count); Assert.AreEqual(1, replicasNotSync.Count); - Assert.IsFalse(object.ReferenceEquals(ClusterObjNotSync.Metadata.TokenToReplicasMap, oldTokenMapNotSync)); - Assert.IsFalse(object.ReferenceEquals(ClusterObjSync.Metadata.TokenToReplicasMap, oldTokenMapSync)); + Assert.IsFalse(object.ReferenceEquals(ClusterObjNotSync.InternalMetadata.TokenToReplicasMap, oldTokenMapNotSync)); + Assert.IsFalse(object.ReferenceEquals(ClusterObjSync.InternalMetadata.TokenToReplicasMap, oldTokenMapSync)); }, 1000, 360); } diff --git a/src/Cassandra.IntegrationTests/Metrics/MetricsTests.cs b/src/Cassandra.IntegrationTests/Metrics/MetricsTests.cs index db43baa4f..2fba6498b 100644 --- a/src/Cassandra.IntegrationTests/Metrics/MetricsTests.cs +++ b/src/Cassandra.IntegrationTests/Metrics/MetricsTests.cs @@ -98,13 +98,14 @@ public void Should_RemoveNodeMetricsAndDisposeMetricsContext_When_HostIsRemoved( _metricsRoot = new MetricsBuilder().Build(); var cluster = GetNewTemporaryCluster(b => b.WithMetrics(_metricsRoot.CreateDriverMetricsProvider())); var session = cluster.Connect(); + session.Connect(); var metrics = session.GetMetrics(); - Assert.AreEqual(3, cluster.Metadata.Hosts.Count); + Assert.AreEqual(3, cluster.InternalMetadata.Hosts.Count); Assert.AreEqual(3, metrics.NodeMetrics.Count); // get address for host that will be removed from the cluster in this test var address = TestCluster.ClusterIpPrefix + "2"; - var hostToBeRemoved = cluster.Metadata.Hosts.First(h => h.Address.Address.Equals(IPAddress.Parse(address))); + var hostToBeRemoved = cluster.InternalMetadata.Hosts.First(h => h.Address.Address.Equals(IPAddress.Parse(address))); // check node metrics are valid var gauge = metrics.GetNodeGauge(hostToBeRemoved, NodeMetric.Gauges.OpenConnections); @@ -130,7 +131,7 @@ public void Should_RemoveNodeMetricsAndDisposeMetricsContext_When_HostIsRemoved( TestCluster.Stop(2); try { - TestHelper.RetryAssert(() => { Assert.AreEqual(2, cluster.Metadata.Hosts.Count, "metadata hosts count failed"); }, 200, 50); + TestHelper.RetryAssert(() => { Assert.AreEqual(2, cluster.InternalMetadata.Hosts.Count, "metadata hosts count failed"); }, 200, 50); TestHelper.RetryAssert(() => { Assert.AreEqual(2, metrics.NodeMetrics.Count, "Node metrics count failed"); }, 10, 500); } catch @@ -146,7 +147,7 @@ public void Should_RemoveNodeMetricsAndDisposeMetricsContext_When_HostIsRemoved( TestCluster.Start(2, "--jvm_arg=\"-Dcassandra.override_decommission=true\""); - TestHelper.RetryAssert(() => { Assert.AreEqual(3, cluster.Metadata.Hosts.Count, "metadata hosts count after bootstrap failed"); }, + TestHelper.RetryAssert(() => { Assert.AreEqual(3, cluster.InternalMetadata.Hosts.Count, "metadata hosts count after bootstrap failed"); }, 200, 50); // when new host is chosen by LBP, connection pool is created @@ -177,6 +178,7 @@ public void Should_AllMetricsHaveValidValues_When_AllNodesAreUp() .SetEnabledNodeMetrics(NodeMetric.AllNodeMetrics) .SetEnabledSessionMetrics(SessionMetric.AllSessionMetrics))); var session = cluster.Connect(); + session.Connect(); Assert.AreEqual(25, NodeMetric.AllNodeMetrics.Count()); Assert.AreEqual(25, MetricsTests.Counters.Concat(MetricsTests.Gauges.Concat(MetricsTests.Timers.Concat(MetricsTests.Meters))) @@ -192,7 +194,7 @@ public void Should_AllMetricsHaveValidValues_When_AllNodesAreUp() var waitedForRefresh = false; var metrics = session.GetMetrics(); - foreach (var h in cluster.AllHosts()) + foreach (var h in cluster.Metadata.AllHosts()) { foreach (var c in MetricsTests.Counters) { @@ -231,6 +233,7 @@ public void Should_DefaultMetricsHaveValidValuesAndTimersDisabled() _metricsRoot = new MetricsBuilder().Build(); var cluster = GetNewTemporaryCluster(b => b.WithMetrics(_metricsRoot.CreateDriverMetricsProvider())); var session = cluster.Connect(); + session.Connect(); Assert.AreEqual(24, NodeMetric.DefaultNodeMetrics.Count()); Assert.IsTrue(NodeMetric.DefaultNodeMetrics.SequenceEqual(NodeMetric.DefaultNodeMetrics.Distinct())); Assert.AreEqual(NodeMetric.Timers.CqlMessages, NodeMetric.AllNodeMetrics.Except(NodeMetric.DefaultNodeMetrics).Single()); @@ -248,7 +251,7 @@ public void Should_DefaultMetricsHaveValidValuesAndTimersDisabled() } var metrics = session.GetMetrics(); - foreach (var h in cluster.AllHosts()) + foreach (var h in cluster.Metadata.AllHosts()) { foreach (var c in MetricsTests.Counters) { @@ -293,8 +296,8 @@ public void Should_AllMetricsHaveValidValues_When_NodeIsDown() } var metrics = session.GetMetrics(); - var downNode = session.Cluster.AllHosts().Single(h => h.Address.Address.ToString() == (TestCluster.ClusterIpPrefix + "2")); - foreach (var h in cluster.AllHosts()) + var downNode = session.Cluster.Metadata.AllHosts().Single(h => h.Address.Address.ToString() == (TestCluster.ClusterIpPrefix + "2")); + foreach (var h in cluster.Metadata.AllHosts()) { foreach (var c in MetricsTests.Counters) { diff --git a/src/Cassandra.IntegrationTests/Policies/Tests/LoadBalancingPolicyShortTests.cs b/src/Cassandra.IntegrationTests/Policies/Tests/LoadBalancingPolicyShortTests.cs index fe8e8da30..b895a4042 100644 --- a/src/Cassandra.IntegrationTests/Policies/Tests/LoadBalancingPolicyShortTests.cs +++ b/src/Cassandra.IntegrationTests/Policies/Tests/LoadBalancingPolicyShortTests.cs @@ -321,7 +321,7 @@ public void TokenAware_VNodes_Test(bool metadataSync) try { var session = cluster.Connect(); - Assert.AreEqual(256, cluster.AllHosts().First().Tokens.Count()); + Assert.AreEqual(256, cluster.Metadata.AllHosts().First().Tokens.Count()); var ks = TestUtils.GetUniqueKeyspaceName(); session.Execute($"CREATE KEYSPACE \"{ks}\" WITH replication = {{'class': 'SimpleStrategy', 'replication_factor' : 1}}"); session.ChangeKeyspace(ks); @@ -380,7 +380,7 @@ public void Token_Aware_Uses_Keyspace_From_Statement_To_Determine_Replication(bo // Manually calculate the routing key var routingKey = SerializerManager.Default.GetCurrentSerializer().Serialize(id); // Get the replicas - var replicas = cluster.GetReplicas(ks, routingKey); + var replicas = cluster.Metadata.GetReplicas(ks, routingKey); Assert.AreEqual(metadataSync ? 2 : 1, replicas.Count); CollectionAssert.AreEquivalent(replicas.Select(h => h.Address), coordinators); } diff --git a/src/Cassandra.IntegrationTests/Policies/Tests/RetryPolicyShortTests.cs b/src/Cassandra.IntegrationTests/Policies/Tests/RetryPolicyShortTests.cs index cec3ec437..56482b747 100644 --- a/src/Cassandra.IntegrationTests/Policies/Tests/RetryPolicyShortTests.cs +++ b/src/Cassandra.IntegrationTests/Policies/Tests/RetryPolicyShortTests.cs @@ -23,6 +23,7 @@ using Cassandra.IntegrationTests.SimulacronAPI.PrimeBuilder.Then; using Cassandra.IntegrationTests.TestBase; using Cassandra.IntegrationTests.TestClusterManagement.Simulacron; +using Cassandra.Tasks; using Cassandra.Tests; using Newtonsoft.Json; @@ -266,7 +267,6 @@ public RetryDecision OnRequestError(IStatement statement, Configuration config, private class CustomLoadBalancingPolicy : ILoadBalancingPolicy { - private ICluster _cluster; private readonly string[] _hosts; public CustomLoadBalancingPolicy(string[] hosts) @@ -274,20 +274,20 @@ public CustomLoadBalancingPolicy(string[] hosts) _hosts = hosts; } - public void Initialize(ICluster cluster) + public Task InitializeAsync(IMetadataSnapshotProvider metadata) { - _cluster = cluster; + return TaskHelper.Completed; } - public HostDistance Distance(Host host) + public HostDistance Distance(IMetadataSnapshotProvider metadata, Host host) { return HostDistance.Local; } - public IEnumerable NewQueryPlan(string keyspace, IStatement query) + public IEnumerable NewQueryPlan(ICluster cluster, string keyspace, IStatement query) { var queryPlan = new List(); - var allHosts = _cluster.AllHosts(); + var allHosts = cluster.Metadata.AllHostsSnapshot(); foreach (var host in _hosts) { queryPlan.Add(allHosts.Single(h => h.Address.ToString() == host)); diff --git a/src/Cassandra.IntegrationTests/Policies/Tests/TokenAwareTransientReplicationTests.cs b/src/Cassandra.IntegrationTests/Policies/Tests/TokenAwareTransientReplicationTests.cs index f613d6071..a623992a8 100644 --- a/src/Cassandra.IntegrationTests/Policies/Tests/TokenAwareTransientReplicationTests.cs +++ b/src/Cassandra.IntegrationTests/Policies/Tests/TokenAwareTransientReplicationTests.cs @@ -46,7 +46,7 @@ public void TokenAware_TransientReplication_NoHopsAndOnlyFullReplicas() { var session = cluster.Connect(); var ks = TestUtils.GetUniqueKeyspaceName(); - session.Execute($"CREATE KEYSPACE \"{ks}\" WITH replication = {{'class': 'NetworkTopologyStrategy', '{cluster.AllHosts().First().Datacenter}' : '3/1'}}"); + session.Execute($"CREATE KEYSPACE \"{ks}\" WITH replication = {{'class': 'NetworkTopologyStrategy', '{cluster.Metadata.AllHosts().First().Datacenter}' : '3/1'}}"); session.ChangeKeyspace(ks); session.Execute("CREATE TABLE tbl1 (id uuid primary key) WITH read_repair='NONE' AND additional_write_policy='NEVER'"); var ps = session.Prepare("INSERT INTO tbl1 (id) VALUES (?)"); diff --git a/src/Cassandra.IntegrationTests/SharedCloudClusterTest.cs b/src/Cassandra.IntegrationTests/SharedCloudClusterTest.cs index 6a7d30a6c..1a2ba77e3 100644 --- a/src/Cassandra.IntegrationTests/SharedCloudClusterTest.cs +++ b/src/Cassandra.IntegrationTests/SharedCloudClusterTest.cs @@ -18,6 +18,7 @@ using System.IO; using System.Threading.Tasks; using Cassandra.IntegrationTests.TestClusterManagement; +using Cassandra.SessionManagement; namespace Cassandra.IntegrationTests { @@ -99,7 +100,7 @@ protected ICluster CreateTemporaryCluster(string creds = "creds-v1.zip", Action< return cluster; } - protected async Task CreateSessionAsync( + internal async Task CreateSessionAsync( string creds = "creds-v1.zip", int retries = SharedCloudClusterTest.MaxRetries, Action act = null) { Exception last = null; @@ -109,7 +110,7 @@ protected async Task CreateSessionAsync( try { cluster = CreateTemporaryCluster(creds, act); - return await cluster.ConnectAsync().ConfigureAwait(false); + return (IInternalSession) await cluster.ConnectAsync().ConfigureAwait(false); } catch (Exception ex) { diff --git a/src/Cassandra.IntegrationTests/SharedClusterTest.cs b/src/Cassandra.IntegrationTests/SharedClusterTest.cs index 16e77b1c9..ad0a6c4d9 100644 --- a/src/Cassandra.IntegrationTests/SharedClusterTest.cs +++ b/src/Cassandra.IntegrationTests/SharedClusterTest.cs @@ -18,6 +18,7 @@ using System.Collections.Generic; using Cassandra.IntegrationTests.TestBase; using Cassandra.IntegrationTests.TestClusterManagement; +using Cassandra.SessionManagement; using NUnit.Framework; namespace Cassandra.IntegrationTests @@ -57,6 +58,8 @@ public abstract class SharedClusterTest : TestGlobals /// protected ISession Session { get; set; } + internal IInternalSession InternalSession => (IInternalSession) Session; + /// /// It executes the queries provided on test fixture setup. /// Ignored when null. @@ -135,9 +138,9 @@ public virtual void OneTimeTearDown() ClusterInstances.Clear(); } - protected ISession GetNewTemporarySession(string keyspace = null) + internal IInternalSession GetNewTemporarySession(string keyspace = null) { - return GetNewTemporaryCluster().Connect(keyspace); + return (IInternalSession) GetNewTemporaryCluster().Connect(keyspace); } [TearDown] @@ -157,7 +160,7 @@ public virtual void TearDown() ClusterInstances.Clear(); } - protected virtual ICluster GetNewTemporaryCluster(Action build = null) + internal virtual IInternalCluster GetNewTemporaryCluster(Action build = null) { var builder = ClusterBuilder() diff --git a/src/Cassandra.IntegrationTests/SimulacronTest.cs b/src/Cassandra.IntegrationTests/SimulacronTest.cs index dd10fdc0f..90e5f88e1 100644 --- a/src/Cassandra.IntegrationTests/SimulacronTest.cs +++ b/src/Cassandra.IntegrationTests/SimulacronTest.cs @@ -61,6 +61,8 @@ protected SimulacronTest( protected ICluster SessionCluster => Session?.Cluster; + internal IInternalCluster InternalCluster => (IInternalCluster) SessionCluster; + protected SimulacronCluster TestCluster { get; private set; } protected IEnumerable> GetBoundStatementExecutionParameters(string cql) @@ -71,7 +73,7 @@ protected IEnumerable> GetBoundStatementExecutionParameters(string protected string SerializeParameter(object parameter) { - var serializer = Session.Cluster.Metadata.ControlConnection.Serializer.GetCurrentSerializer(); + var serializer = Session.Cluster.Configuration.SerializerManager.GetCurrentSerializer(); return Convert.ToBase64String(serializer.Serialize(parameter)); } @@ -93,7 +95,7 @@ protected void VerifyStatement(QueryType type, string cql, int count, params obj protected void VerifyStatement(IList logs, string cql, int count, params object[] positionalParameters) { - var serializer = Session.Cluster.Metadata.ControlConnection.Serializer.GetCurrentSerializer(); + var serializer = Session.Cluster.Configuration.SerializerManager.GetCurrentSerializer(); var paramBytes = positionalParameters.Select(obj => obj == null ? null : Convert.ToBase64String(serializer.Serialize(obj))).ToList(); var filteredQueries = logs.Where(q => (q.Query == cql || cql == null) && q.Frame.GetQueryMessage().Options.PositionalValues.SequenceEqual(paramBytes)); @@ -107,7 +109,7 @@ protected void VerifyBatchStatement(int count, byte[][] ids, params object[][] p protected void VerifyBatchStatement(int count, string[] queries, params object[][] parameters) { - var serializer = Session.Cluster.Metadata.ControlConnection.Serializer.GetCurrentSerializer(); + var serializer = Session.Cluster.Configuration.SerializerManager.GetCurrentSerializer(); var logs = TestCluster.GetQueries(null, QueryType.Batch); var paramBytes = parameters.SelectMany(obj => obj.Select(o => o == null ? null : Convert.ToBase64String(serializer.Serialize(o)))).ToArray(); @@ -119,7 +121,7 @@ protected void VerifyBatchStatement(int count, string[] queries, params object[] protected void VerifyBatchStatement(int count, string[] queries, Func func, params object[][] parameters) { - var serializer = Session.Cluster.Metadata.ControlConnection.Serializer.GetCurrentSerializer(); + var serializer = Session.Cluster.Configuration.SerializerManager.GetCurrentSerializer(); var logs = TestCluster.GetQueries(null, QueryType.Batch); var paramBytes = parameters.SelectMany(obj => obj.Select(o => o == null ? null : Convert.ToBase64String(serializer.Serialize(o)))).ToArray(); diff --git a/src/Cassandra.IntegrationTests/TestBase/TestUtils.cs b/src/Cassandra.IntegrationTests/TestBase/TestUtils.cs index b710b483f..f33994247 100644 --- a/src/Cassandra.IntegrationTests/TestBase/TestUtils.cs +++ b/src/Cassandra.IntegrationTests/TestBase/TestUtils.cs @@ -25,6 +25,7 @@ using System.Text.RegularExpressions; using System.Threading; using Cassandra.IntegrationTests.TestClusterManagement; +using Cassandra.SessionManagement; using Cassandra.Tests; using NUnit.Framework; @@ -197,7 +198,7 @@ private static void WaitForMeta(string nodeHost, Cluster cluster, int maxTry, bo try { // Are all nodes in the cluster accounted for? - bool disconnected = !cluster.RefreshSchema(); + bool disconnected = !cluster.Metadata.RefreshSchema(); if (disconnected) { string warnStr = "While waiting for host " + nodeHost + " to be " + expectedFinalNodeState + ", the cluster is now totally down, returning now ... "; @@ -205,7 +206,7 @@ private static void WaitForMeta(string nodeHost, Cluster cluster, int maxTry, bo return; } - Metadata metadata = cluster.Metadata; + var metadata = cluster.Metadata; foreach (Host host in metadata.AllHosts()) { bool hostFound = false; @@ -621,23 +622,24 @@ public static bool IsNodeReachable(IPAddress ip, int port = 9042) public static void WaitForSchemaAgreement( ICluster cluster, bool ignoreDownNodes = true, bool throwOnMaxRetries = false, int maxRetries = 20) { - var hostsLength = cluster.AllHosts().Count; + var hostsLength = cluster.Metadata.AllHosts().Count; if (hostsLength == 1) { return; } - var cc = cluster.Metadata.ControlConnection; + var cc = ((IInternalCluster)cluster).InternalMetadata.ControlConnection; var counter = 0; - var nodesDown = ignoreDownNodes ? cluster.AllHosts().Count(h => !h.IsConsiderablyUp) : 0; + var nodesDown = ignoreDownNodes ? cluster.Metadata.AllHosts().Count(h => !h.IsConsiderablyUp) : 0; while (counter++ < maxRetries) { Trace.TraceInformation("Waiting for test schema agreement"); - Thread.Sleep(500); var schemaVersions = new List(); //peers - schemaVersions.AddRange(cc.Query("SELECT peer, schema_version FROM system.peers").Select(r => r.GetValue("schema_version"))); + schemaVersions.AddRange(cc.QueryAsync("SELECT peer, schema_version FROM system.peers").GetAwaiter().GetResult() + .Select(r => r.GetValue("schema_version"))); //local - schemaVersions.Add(cc.Query("SELECT schema_version FROM system.local").Select(r => r.GetValue("schema_version")).First()); + schemaVersions.Add(cc.QueryAsync("SELECT schema_version FROM system.local").GetAwaiter().GetResult() + .Select(r => r.GetValue("schema_version")).First()); var differentSchemas = schemaVersions.Distinct().Count(); if (differentSchemas <= 1 + nodesDown) @@ -645,6 +647,8 @@ public static void WaitForSchemaAgreement( //There is 1 schema version or 1 + nodes that are considered as down return; } + + Thread.Sleep(500); } if (throwOnMaxRetries) diff --git a/src/Cassandra.Tests/BaseUnitTest.cs b/src/Cassandra.Tests/BaseUnitTest.cs index fed7c9b55..cae0da116 100644 --- a/src/Cassandra.Tests/BaseUnitTest.cs +++ b/src/Cassandra.Tests/BaseUnitTest.cs @@ -18,6 +18,7 @@ using System.Linq; using System.Net; using System.Threading.Tasks; +using Cassandra.Tasks; using NUnit.Framework; namespace Cassandra.Tests @@ -69,17 +70,17 @@ public TestLoadBalancingPolicy(HostDistance distance = HostDistance.Local) _distance = distance; } - public void Initialize(ICluster cluster) + public Task InitializeAsync(IMetadataSnapshotProvider metadata) { - + return TaskHelper.Completed; } - public HostDistance Distance(Host host) + public HostDistance Distance(IMetadataSnapshotProvider metadata, Host host) { return _distance; } - public IEnumerable NewQueryPlan(string keyspace, IStatement query) + public IEnumerable NewQueryPlan(ICluster cluster, string keyspace, IStatement query) { return new[] { diff --git a/src/Cassandra.Tests/BuilderTests.cs b/src/Cassandra.Tests/BuilderTests.cs index 115d39438..b59954c8a 100644 --- a/src/Cassandra.Tests/BuilderTests.cs +++ b/src/Cassandra.Tests/BuilderTests.cs @@ -146,7 +146,7 @@ public void MaxProtocolVersion_Defaults_To_Cluster_Max() var builder = Cluster.Builder() .AddContactPoint("192.168.1.10"); var cluster = builder.Build(); - Assert.AreEqual(Cluster.MaxProtocolVersion, cluster.Configuration.ProtocolOptions.MaxProtocolVersion); + Assert.AreEqual(Configuration.MaxProtocolVersion, cluster.Configuration.ProtocolOptions.MaxProtocolVersion); //Defaults to null Assert.Null(new ProtocolOptions().MaxProtocolVersion); } diff --git a/src/Cassandra.Tests/ClusterTests.cs b/src/Cassandra.Tests/ClusterTests.cs index 5fb203b35..fc3cf7e79 100644 --- a/src/Cassandra.Tests/ClusterTests.cs +++ b/src/Cassandra.Tests/ClusterTests.cs @@ -19,7 +19,9 @@ using System.Diagnostics; using System.Linq; using System.Net; +using System.Threading.Tasks; using Cassandra.ExecutionProfiles; +using Cassandra.Tasks; using Cassandra.Tests.Connections.TestHelpers; using Moq; using NUnit.Framework; @@ -76,7 +78,7 @@ public void ClusterAllHostsReturnsZeroHostsOnDisconnectedCluster() .AddContactPoint(ip) .Build(); //No ring was discovered - Assert.AreEqual(0, cluster.AllHosts().Count); + Assert.AreEqual(0, cluster.Metadata.AllHosts().Count); } [Test] @@ -233,7 +235,7 @@ as IExecutionProfile cluster.Connect(); cluster.Dispose(); - foreach (var h in cluster.AllHosts()) + foreach (var h in cluster.Metadata.AllHosts()) { Assert.AreEqual(expected[h.Address.Address.ToString()], h.GetDistanceUnsafe()); } @@ -242,26 +244,25 @@ as IExecutionProfile internal class FakeHostDistanceLbp : ILoadBalancingPolicy { private readonly IDictionary _distances; - private ICluster _cluster; public FakeHostDistanceLbp(IDictionary distances) { _distances = distances; } - public void Initialize(ICluster cluster) + public Task InitializeAsync(IMetadataSnapshotProvider metadata) { - _cluster = cluster; + return TaskHelper.Completed; } - public HostDistance Distance(Host host) + public HostDistance Distance(IMetadataSnapshotProvider metadata, Host host) { return _distances[host.Address.Address.ToString()]; } - public IEnumerable NewQueryPlan(string keyspace, IStatement query) + public IEnumerable NewQueryPlan(ICluster cluster, string keyspace, IStatement query) { - return _cluster.AllHosts().OrderBy(h => Guid.NewGuid().GetHashCode()).Take(_distances.Count); + return cluster.Metadata.AllHosts().OrderBy(h => Guid.NewGuid().GetHashCode()).Take(_distances.Count); } } } diff --git a/src/Cassandra.Tests/Connections/Control/ControlConnectionTests.cs b/src/Cassandra.Tests/Connections/Control/ControlConnectionTests.cs index 4bdbdc847..7f81f5eee 100644 --- a/src/Cassandra.Tests/Connections/Control/ControlConnectionTests.cs +++ b/src/Cassandra.Tests/Connections/Control/ControlConnectionTests.cs @@ -20,11 +20,15 @@ using System.Linq; using System.Net; using System.Net.Sockets; +using System.Threading; using System.Threading.Tasks; using Cassandra.Connections; using Cassandra.Connections.Control; +using Cassandra.Helpers; using Cassandra.ProtocolEvents; +using Cassandra.Serialization; using Cassandra.SessionManagement; +using Cassandra.Tasks; using Cassandra.Tests.Connections.TestHelpers; using Cassandra.Tests.MetadataHelpers.TestHelpers; using Moq; @@ -58,7 +62,7 @@ private ControlConnectionCreateResult NewInstance( IDictionary rows = null, IInternalCluster cluster = null, Configuration config = null, - Metadata metadata = null, + IInternalMetadata internalMetadata = null, Action configBuilderAct = null) { if (rows == null) @@ -103,40 +107,40 @@ private ControlConnectionCreateResult NewInstance( } Mock.Get(cluster).SetupGet(c => c.Configuration).Returns(config); - - if (metadata == null) + var contactPoints = new List { - metadata = new Metadata(config); + new IpLiteralContactPoint( + IPAddress.Parse("127.0.0.1"), + config.ProtocolOptions, + config.ServerNameResolver) + }; + + if (internalMetadata == null) + { + internalMetadata = new InternalMetadata(cluster, config, contactPoints); } + var metadata = new FakeMetadata(internalMetadata); + Mock.Get(cluster).SetupGet(c => c.Metadata).Returns(metadata); + return new ControlConnectionCreateResult { ConnectionFactory = connectionFactory, Metadata = metadata, Cluster = cluster, Config = config, - ControlConnection = new ControlConnection( - cluster, - GetEventDebouncer(config), - ProtocolVersion.MaxSupported, - config, - metadata, - new List - { - new IpLiteralContactPoint( - IPAddress.Parse("127.0.0.1"), - config.ProtocolOptions, - config.ServerNameResolver) - }) + ControlConnection = (ControlConnection)internalMetadata.ControlConnection, + InternalMetadata = internalMetadata }; } [Test] public async Task Should_SetCurrentHost_When_ANewConnectionIsOpened() { - using (var cc = NewInstance().ControlConnection) + var createResult = NewInstance(); + using (var cc = createResult.ControlConnection) { - await cc.InitAsync().ConfigureAwait(false); + await createResult.ControlConnection.InitAsync(createResult.Cluster.ClusterInitializer).ConfigureAwait(false); Assert.AreEqual("ut-dc", cc.Host.Datacenter); Assert.AreEqual("ut-rack", cc.Host.Rack); Assert.AreEqual(Version.Parse("2.2.1"), cc.Host.CassandraVersion); @@ -201,7 +205,7 @@ public void Should_NotAttemptDownOrIgnoredHosts() var config = createResult.Config; var cluster = createResult.Cluster; var cc = createResult.ControlConnection; - cc.InitAsync().GetAwaiter().GetResult(); + cc.InitAsync(cluster.ClusterInitializer).GetAwaiter().GetResult(); Assert.AreEqual(4, metadata.AllHosts().Count); var host2 = metadata.GetHost(new IPEndPoint(hostAddress2, ProtocolOptions.DefaultPort)); Assert.NotNull(host2); @@ -211,9 +215,8 @@ public void Should_NotAttemptDownOrIgnoredHosts() Mock.Get(cluster) .Setup(c => c.RetrieveAndSetDistance(It.IsAny())) - .Returns(h => config.Policies.LoadBalancingPolicy.Distance(h)); - Mock.Get(cluster).Setup(c => c.AllHosts()).Returns(() => metadata.AllHosts()); - config.Policies.LoadBalancingPolicy.Initialize(cluster); + .Returns(h => config.Policies.LoadBalancingPolicy.Distance(cluster.Metadata, h)); + config.Policies.LoadBalancingPolicy.InitializeAsync(metadata); connectionOpenEnabled = false; @@ -262,16 +265,14 @@ public async Task Should_NotLeakConnections_When_DisposeAndReconnectHappenSimult var config = createResult.Config; var cluster = createResult.Cluster; var cc = createResult.ControlConnection; - cc.InitAsync().GetAwaiter().GetResult(); + cc.InitAsync(cluster.ClusterInitializer).GetAwaiter().GetResult(); Assert.AreEqual(4, metadata.AllHosts().Count); Mock.Get(cluster) .Setup(c => c.RetrieveAndSetDistance(It.IsAny())) - .Returns(h => config.Policies.LoadBalancingPolicy.Distance(h)); - Mock.Get(cluster).Setup(c => c.AllHosts()).Returns(() => metadata.AllHosts()); - Mock.Get(cluster).Setup(c => c.GetControlConnection()).Returns(cc); - config.LocalDatacenterProvider.Initialize(cluster); - config.Policies.LoadBalancingPolicy.Initialize(cluster); + .Returns(h => config.Policies.LoadBalancingPolicy.Distance(cluster.Metadata, h)); + config.LocalDatacenterProvider.Initialize(cluster, createResult.InternalMetadata); + await config.Policies.LoadBalancingPolicy.InitializeAsync(metadata).ConfigureAwait(false); createResult.ConnectionFactory.CreatedConnections.Clear(); @@ -315,7 +316,7 @@ public void Should_ResolveContactPointsAndAttemptEveryOne_When_ContactPointResol var createResult = CreateForContactPointTest(keepContactPointsUnresolved); var target = createResult.ControlConnection; - Assert.ThrowsAsync(() => target.InitAsync()); + Assert.ThrowsAsync(() => target.InitAsync(createResult.Cluster.ClusterInitializer)); if (keepContactPointsUnresolved) { @@ -343,7 +344,8 @@ private ControlConnectionCreateResult CreateForContactPointTest(bool keepContact var config = new TestConfigurationBuilder { ConnectionFactory = connectionFactory, - KeepContactPointsUnresolved = keepContactPointsUnresolved + KeepContactPointsUnresolved = keepContactPointsUnresolved, + SerializerManager = new SerializerManager(ProtocolVersion.V3, new TypeSerializerDefinitions().Definitions) }.Build(); _cp1 = new TestContactPoint(new List { @@ -358,22 +360,29 @@ private ControlConnectionCreateResult CreateForContactPointTest(bool keepContact new ConnectionEndPoint(_endpoint1, config.ServerNameResolver, _localhost), new ConnectionEndPoint(_endpoint2, config.ServerNameResolver, _localhost) }); + config.SerializerManager.ChangeProtocolVersion(ProtocolVersion.V3); + var internalMetadata = new InternalMetadata( + Mock.Of(), + config, + new List + { + _cp1, + _cp2, + _localhost + }); + var metadata = new FakeMetadata(internalMetadata); + var cluster = Mock.Of(); + var clusterInitializer = Mock.Of(); + Mock.Get(clusterInitializer).Setup(c => c.PostInitializeAsync()).Returns(TaskHelper.Completed); + Mock.Get(cluster).SetupGet(c => c.ClusterInitializer).Returns(clusterInitializer); + Mock.Get(cluster).SetupGet(c => c.Metadata).Returns(metadata); return new ControlConnectionCreateResult { ConnectionFactory = connectionFactory, - ControlConnection = new ControlConnection( - Mock.Of(), - new ProtocolEventDebouncer( - new FakeTimerFactory(), TimeSpan.Zero, TimeSpan.Zero), - ProtocolVersion.V3, - config, - new Metadata(config), - new List - { - _cp1, - _cp2, - _localhost - }) + ControlConnection = (ControlConnection)metadata.InternalMetadata.ControlConnection, + Metadata = new Metadata(cluster.ClusterInitializer, internalMetadata), + InternalMetadata = internalMetadata, + Cluster = cluster }; } @@ -381,7 +390,9 @@ private class ControlConnectionCreateResult { public ControlConnection ControlConnection { get; set; } - public Metadata Metadata { get; set; } + public IMetadata Metadata { get; set; } + + public IInternalMetadata InternalMetadata { get; set; } public Configuration Config { get; set; } diff --git a/src/Cassandra.Tests/Connections/Control/TopologyRefresherTests.cs b/src/Cassandra.Tests/Connections/Control/TopologyRefresherTests.cs index bbb831a48..a20914b98 100644 --- a/src/Cassandra.Tests/Connections/Control/TopologyRefresherTests.cs +++ b/src/Cassandra.Tests/Connections/Control/TopologyRefresherTests.cs @@ -23,6 +23,7 @@ using Cassandra.Connections.Control; using Cassandra.Requests; using Cassandra.Serialization; +using Cassandra.SessionManagement; using Cassandra.Tests.Connections.TestHelpers; using Moq; using NUnit.Framework; @@ -36,7 +37,7 @@ public class TopologyRefresherTests private const string PeersQuery = "SELECT * FROM system.peers"; private const string PeersV2Query = "SELECT * FROM system.peers_v2"; - private Metadata _metadata; + private IInternalMetadata _internalMetadata; private ISerializer _serializer = new SerializerManager(ProtocolVersion.MaxSupported).GetCurrentSerializer(); @@ -102,9 +103,8 @@ private TopologyRefresher CreateTopologyRefresher( { MetadataRequestHandler = fakeRequestHandler }.Build(); - var metadata = new Metadata(config); - _metadata = metadata; - return new TopologyRefresher(metadata, config); + _internalMetadata = new InternalMetadata(Mock.Of(), config, new List()); + return new TopologyRefresher(_internalMetadata, config); } [Test] @@ -115,8 +115,8 @@ public async Task Should_SendSystemLocalAndPeersV1AndPeersV2Queries() { MetadataRequestHandler = fakeRequestHandler }.Build(); - _metadata = new Metadata(config); - var topologyRefresher = new TopologyRefresher(_metadata, config); + _internalMetadata = new InternalMetadata(Mock.Of(), config, new List()); + var topologyRefresher = new TopologyRefresher(_internalMetadata, config); var connection = Mock.Of(); await topologyRefresher @@ -139,8 +139,8 @@ public async Task Should_SendSystemLocalAndPeersV2Queries() { MetadataRequestHandler = fakeRequestHandler }.Build(); - _metadata = new Metadata(config); - var topologyRefresher = new TopologyRefresher(_metadata, config); + _internalMetadata = new InternalMetadata(Mock.Of(), config, new List()); + var topologyRefresher = new TopologyRefresher(_internalMetadata, config); var connection = Mock.Of(); await topologyRefresher @@ -161,8 +161,8 @@ public async Task Should_KeepSendingSystemPeersV2Queries_When_ItDoesNotFail() { MetadataRequestHandler = fakeRequestHandler }.Build(); - _metadata = new Metadata(config); - var topologyRefresher = new TopologyRefresher(_metadata, config); + _internalMetadata = new InternalMetadata(Mock.Of(), config, new List()); + var topologyRefresher = new TopologyRefresher(_internalMetadata, config); var connection = Mock.Of(); await topologyRefresher @@ -191,8 +191,8 @@ public async Task Should_SendPeersV1OnlyAfterPeersV2Fails() { var fakeRequestHandler = CreateFakeMetadataRequestHandler(); var config = new TestConfigurationBuilder { MetadataRequestHandler = fakeRequestHandler }.Build(); - _metadata = new Metadata(config); - var topologyRefresher = new TopologyRefresher(_metadata, config); + _internalMetadata = new InternalMetadata(Mock.Of(), config, new List()); + var topologyRefresher = new TopologyRefresher(_internalMetadata, config); var connection = Mock.Of(); await topologyRefresher @@ -226,7 +226,7 @@ public async Task Should_SetClusterName() await topologyRefresher.RefreshNodeListAsync( new FakeConnectionEndPoint("127.0.0.1", 9042), connection, _serializer).ConfigureAwait(false); - Assert.AreEqual("ut-cluster", _metadata.ClusterName); + Assert.AreEqual("ut-cluster", _internalMetadata.ClusterName); } [Test] @@ -245,14 +245,14 @@ await topologyRefresher.RefreshNodeListAsync( new FakeConnectionEndPoint("127.0.0.1", 9042), Mock.Of(), _serializer) .ConfigureAwait(false); - Assert.AreEqual(3, _metadata.AllHosts().Count); + Assert.AreEqual(3, _internalMetadata.AllHosts().Count); //using rpc_address - var host2 = _metadata.GetHost(new IPEndPoint(hostAddress2, ProtocolOptions.DefaultPort)); + var host2 = _internalMetadata.GetHost(new IPEndPoint(hostAddress2, ProtocolOptions.DefaultPort)); Assert.NotNull(host2); Assert.AreEqual("ut-dc2", host2.Datacenter); Assert.AreEqual("ut-rack2", host2.Rack); //with rpc_address = 0.0.0.0, use peer - var host3 = _metadata.GetHost(new IPEndPoint(hostAddress3, ProtocolOptions.DefaultPort)); + var host3 = _internalMetadata.GetHost(new IPEndPoint(hostAddress3, ProtocolOptions.DefaultPort)); Assert.NotNull(host3); Assert.AreEqual("ut-dc3", host3.Datacenter); Assert.AreEqual("ut-rack3", host3.Rack); @@ -273,7 +273,7 @@ await topologyRefresher.RefreshNodeListAsync( .ConfigureAwait(false); //Only local host present - Assert.AreEqual(1, _metadata.AllHosts().Count); + Assert.AreEqual(1, _internalMetadata.AllHosts().Count); } [Test] @@ -285,9 +285,7 @@ public async Task UpdatePeersInfoUsesAddressTranslator() .Setup(t => t.Translate(It.IsAny())) .Callback(invokedEndPoints.Add) .Returns(e => e); - const int portNumber = 9999; - var metadata = new Metadata(new Configuration()); - var hostAddress2 = IPAddress.Parse("127.0.0.2"); + const int portNumber = 9999;var hostAddress2 = IPAddress.Parse("127.0.0.2"); var hostAddress3 = IPAddress.Parse("127.0.0.3"); var rows = TestHelper.CreateRows(new List> { @@ -303,13 +301,14 @@ public async Task UpdatePeersInfoUsesAddressTranslator() StartupOptionsFactory = Mock.Of(), MetadataRequestHandler = requestHandler }.Build(); - var topologyRefresher = new TopologyRefresher(metadata, config); + _internalMetadata = new InternalMetadata(Mock.Of(), config, new List()); + var topologyRefresher = new TopologyRefresher(_internalMetadata, config); await topologyRefresher.RefreshNodeListAsync( new FakeConnectionEndPoint("127.0.0.1", 9042), Mock.Of(), _serializer) .ConfigureAwait(false); - Assert.AreEqual(3, metadata.AllHosts().Count); + Assert.AreEqual(3, _internalMetadata.AllHosts().Count); Assert.AreEqual(2, invokedEndPoints.Count); Assert.AreEqual(hostAddress2, invokedEndPoints[0].Address); Assert.AreEqual(portNumber, invokedEndPoints[0].Port); diff --git a/src/Cassandra.Tests/Connections/HostConnectionPoolTests.cs b/src/Cassandra.Tests/Connections/HostConnectionPoolTests.cs index c8e521c57..6e287b600 100644 --- a/src/Cassandra.Tests/Connections/HostConnectionPoolTests.cs +++ b/src/Cassandra.Tests/Connections/HostConnectionPoolTests.cs @@ -111,7 +111,9 @@ private IHostConnectionPool CreatePool(IEndPointResolver res = null) _host, config, SerializerManager.Default, - new MetricsObserverFactory(new MetricsManager(new NullDriverMetricsProvider(), new DriverMetricsOptions(), false, "s1")) + new MetricsObserverFactory( + new MetricsManager( + null, new NullDriverMetricsProvider(), new DriverMetricsOptions(), false, "s1")) ); pool.SetDistance(HostDistance.Local); // set expected connections length diff --git a/src/Cassandra.Tests/Connections/TestHelpers/FakeControlConnectionFactory.cs b/src/Cassandra.Tests/Connections/TestHelpers/FakeControlConnectionFactory.cs index 65782d18a..2d41ff1c9 100644 --- a/src/Cassandra.Tests/Connections/TestHelpers/FakeControlConnectionFactory.cs +++ b/src/Cassandra.Tests/Connections/TestHelpers/FakeControlConnectionFactory.cs @@ -17,12 +17,16 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; + using Cassandra.Connections; using Cassandra.Connections.Control; +using Cassandra.Helpers; using Cassandra.ProtocolEvents; using Cassandra.Serialization; using Cassandra.SessionManagement; + using Moq; namespace Cassandra.Tests.Connections.TestHelpers @@ -31,14 +35,12 @@ internal class FakeControlConnectionFactory : IControlConnectionFactory { public IControlConnection Create( IInternalCluster cluster, - IProtocolEventDebouncer protocolEventDebouncer, - ProtocolVersion initialProtocolVersion, Configuration config, - Metadata metadata, + IInternalMetadata metadata, IEnumerable contactPoints) { var cc = Mock.Of(); - Mock.Get(cc).Setup(c => c.InitAsync()).Returns(Task.Run(async () => + Mock.Get(cc).Setup(c => c.InitAsync(It.IsAny(), It.IsAny())).Returns(Task.Run(async () => { var cps = new Dictionary>(); foreach (var cp in contactPoints) @@ -54,8 +56,8 @@ public IControlConnection Create( } } metadata.SetResolvedContactPoints(cps); + config.SerializerManager.ChangeProtocolVersion(ProtocolVersion.V3); })); - Mock.Get(cc).Setup(c => c.Serializer).Returns(new SerializerManager(ProtocolVersion.V3)); return cc; } diff --git a/src/Cassandra.Tests/Connections/TestHelpers/FakeInternalMetadata.cs b/src/Cassandra.Tests/Connections/TestHelpers/FakeInternalMetadata.cs new file mode 100644 index 000000000..65b133439 --- /dev/null +++ b/src/Cassandra.Tests/Connections/TestHelpers/FakeInternalMetadata.cs @@ -0,0 +1,290 @@ +// +// Copyright (C) DataStax Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +using Cassandra.Collections; +using Cassandra.Connections; +using Cassandra.Connections.Control; +using Cassandra.MetadataHelpers; +using Cassandra.Serialization; + +namespace Cassandra.Tests.Connections.TestHelpers +{ + internal class FakeInternalMetadata : IInternalMetadata + { + private volatile CopyOnWriteDictionary> _resolvedContactPoints = + new CopyOnWriteDictionary>(); + + public FakeInternalMetadata(Configuration config) + { + Configuration = config; + Hosts = new Hosts(); + Hosts.Down += OnHostDown; + Hosts.Up += OnHostUp; + } + + public event HostsEventHandler HostsEvent; + + public event SchemaChangedEventHandler SchemaChangedEvent; + + public event Action HostAdded; + + public event Action HostRemoved; + + public Configuration Configuration { get; } + + public IControlConnection ControlConnection { get; } + + public ISerializerManager SerializerManager => throw new NotImplementedException(); + + public ISchemaParser SchemaParser => throw new NotImplementedException(); + + public string Partitioner => throw new NotImplementedException(); + + public Hosts Hosts { get; } + + public IReadOnlyDictionary> ResolvedContactPoints => + _resolvedContactPoints; + + public IReadOnlyTokenMap TokenToReplicasMap => throw new NotImplementedException(); + + public bool IsDbaas => throw new NotImplementedException(); + + public string ClusterName => throw new NotImplementedException(); + + public ProtocolVersion ProtocolVersion => throw new NotImplementedException(); + + public KeyValuePair[] KeyspacesSnapshot => throw new NotImplementedException(); + + public Task InitAsync(CancellationToken token) + { + throw new NotImplementedException(); + } + + public Task ShutdownAsync() + { + throw new NotImplementedException(); + } + + public void SetResolvedContactPoints(IDictionary> resolvedContactPoints) + { + _resolvedContactPoints = + new CopyOnWriteDictionary>(resolvedContactPoints); + } + + public Host GetHost(IPEndPoint address) + { + Hosts.TryGet(address, out var h); + return h; + } + + public Host AddHost(IPEndPoint address) + { + return Hosts.Add(address); + } + + public Host AddHost(IPEndPoint address, IContactPoint contactPoint) + { + return Hosts.Add(address, contactPoint); + } + + public void RemoveHost(IPEndPoint address) + { + Hosts.RemoveIfExists(address); + } + + public void OnHostRemoved(Host h) + { + HostRemoved?.Invoke(h); + } + + public void OnHostAdded(Host h) + { + HostAdded?.Invoke(h); + } + + public void FireSchemaChangedEvent(SchemaChangedEventArgs.Kind what, string keyspace, string table, object sender = null) + { + SchemaChangedEvent?.Invoke(sender ?? this, new SchemaChangedEventArgs { Keyspace = keyspace, What = what, Table = table }); + } + + public void OnHostDown(Host h) + { + HostsEvent?.Invoke(this, new HostsEventArgs { Address = h.Address, What = HostsEventArgs.Kind.Down }); + } + + public void OnHostUp(Host h) + { + HostsEvent?.Invoke(h, new HostsEventArgs { Address = h.Address, What = HostsEventArgs.Kind.Up }); + } + + public ICollection AllHosts() + { + return Hosts.ToCollection(); + } + + public IEnumerable AllReplicas() + { + throw new NotImplementedException(); + } + + public Task RebuildTokenMapAsync(bool retry, bool fetchKeyspaces) + { + throw new NotImplementedException(); + } + + public bool RemoveKeyspaceFromTokenMap(string name) + { + throw new NotImplementedException(); + } + + public Task UpdateTokenMapForKeyspace(string name) + { + throw new NotImplementedException(); + } + + public ICollection GetReplicas(string keyspaceName, byte[] partitionKey) + { + throw new NotImplementedException(); + } + + public ICollection GetReplicas(byte[] partitionKey) + { + throw new NotImplementedException(); + } + + public Task GetKeyspaceAsync(string keyspace) + { + throw new NotImplementedException(); + } + + public Task> GetKeyspacesAsync() + { + throw new NotImplementedException(); + } + + public Task> GetTablesAsync(string keyspace) + { + throw new NotImplementedException(); + } + + public Task GetTableAsync(string keyspace, string tableName) + { + throw new NotImplementedException(); + } + + public Task GetMaterializedViewAsync(string keyspace, string name) + { + throw new NotImplementedException(); + } + + public Task GetUdtDefinitionAsync(string keyspace, string typeName) + { + throw new NotImplementedException(); + } + + public Task GetFunctionAsync(string keyspace, string name, string[] signature) + { + throw new NotImplementedException(); + } + + public Task GetAggregateAsync(string keyspace, string name, string[] signature) + { + throw new NotImplementedException(); + } + + public Task GetQueryTraceAsync(QueryTrace trace) + { + throw new NotImplementedException(); + } + + public Task RefreshSchemaAsync(string keyspace = null, string table = null) + { + throw new NotImplementedException(); + } + + public bool RemoveKeyspace(string name) + { + throw new NotImplementedException(); + } + + public Task RefreshSingleKeyspaceAsync(string name) + { + throw new NotImplementedException(); + } + + public void ClearTable(string keyspaceName, string tableName) + { + throw new NotImplementedException(); + } + + public void ClearView(string keyspaceName, string name) + { + throw new NotImplementedException(); + } + + public void ClearFunction(string keyspaceName, string functionName, string[] signature) + { + throw new NotImplementedException(); + } + + public void ClearAggregate(string keyspaceName, string aggregateName, string[] signature) + { + throw new NotImplementedException(); + } + + public Task CheckSchemaAgreementAsync() + { + throw new NotImplementedException(); + } + + public Task WaitForSchemaAgreementAsync(IConnection connection) + { + throw new NotImplementedException(); + } + + public void SetCassandraVersion(Version version) + { + throw new NotImplementedException(); + } + + public void SetProductTypeAsDbaas() + { + throw new NotImplementedException(); + } + + public void SetClusterName(string clusterName) + { + throw new NotImplementedException(); + } + + public void SetPartitioner(string partitioner) + { + throw new NotImplementedException(); + } + + public IEnumerable UpdateResolvedContactPoint( + IContactPoint contactPoint, IEnumerable endpoints) + { + return _resolvedContactPoints.AddOrUpdate(contactPoint, _ => endpoints, (_, __) => endpoints); + } + } +} \ No newline at end of file diff --git a/src/Cassandra.Tests/Connections/TestHelpers/FakeMetadata.cs b/src/Cassandra.Tests/Connections/TestHelpers/FakeMetadata.cs new file mode 100644 index 000000000..a902ca5c4 --- /dev/null +++ b/src/Cassandra.Tests/Connections/TestHelpers/FakeMetadata.cs @@ -0,0 +1,264 @@ +// +// Copyright (C) DataStax Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; + +using Cassandra.Connections.Control; + +namespace Cassandra.Tests.Connections.TestHelpers +{ + internal class FakeMetadata : IMetadata + { + public FakeMetadata(Configuration config) + { + Configuration = config; + InternalMetadata = new FakeInternalMetadata(config); + SetupEventForwarding(InternalMetadata); + } + + public FakeMetadata(ICluster cluster, IInternalMetadata internalMetadata) + { + Cluster = cluster; + Configuration = internalMetadata.Configuration; + InternalMetadata = internalMetadata; + SetupEventForwarding(internalMetadata); + } + + public FakeMetadata(IInternalMetadata internalMetadata) : this(null, internalMetadata) + { + } + + public event HostsEventHandler HostsEvent; + + public event SchemaChangedEventHandler SchemaChangedEvent; + + public event Action HostAdded; + + public event Action HostRemoved; + + public ICluster Cluster { get; } + + public Configuration Configuration { get; } + + internal IInternalMetadata InternalMetadata { get; } + + public Task GetClusterDescriptionAsync() + { + throw new NotImplementedException(); + } + + public ClusterDescription GetClusterDescription() + { + throw new NotImplementedException(); + } + + public ICollection AllHostsSnapshot() + { + return InternalMetadata.AllHosts(); + } + + public IEnumerable AllReplicasSnapshot() + { + return InternalMetadata.AllReplicas(); + } + + public ICollection GetReplicasSnapshot(string keyspaceName, byte[] partitionKey) + { + return InternalMetadata.GetReplicas(keyspaceName, partitionKey); + } + + public ICollection GetReplicasSnapshot(byte[] partitionKey) + { + return InternalMetadata.GetReplicas(partitionKey); + } + + public Host GetHost(IPEndPoint address) + { + throw new NotImplementedException(); + } + + public Task GetHostAsync(IPEndPoint address) + { + throw new NotImplementedException(); + } + + public ICollection AllHosts() + { + throw new NotImplementedException(); + } + + public Task> AllHostsAsync() + { + throw new NotImplementedException(); + } + + public IEnumerable AllReplicas() + { + throw new NotImplementedException(); + } + + public Task> AllReplicasAsync() + { + throw new NotImplementedException(); + } + + public ICollection GetReplicas(string keyspaceName, byte[] partitionKey) + { + throw new NotImplementedException(); + } + + public ICollection GetReplicas(byte[] partitionKey) + { + throw new NotImplementedException(); + } + + public Task> GetReplicasAsync(string keyspaceName, byte[] partitionKey) + { + throw new NotImplementedException(); + } + + public Task> GetReplicasAsync(byte[] partitionKey) + { + throw new NotImplementedException(); + } + + public KeyspaceMetadata GetKeyspace(string keyspace) + { + throw new NotImplementedException(); + } + + public Task GetKeyspaceAsync(string keyspace) + { + throw new NotImplementedException(); + } + + public ICollection GetKeyspaces() + { + throw new NotImplementedException(); + } + + public Task> GetKeyspacesAsync() + { + throw new NotImplementedException(); + } + + public ICollection GetTables(string keyspace) + { + throw new NotImplementedException(); + } + + public Task> GetTablesAsync(string keyspace) + { + throw new NotImplementedException(); + } + + public TableMetadata GetTable(string keyspace, string tableName) + { + throw new NotImplementedException(); + } + + public Task GetTableAsync(string keyspace, string tableName) + { + throw new NotImplementedException(); + } + + public MaterializedViewMetadata GetMaterializedView(string keyspace, string name) + { + throw new NotImplementedException(); + } + + public Task GetMaterializedViewAsync(string keyspace, string name) + { + throw new NotImplementedException(); + } + + public UdtColumnInfo GetUdtDefinition(string keyspace, string typeName) + { + throw new NotImplementedException(); + } + + public Task GetUdtDefinitionAsync(string keyspace, string typeName) + { + throw new NotImplementedException(); + } + + public FunctionMetadata GetFunction(string keyspace, string name, string[] signature) + { + throw new NotImplementedException(); + } + + public Task GetFunctionAsync(string keyspace, string name, string[] signature) + { + throw new NotImplementedException(); + } + + public AggregateMetadata GetAggregate(string keyspace, string name, string[] signature) + { + throw new NotImplementedException(); + } + + public Task GetAggregateAsync(string keyspace, string name, string[] signature) + { + throw new NotImplementedException(); + } + + public bool RefreshSchema(string keyspace = null, string table = null) + { + throw new NotImplementedException(); + } + + public Task RefreshSchemaAsync(string keyspace = null, string table = null) + { + throw new NotImplementedException(); + } + + public Task CheckSchemaAgreementAsync() + { + throw new NotImplementedException(); + } + + private void OnInternalHostRemoved(Host h) + { + HostRemoved?.Invoke(h); + } + + private void OnInternalHostAdded(Host h) + { + HostAdded?.Invoke(h); + } + + private void OnInternalHostsEvent(object sender, HostsEventArgs args) + { + HostsEvent?.Invoke(sender, args); + } + + private void OnInternalSchemaChangedEvent(object sender, SchemaChangedEventArgs args) + { + SchemaChangedEvent?.Invoke(sender, args); + } + + private void SetupEventForwarding(IInternalMetadata internalMetadata) + { + internalMetadata.HostAdded += OnInternalHostAdded; + internalMetadata.HostRemoved += OnInternalHostRemoved; + internalMetadata.HostsEvent += OnInternalHostsEvent; + internalMetadata.SchemaChangedEvent += OnInternalSchemaChangedEvent; + } + } +} \ No newline at end of file diff --git a/src/Cassandra.Tests/Connections/TestHelpers/FakeProtocolVersionNegotiator.cs b/src/Cassandra.Tests/Connections/TestHelpers/FakeProtocolVersionNegotiator.cs index adbcaf74b..3b753a956 100644 --- a/src/Cassandra.Tests/Connections/TestHelpers/FakeProtocolVersionNegotiator.cs +++ b/src/Cassandra.Tests/Connections/TestHelpers/FakeProtocolVersionNegotiator.cs @@ -15,21 +15,26 @@ // using System.Threading.Tasks; + using Cassandra.Connections; using Cassandra.Connections.Control; -using Cassandra.Serialization; namespace Cassandra.Tests.Connections.TestHelpers { internal class FakeProtocolVersionNegotiator : IProtocolVersionNegotiator { - public Task ChangeProtocolVersion(Configuration config, ISerializerManager serializer, ProtocolVersion nextVersion, IConnection previousConnection, - UnsupportedProtocolVersionException ex = null, ProtocolVersion? previousVersion = null) + public Task ChangeProtocolVersion( + Configuration config, + ProtocolVersion nextVersion, + IConnection previousConnection, + UnsupportedProtocolVersionException ex = null, + ProtocolVersion? previousVersion = null) { return Task.FromResult(previousConnection); } - public Task NegotiateVersionAsync(Configuration config, Metadata metadata, IConnection connection, ISerializerManager serializer) + public Task NegotiateVersionAsync( + Configuration config, IInternalMetadata internalMetadata, IConnection connection) { return Task.FromResult(connection); } diff --git a/src/Cassandra.Tests/Connections/TestHelpers/FakeSupportedOptionsInitializer.cs b/src/Cassandra.Tests/Connections/TestHelpers/FakeSupportedOptionsInitializer.cs index 981118fbc..e3ea1b664 100644 --- a/src/Cassandra.Tests/Connections/TestHelpers/FakeSupportedOptionsInitializer.cs +++ b/src/Cassandra.Tests/Connections/TestHelpers/FakeSupportedOptionsInitializer.cs @@ -22,11 +22,11 @@ namespace Cassandra.Tests.Connections.TestHelpers { internal class FakeSupportedOptionsInitializer : ISupportedOptionsInitializer { - private Metadata _metadata; + private IInternalMetadata _internalMetadata; - public FakeSupportedOptionsInitializer(Metadata metadata) + public FakeSupportedOptionsInitializer(IInternalMetadata internalMetadata) { - _metadata = metadata; + _internalMetadata = internalMetadata; } public Task ApplySupportedOptionsAsync(IConnection connection) diff --git a/src/Cassandra.Tests/Connections/TestHelpers/FakeSupportedOptionsInitializerFactory.cs b/src/Cassandra.Tests/Connections/TestHelpers/FakeSupportedOptionsInitializerFactory.cs index 07efebb5c..e63973bba 100644 --- a/src/Cassandra.Tests/Connections/TestHelpers/FakeSupportedOptionsInitializerFactory.cs +++ b/src/Cassandra.Tests/Connections/TestHelpers/FakeSupportedOptionsInitializerFactory.cs @@ -19,9 +19,9 @@ namespace Cassandra.Tests.Connections.TestHelpers { internal class FakeSupportedOptionsInitializerFactory : ISupportedOptionsInitializerFactory { - public ISupportedOptionsInitializer Create(Metadata metadata) + public ISupportedOptionsInitializer Create(IInternalMetadata internalMetadata) { - return new FakeSupportedOptionsInitializer(metadata); + return new FakeSupportedOptionsInitializer(internalMetadata); } } } \ No newline at end of file diff --git a/src/Cassandra.Tests/Connections/TestHelpers/FakeTopologyRefresher.cs b/src/Cassandra.Tests/Connections/TestHelpers/FakeTopologyRefresher.cs index 421bb78e2..334230ffa 100644 --- a/src/Cassandra.Tests/Connections/TestHelpers/FakeTopologyRefresher.cs +++ b/src/Cassandra.Tests/Connections/TestHelpers/FakeTopologyRefresher.cs @@ -25,30 +25,30 @@ namespace Cassandra.Tests.Connections.TestHelpers { internal class FakeTopologyRefresher : ITopologyRefresher { - private readonly Metadata _metadata; - private readonly Configuration _config; + private readonly IInternalMetadata _internalMetadata; private readonly IDictionary _hosts; - public FakeTopologyRefresher(Metadata metadata, Configuration config, IDictionary hosts) + public FakeTopologyRefresher( + IInternalMetadata internalMetadata, IDictionary hosts) { - _metadata = metadata; - _config = config; + _internalMetadata = internalMetadata; _hosts = hosts; } - public Task RefreshNodeListAsync(IConnectionEndPoint currentEndPoint, IConnection connection, ISerializer serializer) + public Task RefreshNodeListAsync( + IConnectionEndPoint currentEndPoint, IConnection connection, ISerializer serializer) { foreach (var h in _hosts) { - if (_metadata.GetHost(h.Key) == null) + if (_internalMetadata.GetHost(h.Key) == null) { - var host = _metadata.AddHost(h.Key); + var host = _internalMetadata.AddHost(h.Key); host.SetInfo(h.Value); } } - _metadata.Partitioner = "Murmur3Partitioner"; - return Task.FromResult(_metadata.Hosts.First()); + _internalMetadata.SetPartitioner("Murmur3Partitioner"); + return Task.FromResult(_internalMetadata.Hosts.First()); } } } \ No newline at end of file diff --git a/src/Cassandra.Tests/Connections/TestHelpers/FakeTopologyRefresherFactory.cs b/src/Cassandra.Tests/Connections/TestHelpers/FakeTopologyRefresherFactory.cs index eae0343e4..548f30e50 100644 --- a/src/Cassandra.Tests/Connections/TestHelpers/FakeTopologyRefresherFactory.cs +++ b/src/Cassandra.Tests/Connections/TestHelpers/FakeTopologyRefresherFactory.cs @@ -53,9 +53,9 @@ public FakeTopologyRefresherFactory(ICollection hosts) _rows = rows; } - public ITopologyRefresher Create(Metadata metadata, Configuration config) + public ITopologyRefresher Create(IInternalMetadata internalMetadata, Configuration config) { - return new FakeTopologyRefresher(metadata, config, _rows); + return new FakeTopologyRefresher(internalMetadata, _rows); } } } \ No newline at end of file diff --git a/src/Cassandra.Tests/DataStax/Insights/InsightsClientTests.cs b/src/Cassandra.Tests/DataStax/Insights/InsightsClientTests.cs index 3b2509494..70e0d9bc2 100644 --- a/src/Cassandra.Tests/DataStax/Insights/InsightsClientTests.cs +++ b/src/Cassandra.Tests/DataStax/Insights/InsightsClientTests.cs @@ -71,14 +71,14 @@ public void Should_LogFiveTimes_When_ThereAreMoreThanFiveErrorsOnStartupMessageS cc => cc.UnsafeSendQueryRequestAsync( "CALL InsightsRpc.reportInsight(?)", It.IsAny()); - Mock.Get(cluster.Metadata.ControlConnection).Setup(mockExpression).ReturnsAsync((Response)null); + Mock.Get(cluster.InternalMetadata.ControlConnection).Setup(mockExpression).ReturnsAsync((Response)null); - target.Init(); + target.Initialize(cluster.InternalMetadata); TestHelper.RetryAssert( () => { - Mock.Get(cluster.Metadata.ControlConnection).Verify(mockExpression, Times.AtLeast(10)); + Mock.Get(cluster.InternalMetadata.ControlConnection).Verify(mockExpression, Times.AtLeast(10)); }, 30); @@ -104,7 +104,7 @@ public void Should_ResetErrorCounterForLogging_When_ThereSendMessageIsSuccessful cc => cc.UnsafeSendQueryRequestAsync( "CALL InsightsRpc.reportInsight(?)", It.IsAny()); - Mock.Get(cluster.Metadata.ControlConnection) + Mock.Get(cluster.InternalMetadata.ControlConnection) .SetupSequence(mockExpression) .ReturnsAsync((Response)null) .ReturnsAsync(new FakeResultResponse(ResultResponse.ResultResponseKind.Void)) @@ -113,12 +113,12 @@ public void Should_ResetErrorCounterForLogging_When_ThereSendMessageIsSuccessful .ReturnsAsync(new FakeResultResponse(ResultResponse.ResultResponseKind.Void)) .ReturnsAsync((Response)null); - target.Init(); + target.Initialize(cluster.InternalMetadata); TestHelper.RetryAssert( () => { - Mock.Get(cluster.Metadata.ControlConnection).Verify(mockExpression, Times.AtLeast(20)); + Mock.Get(cluster.InternalMetadata.ControlConnection).Verify(mockExpression, Times.AtLeast(20)); }, 30); @@ -251,13 +251,13 @@ public void Should_InvokeRpcCallCorrectlyAndImmediately_When_SendStartupMessageI using (var target = InsightsClientTests.GetInsightsClient(cluster, session)) { var queryProtocolOptions = new ConcurrentQueue(); - Mock.Get(cluster.Metadata.ControlConnection).Setup(cc => cc.UnsafeSendQueryRequestAsync( + Mock.Get(cluster.InternalMetadata.ControlConnection).Setup(cc => cc.UnsafeSendQueryRequestAsync( "CALL InsightsRpc.reportInsight(?)", It.IsAny())) .ReturnsAsync(new FakeResultResponse(ResultResponse.ResultResponseKind.Void)) .Callback((query, opts) => { queryProtocolOptions.Enqueue(opts); }); - target.Init(); + target.Initialize(cluster.InternalMetadata); TestHelper.RetryAssert( () => { Assert.GreaterOrEqual(queryProtocolOptions.Count, 1); }, 10, 50); @@ -375,13 +375,13 @@ public void Should_InvokeRpcCallCorrectlyAndImmediatelyWithExecutionProfiles_Whe using (var target = InsightsClientTests.GetInsightsClient(cluster, session)) { var queryProtocolOptions = new ConcurrentQueue(); - Mock.Get(cluster.Metadata.ControlConnection).Setup(cc => cc.UnsafeSendQueryRequestAsync( + Mock.Get(cluster.InternalMetadata.ControlConnection).Setup(cc => cc.UnsafeSendQueryRequestAsync( "CALL InsightsRpc.reportInsight(?)", It.IsAny())) .ReturnsAsync(new FakeResultResponse(ResultResponse.ResultResponseKind.Void)) .Callback((query, opts) => { queryProtocolOptions.Enqueue(opts); }); - target.Init(); + target.Initialize(cluster.InternalMetadata); TestHelper.RetryAssert( () => { Assert.GreaterOrEqual(queryProtocolOptions.Count, 1); }, 10, 50); @@ -412,13 +412,13 @@ public void Should_InvokeRpcCallCorrectlyAndPeriodically_When_SendStatusMessageI using (var target = InsightsClientTests.GetInsightsClient(cluster, session)) { var queryProtocolOptions = new ConcurrentQueue(); - Mock.Get(cluster.Metadata.ControlConnection).Setup(cc => cc.UnsafeSendQueryRequestAsync( + Mock.Get(cluster.InternalMetadata.ControlConnection).Setup(cc => cc.UnsafeSendQueryRequestAsync( "CALL InsightsRpc.reportInsight(?)", It.IsAny())) .ReturnsAsync(new FakeResultResponse(ResultResponse.ResultResponseKind.Void)) .Callback((query, opts) => { queryProtocolOptions.Enqueue(opts); }); - target.Init(); + target.Initialize(cluster.InternalMetadata); TestHelper.RetryAssert(() => { Assert.GreaterOrEqual(queryProtocolOptions.Count, 5); }, 5, 400); queryProtocolOptions.TryDequeue(out var result); // ignore startup message @@ -434,14 +434,14 @@ private static InsightsClient GetInsightsClient(IInternalCluster cluster, IInter var timestampGeneratorMock = Mock.Of(); var platformInfoMock = Mock.Of>(); - Mock.Get(hostnameInfoMock).Setup(m => m.GetInformation(cluster, session)).Returns("awesome_hostname"); - Mock.Get(driverInfoMock).Setup(m => m.GetInformation(cluster, session)).Returns(new DriverInfo + Mock.Get(hostnameInfoMock).Setup(m => m.GetInformation(cluster, session, cluster.InternalMetadata)).Returns("awesome_hostname"); + Mock.Get(driverInfoMock).Setup(m => m.GetInformation(cluster, session, cluster.InternalMetadata)).Returns(new DriverInfo { DriverVersion = "1.1.2", DriverName = "Driver Name" }); Mock.Get(timestampGeneratorMock).Setup(m => m.GenerateTimestamp()).Returns(124219041); - Mock.Get(platformInfoMock).Setup(m => m.GetInformation(cluster, session)).Returns(new InsightsPlatformInfo + Mock.Get(platformInfoMock).Setup(m => m.GetInformation(cluster, session, cluster.InternalMetadata)).Returns(new InsightsPlatformInfo { CentralProcessingUnits = new CentralProcessingUnitsInfo { @@ -507,8 +507,8 @@ private IInternalSession GetSession(IInternalCluster cluster) { host1, mockPool1 }, { host2, mockPool2 } }; - Mock.Get(cluster).Setup(m => m.GetHost(host1)).Returns(new Host(host1, contactPoint: null)); - Mock.Get(cluster).Setup(m => m.GetHost(host2)).Returns(new Host(host2, contactPoint: null)); + Mock.Get(cluster.InternalMetadata).Setup(m => m.GetHost(host1)).Returns(new Host(host1, contactPoint: null)); + Mock.Get(cluster.InternalMetadata).Setup(m => m.GetHost(host2)).Returns(new Host(host2, contactPoint: null)); Mock.Get(session).Setup(s => s.GetPools()).Returns(pools.ToArray()); Mock.Get(session).Setup(m => m.Cluster).Returns(cluster); Mock.Get(session).SetupGet(m => m.InternalSessionId).Returns(Guid.Parse("E21EAB96-D91E-4790-80BD-1D5FB5472258")); @@ -519,17 +519,16 @@ private IInternalCluster GetCluster(bool withProfiles, int eventDelayMillisecond { var cluster = Mock.Of(); var config = GetConfig(eventDelayMilliseconds, withProfiles); - var metadata = new Metadata(config) - { - ControlConnection = Mock.Of() - }; - Mock.Get(metadata.ControlConnection).SetupGet(cc => cc.ProtocolVersion).Returns(ProtocolVersion.V4); - Mock.Get(metadata.ControlConnection).SetupGet(cc => cc.EndPoint) + var metadata = Mock.Of(); + var controlConnection = Mock.Of(); + Mock.Get(metadata).SetupGet(m => m.ControlConnection).Returns(controlConnection); + Mock.Get(metadata).SetupGet(cc => cc.ProtocolVersion).Returns(ProtocolVersion.V4); + Mock.Get(controlConnection).SetupGet(cc => cc.EndPoint) .Returns(new ConnectionEndPoint(new IPEndPoint(IPAddress.Parse("10.10.10.10"), 9011), config.ServerNameResolver, null)); - Mock.Get(metadata.ControlConnection).SetupGet(cc => cc.LocalAddress).Returns(new IPEndPoint(IPAddress.Parse("10.10.10.2"), 9015)); + Mock.Get(controlConnection).SetupGet(cc => cc.LocalAddress).Returns(new IPEndPoint(IPAddress.Parse("10.10.10.2"), 9015)); var hostIp = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9042); var hostIp2 = new IPEndPoint(IPAddress.Parse("127.0.0.2"), 9042); - metadata.SetResolvedContactPoints(new Dictionary> + Mock.Get(metadata).SetupGet(cc => cc.ResolvedContactPoints).Returns(new Dictionary> { { new HostnameContactPoint( @@ -540,12 +539,14 @@ private IInternalCluster GetCluster(bool withProfiles, int eventDelayMillisecond "localhost"), new[] { new ConnectionEndPoint(hostIp, config.ServerNameResolver, null) } } }); - metadata.AddHost(hostIp); - metadata.AddHost(hostIp2); - metadata.Hosts.ToCollection().First().Datacenter = "dc123"; + var hosts = new Hosts(); + hosts.Add(hostIp); + hosts.Add(hostIp2); + hosts.ToCollection().First().Datacenter = "dc123"; + Mock.Get(metadata).SetupGet(m => m.Hosts).Returns(hosts); + Mock.Get(metadata).Setup(m => m.AllHosts()).Returns(hosts.ToCollection); Mock.Get(cluster).SetupGet(m => m.Configuration).Returns(config); - Mock.Get(cluster).SetupGet(m => m.Metadata).Returns(metadata); - Mock.Get(cluster).Setup(c => c.AllHosts()).Returns(metadata.AllHosts); + Mock.Get(cluster).SetupGet(m => m.InternalMetadata).Returns(metadata); return cluster; } @@ -553,7 +554,7 @@ private Configuration GetConfig(int eventDelayMilliseconds, bool withProfiles) { var graphOptions = new GraphOptions().SetName("testGraphName").SetReadConsistencyLevel(ConsistencyLevel.All); var supportVerifier = Mock.Of(); - Mock.Get(supportVerifier).Setup(m => m.SupportsInsights(It.IsAny())).Returns(true); + Mock.Get(supportVerifier).Setup(m => m.SupportsInsights(It.IsAny())).Returns(true); return new TestConfigurationBuilder { Policies = new Cassandra.Policies( diff --git a/src/Cassandra.Tests/DataStax/Insights/InsightsSupportVerifierTests.cs b/src/Cassandra.Tests/DataStax/Insights/InsightsSupportVerifierTests.cs index 322e88e28..baa4b82fc 100644 --- a/src/Cassandra.Tests/DataStax/Insights/InsightsSupportVerifierTests.cs +++ b/src/Cassandra.Tests/DataStax/Insights/InsightsSupportVerifierTests.cs @@ -16,6 +16,7 @@ using System.Collections.Generic; using System.Net; +using Cassandra.Connections.Control; using Cassandra.DataStax.Insights; using Cassandra.SessionManagement; using Moq; @@ -29,166 +30,166 @@ public class InsightsSupportVerifierTests [Test] public void Should_ReturnFalse_When_OneNode_6_0_4_AndOneNode_6_0_5() { - var cluster = Mock.Of(); - Mock.Get(cluster).Setup(c => c.AllHosts()).Returns(GetHosts("6.0.4", "6.0.5")); + var internalMetadata = Mock.Of(); + Mock.Get(internalMetadata).Setup(c => c.AllHosts()).Returns(GetHosts("6.0.4", "6.0.5")); var target = new InsightsSupportVerifier(); - Assert.IsFalse(target.SupportsInsights(cluster)); + Assert.IsFalse(target.SupportsInsights(internalMetadata)); } [Test] public void Should_ReturnFalse_When_OneNode_6_0_5_AndOneNode_6_0_4() { - var cluster = Mock.Of(); - Mock.Get(cluster).Setup(c => c.AllHosts()).Returns(GetHosts("6.0.5", "6.0.4")); + var internalMetadata = Mock.Of(); + Mock.Get(internalMetadata).Setup(c => c.AllHosts()).Returns(GetHosts("6.0.5", "6.0.4")); var target = new InsightsSupportVerifier(); - Assert.IsFalse(target.SupportsInsights(cluster)); + Assert.IsFalse(target.SupportsInsights(internalMetadata)); } [Test] public void Should_ReturnTrue_When_OneNode_6_1_0_AndOneNode_6_0_5() { - var cluster = Mock.Of(); - Mock.Get(cluster).Setup(c => c.AllHosts()).Returns(GetHosts("6.1.0", "6.0.5")); + var internalMetadata = Mock.Of(); + Mock.Get(internalMetadata).Setup(c => c.AllHosts()).Returns(GetHosts("6.1.0", "6.0.5")); var target = new InsightsSupportVerifier(); - Assert.IsTrue(target.SupportsInsights(cluster)); + Assert.IsTrue(target.SupportsInsights(internalMetadata)); } [Test] public void Should_ReturnTrue_When_TwoNodes_6_0_5() { - var cluster = Mock.Of(); - Mock.Get(cluster).Setup(c => c.AllHosts()).Returns(GetHosts("6.0.5", "6.0.5")); + var internalMetadata = Mock.Of(); + Mock.Get(internalMetadata).Setup(c => c.AllHosts()).Returns(GetHosts("6.0.5", "6.0.5")); var target = new InsightsSupportVerifier(); - Assert.IsTrue(target.SupportsInsights(cluster)); + Assert.IsTrue(target.SupportsInsights(internalMetadata)); } [Test] public void Should_ReturnFalse_When_TwoNodes_6_0_4() { - var cluster = Mock.Of(); - Mock.Get(cluster).Setup(c => c.AllHosts()).Returns(GetHosts("6.0.4", "6.0.4")); + var internalMetadata = Mock.Of(); + Mock.Get(internalMetadata).Setup(c => c.AllHosts()).Returns(GetHosts("6.0.4", "6.0.4")); var target = new InsightsSupportVerifier(); - Assert.IsFalse(target.SupportsInsights(cluster)); + Assert.IsFalse(target.SupportsInsights(internalMetadata)); } [Test] public void Should_ReturnFalse_When_OneNode5_1_12() { - var cluster = Mock.Of(); - Mock.Get(cluster).Setup(c => c.AllHosts()).Returns(GetHosts("5.1.12")); + var internalMetadata = Mock.Of(); + Mock.Get(internalMetadata).Setup(c => c.AllHosts()).Returns(GetHosts("5.1.12")); var target = new InsightsSupportVerifier(); - Assert.IsFalse(target.SupportsInsights(cluster)); + Assert.IsFalse(target.SupportsInsights(internalMetadata)); } [Test] public void Should_ReturnTrue_When_OneNode5_1_13() { - var cluster = Mock.Of(); - Mock.Get(cluster).Setup(c => c.AllHosts()).Returns(GetHosts("5.1.13")); + var internalMetadata = Mock.Of(); + Mock.Get(internalMetadata).Setup(c => c.AllHosts()).Returns(GetHosts("5.1.13")); var target = new InsightsSupportVerifier(); - Assert.IsTrue(target.SupportsInsights(cluster)); + Assert.IsTrue(target.SupportsInsights(internalMetadata)); } [Test] public void Should_ReturnTrue_When_OneNode5_2_0() { - var cluster = Mock.Of(); - Mock.Get(cluster).Setup(c => c.AllHosts()).Returns(GetHosts("5.2.0")); + var internalMetadata = Mock.Of(); + Mock.Get(internalMetadata).Setup(c => c.AllHosts()).Returns(GetHosts("5.2.0")); var target = new InsightsSupportVerifier(); - Assert.IsTrue(target.SupportsInsights(cluster)); + Assert.IsTrue(target.SupportsInsights(internalMetadata)); } [Test] public void Should_ReturnTrue_When_OneNode6_0_5() { - var cluster = Mock.Of(); - Mock.Get(cluster).Setup(c => c.AllHosts()).Returns(GetHosts("6.0.5")); + var internalMetadata = Mock.Of(); + Mock.Get(internalMetadata).Setup(c => c.AllHosts()).Returns(GetHosts("6.0.5")); var target = new InsightsSupportVerifier(); - Assert.IsTrue(target.SupportsInsights(cluster)); + Assert.IsTrue(target.SupportsInsights(internalMetadata)); } [Test] public void Should_ReturnTrue_When_OneNode6_0_5_alpha() { - var cluster = Mock.Of(); - Mock.Get(cluster).Setup(c => c.AllHosts()).Returns(GetHosts("6.0.5-alpha")); + var internalMetadata = Mock.Of(); + Mock.Get(internalMetadata).Setup(c => c.AllHosts()).Returns(GetHosts("6.0.5-alpha")); var target = new InsightsSupportVerifier(); - Assert.IsTrue(target.SupportsInsights(cluster)); + Assert.IsTrue(target.SupportsInsights(internalMetadata)); } [Test] public void Should_ReturnFalse_When_OneNode6_0_4() { - var cluster = Mock.Of(); - Mock.Get(cluster).Setup(c => c.AllHosts()).Returns(GetHosts("6.0.4")); + var internalMetadata = Mock.Of(); + Mock.Get(internalMetadata).Setup(c => c.AllHosts()).Returns(GetHosts("6.0.4")); var target = new InsightsSupportVerifier(); - Assert.IsFalse(target.SupportsInsights(cluster)); + Assert.IsFalse(target.SupportsInsights(internalMetadata)); } [Test] public void Should_ReturnTrue_When_OneNode6_1_0() { - var cluster = Mock.Of(); - Mock.Get(cluster).Setup(c => c.AllHosts()).Returns(GetHosts("6.1.0")); + var internalMetadata = Mock.Of(); + Mock.Get(internalMetadata).Setup(c => c.AllHosts()).Returns(GetHosts("6.1.0")); var target = new InsightsSupportVerifier(); - Assert.IsTrue(target.SupportsInsights(cluster)); + Assert.IsTrue(target.SupportsInsights(internalMetadata)); } [Test] public void Should_ReturnFalse_When_OneNode5_0_99() { - var cluster = Mock.Of(); - Mock.Get(cluster).Setup(c => c.AllHosts()).Returns(GetHosts("5.0.99")); + var internalMetadata = Mock.Of(); + Mock.Get(internalMetadata).Setup(c => c.AllHosts()).Returns(GetHosts("5.0.99")); var target = new InsightsSupportVerifier(); - Assert.IsFalse(target.SupportsInsights(cluster)); + Assert.IsFalse(target.SupportsInsights(internalMetadata)); } [Test] public void Should_ReturnFalse_When_OneNode5_0_0() { - var cluster = Mock.Of(); - Mock.Get(cluster).Setup(c => c.AllHosts()).Returns(GetHosts("5.0.0")); + var internalMetadata = Mock.Of(); + Mock.Get(internalMetadata).Setup(c => c.AllHosts()).Returns(GetHosts("5.0.0")); var target = new InsightsSupportVerifier(); - Assert.IsFalse(target.SupportsInsights(cluster)); + Assert.IsFalse(target.SupportsInsights(internalMetadata)); } [Test] public void Should_ReturnFalse_When_OneNode4_8_0() { - var cluster = Mock.Of(); - Mock.Get(cluster).Setup(c => c.AllHosts()).Returns(GetHosts("4.8.0")); + var internalMetadata = Mock.Of(); + Mock.Get(internalMetadata).Setup(c => c.AllHosts()).Returns(GetHosts("4.8.0")); var target = new InsightsSupportVerifier(); - Assert.IsFalse(target.SupportsInsights(cluster)); + Assert.IsFalse(target.SupportsInsights(internalMetadata)); } private ICollection GetHosts(params string[] dseVersions) diff --git a/src/Cassandra.Tests/DataStax/Insights/MessageFactories/InsightsMessageFactoryTests.cs b/src/Cassandra.Tests/DataStax/Insights/MessageFactories/InsightsMessageFactoryTests.cs index 1968ea2f1..cec6c7d89 100644 --- a/src/Cassandra.Tests/DataStax/Insights/MessageFactories/InsightsMessageFactoryTests.cs +++ b/src/Cassandra.Tests/DataStax/Insights/MessageFactories/InsightsMessageFactoryTests.cs @@ -39,7 +39,7 @@ public void Should_ReturnCorrectMetadata_When_CreateStartupMessageIsCalled() var target = Configuration.DefaultInsightsStartupMessageFactory; var timestamp = (long)(DateTimeOffset.UtcNow - new DateTimeOffset(1970, 1, 1, 0, 0, 0, 0, TimeSpan.Zero)).TotalMilliseconds; - var act = target.CreateMessage(cluster, Mock.Of()); + var act = target.CreateMessage(cluster, Mock.Of(), cluster.InternalMetadata); Assert.AreEqual(InsightType.Event, act.Metadata.InsightType); Assert.AreEqual("v1", act.Metadata.InsightMappingId); @@ -56,7 +56,7 @@ public void Should_ReturnCorrectMetadata_When_CreateStatusMessageIsCalled() var target = Configuration.DefaultInsightsStatusMessageFactory; var timestamp = (long)(DateTimeOffset.UtcNow - new DateTimeOffset(1970, 1, 1, 0, 0, 0, 0, TimeSpan.Zero)).TotalMilliseconds; - var act = target.CreateMessage(cluster, Mock.Of()); + var act = target.CreateMessage(cluster, Mock.Of(), cluster.InternalMetadata); Assert.AreEqual(InsightType.Event, act.Metadata.InsightType); Assert.AreEqual("v1", act.Metadata.InsightMappingId); @@ -84,14 +84,14 @@ public void Should_ReturnCorrectData_When_CreateStatusMessageIsCalled() { host1, mockPool1 }, { host2, mockPool2 } }; - Mock.Get(cluster).Setup(m => m.GetHost(host1)).Returns(new Host(host1, contactPoint: null)); - Mock.Get(cluster).Setup(m => m.GetHost(host2)).Returns(new Host(host2, contactPoint: null)); + Mock.Get(cluster.InternalMetadata).Setup(m => m.GetHost(host1)).Returns(new Host(host1, contactPoint: null)); + Mock.Get(cluster.InternalMetadata).Setup(m => m.GetHost(host2)).Returns(new Host(host2, contactPoint: null)); Mock.Get(session).Setup(s => s.GetPools()).Returns(pools.ToArray()); Mock.Get(session).Setup(m => m.Cluster).Returns(cluster); Mock.Get(session).SetupGet(m => m.InternalSessionId).Returns(Guid.Parse("E21EAB96-D91E-4790-80BD-1D5FB5472258")); var target = Configuration.DefaultInsightsStatusMessageFactory; - var act = target.CreateMessage(cluster, session); + var act = target.CreateMessage(cluster, session, cluster.InternalMetadata); Assert.AreEqual("127.0.0.1:9011", act.Data.ControlConnection); Assert.AreEqual("BECFE098-E462-47E7-B6A7-A21CD316D4C0", act.Data.ClientId.ToUpper()); @@ -111,7 +111,7 @@ public void Should_ReturnCorrectData_When_CreateStartupMessageIsCalled() var session = Mock.Of(); Mock.Get(session).SetupGet(m => m.InternalSessionId).Returns(Guid.Parse("E21EAB96-D91E-4790-80BD-1D5FB5472258")); - var act = target.CreateMessage(cluster, session); + var act = target.CreateMessage(cluster, session, cluster.InternalMetadata); InsightsMessageFactoryTests.AssertStartupOptions(act); @@ -226,11 +226,11 @@ private static void AssertExecutionProfile(Insight act) private IInternalCluster GetCluster() { var cluster = Mock.Of(); + var metadata = Mock.Of(); + var ccMock = Mock.Of(); + Mock.Get(cluster).SetupGet(c => c.InternalMetadata).Returns(metadata); + Mock.Get(metadata).SetupGet(m => m.ControlConnection).Returns(ccMock); var config = GetConfig(); - var metadata = new Metadata(config) - { - ControlConnection = Mock.Of() - }; var contactPoint = new HostnameContactPoint( config.DnsResolver, config.ProtocolOptions, @@ -239,19 +239,20 @@ private IInternalCluster GetCluster() "localhost"); var connectionEndPoint = new ConnectionEndPoint( new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9011), config.ServerNameResolver, contactPoint); - Mock.Get(metadata.ControlConnection).SetupGet(cc => cc.ProtocolVersion).Returns(ProtocolVersion.V4); + Mock.Get(metadata).SetupGet(m => m.ResolvedContactPoints).Returns( + new Dictionary> + { + { contactPoint, new[] { connectionEndPoint } } + }); + Mock.Get(metadata).SetupGet(cc => cc.ProtocolVersion).Returns(ProtocolVersion.V4); Mock.Get(metadata.ControlConnection).SetupGet(cc => cc.EndPoint).Returns(connectionEndPoint); Mock.Get(metadata.ControlConnection).SetupGet(cc => cc.LocalAddress).Returns(new IPEndPoint(IPAddress.Parse("10.10.10.2"), 9015)); var hostIp = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9042); - metadata.SetResolvedContactPoints(new Dictionary> - { - { contactPoint, new [] { connectionEndPoint } } - }); - metadata.AddHost(hostIp); - metadata.Hosts.ToCollection().First().Datacenter = "dc123"; + var hosts = new Hosts(); + hosts.Add(hostIp); + hosts.ToCollection().First().Datacenter = "dc123"; Mock.Get(cluster).SetupGet(m => m.Configuration).Returns(config); - Mock.Get(cluster).SetupGet(m => m.Metadata).Returns(metadata); - Mock.Get(cluster).Setup(c => c.AllHosts()).Returns(metadata.AllHosts); + Mock.Get(metadata).Setup(c => c.AllHosts()).Returns(hosts.ToCollection()); return cluster; } diff --git a/src/Cassandra.Tests/ExecutionProfiles/ClusterTests.cs b/src/Cassandra.Tests/ExecutionProfiles/ClusterTests.cs index 0319ed4ae..423aa358f 100644 --- a/src/Cassandra.Tests/ExecutionProfiles/ClusterTests.cs +++ b/src/Cassandra.Tests/ExecutionProfiles/ClusterTests.cs @@ -17,7 +17,9 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Threading.Tasks; using Cassandra.ExecutionProfiles; +using Cassandra.Tasks; using Cassandra.Tests.Connections.TestHelpers; using Moq; using NUnit.Framework; @@ -324,17 +326,19 @@ private class FakeSpeculativeExecutionPolicy : ISpeculativeExecutionPolicy public volatile int InitializeCount; public volatile int DisposeCount; - public void Dispose() + public Task ShutdownAsync() { DisposeCount++; + return TaskHelper.Completed; } - public void Initialize(ICluster cluster) + public Task InitializeAsync(IMetadataSnapshotProvider metadata) { InitializeCount++; + return TaskHelper.Completed; } - public ISpeculativeExecutionPlan NewPlan(string keyspace, IStatement statement) + public ISpeculativeExecutionPlan NewPlan(ICluster cluster, string keyspace, IStatement statement) { throw new System.NotImplementedException(); } @@ -343,22 +347,21 @@ public ISpeculativeExecutionPlan NewPlan(string keyspace, IStatement statement) internal class FakeLoadBalancingPolicy : ILoadBalancingPolicy { public volatile int InitializeCount; - private ICluster _cluster; - public void Initialize(ICluster cluster) + public Task InitializeAsync(IMetadataSnapshotProvider metadata) { - _cluster = cluster; InitializeCount++; + return TaskHelper.Completed; } - public HostDistance Distance(Host host) + public HostDistance Distance(IMetadataSnapshotProvider metadata, Host host) { return HostDistance.Local; } - public IEnumerable NewQueryPlan(string keyspace, IStatement query) + public IEnumerable NewQueryPlan(ICluster cluster, string keyspace, IStatement query) { - return _cluster.AllHosts(); + return cluster.Metadata.AllHosts(); } } } diff --git a/src/Cassandra.Tests/ExecutionProfiles/RequestHandlerTests.cs b/src/Cassandra.Tests/ExecutionProfiles/RequestHandlerTests.cs index 72a4b48f2..b92023830 100644 --- a/src/Cassandra.Tests/ExecutionProfiles/RequestHandlerTests.cs +++ b/src/Cassandra.Tests/ExecutionProfiles/RequestHandlerTests.cs @@ -29,6 +29,7 @@ using Cassandra.Responses; using Cassandra.Serialization; using Cassandra.SessionManagement; +using Cassandra.Tasks; using Cassandra.Tests.Connections.TestHelpers; using Cassandra.Tests.Requests; @@ -279,14 +280,16 @@ private RequestHandlerMockResult BuildRequestHandler( cluster.Connect(); // create session - var session = new Session(cluster, config, null, SerializerManager.Default, null); + var session = new Session(cluster, config, null, null); // create request handler var options = profile != null - ? new RequestOptions(profile, null, config.Policies, config.SocketOptions, config.QueryOptions, config.ClientOptions) + ? new RequestOptions( + profile, null, config.Policies, config.SocketOptions, config.QueryOptions, config.ClientOptions) : config.DefaultRequestOptions; var requestHandler = new RequestHandler( session, + cluster.InternalRef.InternalMetadata, new SerializerManager(ProtocolVersion.V3).GetCurrentSerializer(), statement, options); @@ -368,16 +371,17 @@ private class FakeLoadBalancingPolicy : ILoadBalancingPolicy { public long Count; - public void Initialize(ICluster cluster) + public Task InitializeAsync(IMetadataSnapshotProvider metadata) { + return TaskHelper.Completed; } - public HostDistance Distance(Host host) + public HostDistance Distance(IMetadataSnapshotProvider metadata, Host host) { return HostDistance.Local; } - public IEnumerable NewQueryPlan(string keyspace, IStatement query) + public IEnumerable NewQueryPlan(ICluster cluster, string keyspace, IStatement query) { Interlocked.Increment(ref Count); return new List @@ -449,18 +453,20 @@ private class FakeSpeculativeExecutionPolicy : ISpeculativeExecutionPolicy { public long Count; - public void Dispose() + public Task ShutdownAsync() { + return TaskHelper.Completed; } - public void Initialize(ICluster cluster) + public Task InitializeAsync(IMetadataSnapshotProvider metadata) { + return TaskHelper.Completed; } - public ISpeculativeExecutionPlan NewPlan(string keyspace, IStatement statement) + public ISpeculativeExecutionPlan NewPlan(ICluster cluster, string keyspace, IStatement statement) { Interlocked.Increment(ref Count); - return new ConstantSpeculativeExecutionPolicy(10, 1).NewPlan(keyspace, statement); + return new ConstantSpeculativeExecutionPolicy(10, 1).NewPlan(cluster, keyspace, statement); } } } diff --git a/src/Cassandra.Tests/ExecutionProfiles/SessionTests.cs b/src/Cassandra.Tests/ExecutionProfiles/SessionTests.cs index ccb21c3d3..e313c21e0 100644 --- a/src/Cassandra.Tests/ExecutionProfiles/SessionTests.cs +++ b/src/Cassandra.Tests/ExecutionProfiles/SessionTests.cs @@ -58,10 +58,10 @@ public async Task Should_CreateRequestHandlerWithCorrectRequestOptions_When_Exec Mock.Get(clusterMock).Setup(c => c.Configuration).Returns(config); Mock.Get(requestHandlerMock).Setup(r => r.SendAsync()).Returns(Task.FromResult(new RowSet())); - var session = new Session(clusterMock, config, null, serializer, null); + var session = new Session(clusterMock, config, null, null); Mock.Get(requestHandlerFactoryMock) - .Setup(m => m.Create(session, serializer.GetCurrentSerializer(), It.IsAny(), config.RequestOptions["testE"])) + .Setup(m => m.Create(session, clusterMock.InternalMetadata, serializer.GetCurrentSerializer(), It.IsAny(), config.RequestOptions["testE"])) .Returns(requestHandlerMock); if (async) @@ -73,7 +73,7 @@ public async Task Should_CreateRequestHandlerWithCorrectRequestOptions_When_Exec session.Execute(new SimpleStatement("test query"), "testE"); } - Mock.Get(requestHandlerFactoryMock).Verify(m => m.Create(session, serializer.GetCurrentSerializer(), It.IsAny(), config.RequestOptions["testE"]), Times.Once); + Mock.Get(requestHandlerFactoryMock).Verify(m => m.Create(session, clusterMock.InternalMetadata, serializer.GetCurrentSerializer(), It.IsAny(), config.RequestOptions["testE"]), Times.Once); } [Test] @@ -85,7 +85,6 @@ public async Task Should_CreateRequestHandlerWithDefaultRequestOptions_When_Exec var requestHandlerMock = Mock.Of(); var hostConnectionPoolFactoryMock = Mock.Of(); var clusterMock = Mock.Of(); - var serializer = SerializerManager.Default; var config = new TestConfigurationBuilder { RequestHandlerFactory = requestHandlerFactoryMock, @@ -104,10 +103,15 @@ public async Task Should_CreateRequestHandlerWithDefaultRequestOptions_When_Exec Mock.Get(clusterMock).Setup(c => c.Configuration).Returns(config); Mock.Get(requestHandlerMock).Setup(r => r.SendAsync()).Returns(Task.FromResult(new RowSet())); - var session = new Session(clusterMock, config, null, serializer, null); + var session = new Session(clusterMock, config, null, null); Mock.Get(requestHandlerFactoryMock) - .Setup(m => m.Create(session, serializer.GetCurrentSerializer(), It.IsAny(), config.DefaultRequestOptions)) + .Setup(m => m.Create( + session, + clusterMock.InternalMetadata, + config.SerializerManager.GetCurrentSerializer(), + It.IsAny(), + config.DefaultRequestOptions)) .Returns(requestHandlerMock); if (async) @@ -119,7 +123,14 @@ public async Task Should_CreateRequestHandlerWithDefaultRequestOptions_When_Exec session.Execute(new SimpleStatement("test query")); } - Mock.Get(requestHandlerFactoryMock).Verify(m => m.Create(session, serializer.GetCurrentSerializer(), It.IsAny(), config.DefaultRequestOptions), Times.Once); + Mock.Get(requestHandlerFactoryMock).Verify( + m => m.Create( + session, + clusterMock.InternalMetadata, + config.SerializerManager.GetCurrentSerializer(), + It.IsAny(), + config.DefaultRequestOptions), + Times.Once); } } } \ No newline at end of file diff --git a/src/Cassandra.Tests/HostConnectionPoolTests.cs b/src/Cassandra.Tests/HostConnectionPoolTests.cs index 160fcc2f7..65947ea17 100644 --- a/src/Cassandra.Tests/HostConnectionPoolTests.cs +++ b/src/Cassandra.Tests/HostConnectionPoolTests.cs @@ -98,7 +98,7 @@ private static Mock GetPoolMock(Host host = null, Configurat host, config, new SerializerManager(ProtocolVersion.MaxSupported), - new MetricsObserverFactory(new MetricsManager(new NullDriverMetricsProvider(), new DriverMetricsOptions(), false, "s1"))); + new MetricsObserverFactory(new MetricsManager(null, new NullDriverMetricsProvider(), new DriverMetricsOptions(), false, "s1"))); } private static Configuration GetConfig(int coreConnections = 3, int maxConnections = 8, IReconnectionPolicy rp = null) diff --git a/src/Cassandra.Tests/Mapping/Linq/LinqCreateTableUnitTests.cs b/src/Cassandra.Tests/Mapping/Linq/LinqCreateTableUnitTests.cs index 0c26462f6..536b962d5 100644 --- a/src/Cassandra.Tests/Mapping/Linq/LinqCreateTableUnitTests.cs +++ b/src/Cassandra.Tests/Mapping/Linq/LinqCreateTableUnitTests.cs @@ -18,6 +18,7 @@ using Cassandra.Data.Linq; using Cassandra.Mapping; using Cassandra.Serialization; +using Cassandra.Tests.Connections.TestHelpers; using Cassandra.Tests.Mapping.Pocos; using Moq; using NUnit.Framework; @@ -585,13 +586,8 @@ private static Mock GetSessionMock(ISerializerManager serializer = nul serializer = new SerializerManager(ProtocolVersion.MaxSupported); } var sessionMock = new Mock(MockBehavior.Strict); - var config = new Configuration(); - var metadata = new Metadata(config); - var ccMock = new Mock(MockBehavior.Strict); - ccMock.Setup(cc => cc.Serializer).Returns(serializer); - metadata.ControlConnection = ccMock.Object; + var config = new TestConfigurationBuilder { SerializerManager = serializer }.Build(); var clusterMock = new Mock(); - clusterMock.Setup(c => c.Metadata).Returns(metadata); clusterMock.Setup(c => c.Configuration).Returns(config); sessionMock.Setup(s => s.Cluster).Returns(clusterMock.Object); return sessionMock; diff --git a/src/Cassandra.Tests/Mapping/Linq/LinqEntryPointsTests.cs b/src/Cassandra.Tests/Mapping/Linq/LinqEntryPointsTests.cs index 8601b3729..81ca31cf3 100644 --- a/src/Cassandra.Tests/Mapping/Linq/LinqEntryPointsTests.cs +++ b/src/Cassandra.Tests/Mapping/Linq/LinqEntryPointsTests.cs @@ -14,12 +14,12 @@ // limitations under the License. // -using Cassandra.Connections.Control; using Cassandra.Data.Linq; using Cassandra.Mapping; -using Cassandra.Serialization; using Cassandra.Tests.Mapping.Pocos; + using Moq; + using NUnit.Framework; namespace Cassandra.Tests.Mapping.Linq @@ -58,7 +58,7 @@ public void Deprecated_EntryPoint_Honors_Mapping_Defined() public void Deprecated_EntryPoint_Uses_Table_Provided() { MappingConfiguration.Global.Define(new Map().TableName("tbl1")); - var table = _session.GetTable( "linqTable"); + var table = _session.GetTable("linqTable"); Assert.AreEqual( @"SELECT BooleanValue, DateTimeValue, DecimalValue, DoubleValue, Int64Value, IntValue, StringValue, UuidValue FROM linqTable", table.ToString()); @@ -68,7 +68,7 @@ public void Deprecated_EntryPoint_Uses_Table_Provided() public void Deprecated_EntryPoint_Uses_Keyspace_Provided() { MappingConfiguration.Global.Define(new Map().TableName("tbl1")); - var table = _session.GetTable( "linqTable", "linqKs"); + var table = _session.GetTable("linqTable", "linqKs"); Assert.AreEqual( @"SELECT BooleanValue, DateTimeValue, DecimalValue, DoubleValue, Int64Value, IntValue, StringValue, UuidValue FROM linqKs.linqTable", table.ToString()); @@ -124,23 +124,14 @@ public void Table_Constructor_Uses_Provided_Mappings_With_Custom_Keyspace_And_Ta table.Where(t => t.Int64Value == 1).ToString()); } - private static Mock GetSessionMock(ISerializerManager serializer = null) + private static Mock GetSessionMock() { - if (serializer == null) - { - serializer = new SerializerManager(ProtocolVersion.MaxSupported); - } var sessionMock = new Mock(MockBehavior.Strict); var config = new Configuration(); - var metadata = new Metadata(config); - var ccMock = new Mock(MockBehavior.Strict); - ccMock.Setup(cc => cc.Serializer).Returns(serializer); - metadata.ControlConnection = ccMock.Object; var clusterMock = new Mock(); - clusterMock.Setup(c => c.Metadata).Returns(metadata); clusterMock.Setup(c => c.Configuration).Returns(config); sessionMock.Setup(s => s.Cluster).Returns(clusterMock.Object); return sessionMock; } } -} +} \ No newline at end of file diff --git a/src/Cassandra.Tests/Mapping/Linq/LinqMappingUnitTests.cs b/src/Cassandra.Tests/Mapping/Linq/LinqMappingUnitTests.cs index c3a88d51f..57b5627ad 100644 --- a/src/Cassandra.Tests/Mapping/Linq/LinqMappingUnitTests.cs +++ b/src/Cassandra.Tests/Mapping/Linq/LinqMappingUnitTests.cs @@ -18,6 +18,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Cassandra.Connections.Control; using Cassandra.Data.Linq; using Cassandra.Mapping; using Cassandra.Metrics.Internal; @@ -37,6 +38,13 @@ private ISession GetSession(RowSet result) var clusterMock = new Mock(); clusterMock.Setup(c => c.Configuration).Returns(new Configuration()); + var metadataMock = new Mock(); + var metadataInternal = new Mock(); + metadataInternal.SetupGet(m => m.ProtocolVersion).Returns(ProtocolVersion.V2); + metadataMock.Setup(m => m.GetClusterDescription()).Returns(new ClusterDescription(metadataInternal.Object)); + metadataMock.Setup(m => m.GetClusterDescriptionAsync()).ReturnsAsync(new ClusterDescription(metadataInternal.Object)); + clusterMock.SetupGet(c => c.Metadata).Returns(metadataMock.Object); + var sessionMock = new Mock(MockBehavior.Strict); sessionMock.Setup(s => s.Keyspace).Returns(null); sessionMock @@ -48,7 +56,6 @@ private ISession GetSession(RowSet result) .Returns(TestHelper.DelayedTask(result, 200)) .Verifiable(); sessionMock.Setup(s => s.PrepareAsync(It.IsAny())).Returns(TaskHelper.ToTask(GetPrepared("Mock query"))); - sessionMock.Setup(s => s.BinaryProtocolVersion).Returns(2); sessionMock.Setup(s => s.Cluster).Returns(clusterMock.Object); return sessionMock.Object; } @@ -156,7 +163,11 @@ public void Linq_CqlQuery_Automatically_Pages() .Callback((s, profile) => stmt = (BoundStatement)s) .Verifiable(); sessionMock.Setup(s => s.PrepareAsync(It.IsAny())).Returns(TaskHelper.ToTask(GetPrepared("Mock query"))); - sessionMock.Setup(s => s.BinaryProtocolVersion).Returns(2); + var metadataMock = new Mock(); + var metadataInternal = new Mock(); + metadataInternal.SetupGet(m => m.ProtocolVersion).Returns(ProtocolVersion.V2); + metadataMock.Setup(m => m.GetClusterDescription()).Returns(new ClusterDescription(metadataInternal.Object)); + clusterMock.SetupGet(c => c.Metadata).Returns(metadataMock.Object); rs.AutoPage = true; rs.PagingState = new byte[] { 0, 0, 0 }; var counter = 0; diff --git a/src/Cassandra.Tests/Mapping/Linq/LinqToCqlUnitTests.cs b/src/Cassandra.Tests/Mapping/Linq/LinqToCqlUnitTests.cs index 188f68a42..f57b73b49 100644 --- a/src/Cassandra.Tests/Mapping/Linq/LinqToCqlUnitTests.cs +++ b/src/Cassandra.Tests/Mapping/Linq/LinqToCqlUnitTests.cs @@ -449,12 +449,7 @@ public void CreateTableCounterTest() var actualCqlQueries = new List(); var sessionMock = new Mock(MockBehavior.Strict); var config = new Configuration(); - var metadata = new Metadata(config); - var ccMock = new Mock(MockBehavior.Strict); - ccMock.Setup(cc => cc.Serializer).Returns(new SerializerManager(ProtocolVersion.MaxSupported)); - metadata.ControlConnection = ccMock.Object; var clusterMock = new Mock(); - clusterMock.Setup(c => c.Metadata).Returns(metadata); clusterMock.Setup(c => c.Configuration).Returns(config); sessionMock.Setup(s => s.Cluster).Returns(clusterMock.Object); sessionMock @@ -462,6 +457,7 @@ public void CreateTableCounterTest() .Returns(() => new RowSet()) .Callback(actualCqlQueries.Add) .Verifiable(); + sessionMock.Setup(s => s.ConnectAsync()).Returns(TaskHelper.Completed); var session = sessionMock.Object; var table1 = SessionExtensions.GetTable(session); @@ -485,13 +481,9 @@ public void VirtualPropertiesTest() var query2 = (from e in table where e.Id == 1 && e.Name == "MyName" select new { e.Id, e.Name, e.Description }); Assert.AreEqual("SELECT \"Id\", \"Name\", \"Description\" FROM \"InheritedEntity\" WHERE \"Id\" = ? AND \"Name\" = ?", query2.ToString()); var sessionMock = new Mock(MockBehavior.Strict); + sessionMock.Setup(s => s.ConnectAsync()).Returns(TaskHelper.Completed); var config = new Configuration(); - var metadata = new Metadata(config); - var ccMock = new Mock(MockBehavior.Strict); - ccMock.Setup(cc => cc.Serializer).Returns(new SerializerManager(ProtocolVersion.MaxSupported)); - metadata.ControlConnection = ccMock.Object; var clusterMock = new Mock(); - clusterMock.Setup(c => c.Metadata).Returns(metadata); clusterMock.Setup(c => c.Configuration).Returns(config); sessionMock.Setup(s => s.Cluster).Returns(clusterMock.Object); string createQuery = null; diff --git a/src/Cassandra.Tests/Mapping/MappingTestBase.cs b/src/Cassandra.Tests/Mapping/MappingTestBase.cs index 9578f3446..1216cd48b 100644 --- a/src/Cassandra.Tests/Mapping/MappingTestBase.cs +++ b/src/Cassandra.Tests/Mapping/MappingTestBase.cs @@ -133,9 +133,6 @@ protected ISession GetSession(RowSet rs, Action callback .Setup(s => s.PrepareAsync(It.IsAny())) .Returns(query => TaskHelper.ToTask(GetPrepared(query))) .Verifiable(); - sessionMock - .Setup(s => s.BinaryProtocolVersion) - .Returns((int)protocolVersion); return sessionMock.Object; } diff --git a/src/Cassandra.Tests/MetadataHelpers/TestHelpers/FakeSchemaParser.cs b/src/Cassandra.Tests/MetadataHelpers/TestHelpers/FakeSchemaParser.cs index 640ab0007..769cdfe54 100644 --- a/src/Cassandra.Tests/MetadataHelpers/TestHelpers/FakeSchemaParser.cs +++ b/src/Cassandra.Tests/MetadataHelpers/TestHelpers/FakeSchemaParser.cs @@ -18,6 +18,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Cassandra.Tests.Connections.TestHelpers; namespace Cassandra.Tests.MetadataHelpers.TestHelpers { @@ -26,7 +27,7 @@ internal class FakeSchemaParser : SchemaParser private readonly ConcurrentDictionary _keyspaces; public FakeSchemaParser( - ConcurrentDictionary keyspaces) : base(new Metadata(new Configuration())) + ConcurrentDictionary keyspaces) : base(new FakeInternalMetadata(new Configuration())) { _keyspaces = keyspaces; } diff --git a/src/Cassandra.Tests/MetadataHelpers/TestHelpers/FakeSchemaParserFactory.cs b/src/Cassandra.Tests/MetadataHelpers/TestHelpers/FakeSchemaParserFactory.cs index c448953da..c53d30418 100644 --- a/src/Cassandra.Tests/MetadataHelpers/TestHelpers/FakeSchemaParserFactory.cs +++ b/src/Cassandra.Tests/MetadataHelpers/TestHelpers/FakeSchemaParserFactory.cs @@ -1,12 +1,12 @@ -// +// // Copyright (C) DataStax Inc. -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -17,6 +17,8 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; + +using Cassandra.Connections.Control; using Cassandra.MetadataHelpers; namespace Cassandra.Tests.MetadataHelpers.TestHelpers @@ -24,7 +26,7 @@ namespace Cassandra.Tests.MetadataHelpers.TestHelpers internal class FakeSchemaParserFactory : ISchemaParserFactory { public ISchemaParser Create( - Version cassandraVersion, Metadata parent, Func> udtResolver, ISchemaParser currentInstance = null) + Version cassandraVersion, IInternalMetadata parent, Func> udtResolver, ISchemaParser currentInstance = null) { var keyspaces = new ConcurrentDictionary(); diff --git a/src/Cassandra.Tests/Policies/DefaultLoadBalancingTests.cs b/src/Cassandra.Tests/Policies/DefaultLoadBalancingTests.cs index 3e4c958e2..345ff0f00 100644 --- a/src/Cassandra.Tests/Policies/DefaultLoadBalancingTests.cs +++ b/src/Cassandra.Tests/Policies/DefaultLoadBalancingTests.cs @@ -29,7 +29,7 @@ public void Should_Yield_Preferred_Host_First() #pragma warning restore 618 var statement = new TargettedSimpleStatement("Q"); statement.PreferredHost = new Host(new IPEndPoint(201, 9042), ReconnectionPolicy); - var hosts = lbp.NewQueryPlan(null, statement); + var hosts = lbp.NewQueryPlan(null, null, statement); CollectionAssert.AreEqual( new[] { "201.0.0.0:9042", "101.0.0.0:9042", "102.0.0.0:9042" }, hosts.Select(h => h.Address.ToString())); @@ -42,7 +42,7 @@ public void Should_Yield_Child_Hosts_When_No_Preferred_Host_Defined() var lbp = new DefaultLoadBalancingPolicy(new TestLoadBalancingPolicy()); #pragma warning restore 618 var statement = new TargettedSimpleStatement("Q"); - var hosts = lbp.NewQueryPlan(null, statement); + var hosts = lbp.NewQueryPlan(null, null, statement); CollectionAssert.AreEqual( new[] { "101.0.0.0:9042", "102.0.0.0:9042" }, hosts.Select(h => h.Address.ToString())); @@ -54,12 +54,12 @@ public void Should_Set_Distance_For_Preferred_Host_To_Local() #pragma warning disable 618 var lbp = new DefaultLoadBalancingPolicy(new TestLoadBalancingPolicy(HostDistance.Ignored)); #pragma warning restore 618 - Assert.AreEqual(HostDistance.Ignored, lbp.Distance(new Host(new IPEndPoint(200L, 9042), ReconnectionPolicy))); + Assert.AreEqual(HostDistance.Ignored, lbp.Distance(null, new Host(new IPEndPoint(200L, 9042), ReconnectionPolicy))); var statement = new TargettedSimpleStatement("Q"); // Use 201 as preferred statement.PreferredHost = new Host(new IPEndPoint(201L, 9042), ReconnectionPolicy); - lbp.NewQueryPlan(null, statement); - Assert.AreEqual(HostDistance.Local, lbp.Distance(statement.PreferredHost)); + lbp.NewQueryPlan(null, null, statement); + Assert.AreEqual(HostDistance.Local, lbp.Distance(null, statement.PreferredHost)); } } } diff --git a/src/Cassandra.Tests/PoliciesUnitTests.cs b/src/Cassandra.Tests/PoliciesUnitTests.cs index 4b7e6eccf..9299ae970 100644 --- a/src/Cassandra.Tests/PoliciesUnitTests.cs +++ b/src/Cassandra.Tests/PoliciesUnitTests.cs @@ -18,15 +18,15 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Net; -using NUnit.Framework; -using Moq; using System.Threading.Tasks; -using System.Threading; + using Cassandra.Connections.Control; using Cassandra.SessionManagement; using Cassandra.Tests.Connections.TestHelpers; -using Cassandra.Tests.MetadataHelpers.TestHelpers; + +using Moq; + +using NUnit.Framework; #pragma warning disable 618 @@ -36,21 +36,23 @@ namespace Cassandra.Tests public class PoliciesUnitTests { [Test] - public void RoundRobinIsCyclicTest() + public async Task RoundRobinIsCyclicTest() { byte hostLength = 4; var hostList = GetHostList(hostLength); - var clusterMock = new Mock(); - clusterMock - .Setup(c => c.AllHosts()) + var metadataMock = new Mock(); + metadataMock + .Setup(c => c.AllHostsSnapshot()) .Returns(hostList) .Verifiable(); + var clusterMock = new Mock(); + clusterMock.SetupGet(c => c.Metadata).Returns(metadataMock.Object); //Initialize the balancing policy var policy = new RoundRobinPolicy(); - policy.Initialize(clusterMock.Object); - var balancedHosts = policy.NewQueryPlan(null, new SimpleStatement()); + await policy.InitializeAsync(metadataMock.Object).ConfigureAwait(false); + var balancedHosts = policy.NewQueryPlan(clusterMock.Object, null, new SimpleStatement()); //Take a list of hosts of 4, it should get 1 of every one in a cyclic order. var firstRound = balancedHosts.ToList(); @@ -65,7 +67,7 @@ public void RoundRobinIsCyclicTest() var followingRounds = new List(); for (var i = 0; i < 10; i++) { - followingRounds.AddRange(policy.NewQueryPlan(null, new SimpleStatement())); + followingRounds.AddRange(policy.NewQueryPlan(clusterMock.Object, null, new SimpleStatement())); } Assert.AreEqual(10 * hostLength, followingRounds.Count); @@ -77,72 +79,65 @@ public void RoundRobinIsCyclicTest() Assert.AreNotSame(followingRounds[i + 2], followingRounds[i]); } - clusterMock.Verify(); + metadataMock.Verify(); } [Test] - public void RoundRobinIsCyclicTestInParallel() + public async Task RoundRobinIsCyclicTestInParallel() { byte hostLength = 4; var hostList = GetHostList(hostLength); - var clusterMock = new Mock(); - clusterMock - .Setup(c => c.AllHosts()) + var metadataMock = new Mock(); + metadataMock + .Setup(c => c.AllHostsSnapshot()) .Returns(hostList) .Verifiable(); + var clusterMock = new Mock(); + clusterMock.SetupGet(c => c.Metadata).Returns(metadataMock.Object); //Initialize the balancing policy var policy = new RoundRobinPolicy(); - policy.Initialize(clusterMock.Object); + await policy.InitializeAsync(metadataMock.Object).ConfigureAwait(false); - Action action = () => + Func action = async _ => { var resultingHosts = new List(); - var hostEnumerator = policy.NewQueryPlan(null, new SimpleStatement()); + var hostEnumerator = policy.NewQueryPlan(clusterMock.Object, null, new SimpleStatement()); foreach (var h in hostEnumerator) { //Slow down to try to execute it at the same time - Thread.Sleep(50); + await Task.Delay(50).ConfigureAwait(false); resultingHosts.Add(h); } Assert.AreEqual(hostLength, resultingHosts.Count); Assert.AreEqual(hostLength, resultingHosts.Distinct().Count()); }; - var actions = new List(); - for (var i = 0; i < 100; i++) - { - actions.Add(action); - } - - var parallelOptions = new ParallelOptions(); - parallelOptions.TaskScheduler = new ThreadPerTaskScheduler(); - parallelOptions.MaxDegreeOfParallelism = 1000; - - Parallel.Invoke(parallelOptions, actions.ToArray()); - clusterMock.Verify(); + var actions = Enumerable.Range(0, 100).Select(action); + await Task.WhenAll(actions).ConfigureAwait(false); + metadataMock.Verify(); } [TestCase(true)] [TestCase(false)] [Test] - public void DcInferringPolicyInitializeInfersLocalDc(bool implicitContactPoint) + public async Task DcInferringPolicyInitializeInfersLocalDc(bool implicitContactPoint) { var hostList = new List { TestHelper.CreateHost("0.0.0.1", "dc1"), TestHelper.CreateHost("0.0.0.2", "dc2") }; - var clusterMock = CreateClusterMock(hostList, implicitContactPoint: implicitContactPoint); + var metadataMock = CreateMetadataMock(hostList, implicitContactPoint: implicitContactPoint); var policy = new DcInferringLoadBalancingPolicy(); - policy.Initialize(clusterMock); - Assert.AreEqual(HostDistance.Local, policy.Distance(hostList[0])); - Assert.AreEqual(HostDistance.Remote, policy.Distance(hostList[1])); + await policy.InitializeAsync(metadataMock).ConfigureAwait(false); + Assert.AreEqual(HostDistance.Local, policy.Distance(metadataMock, hostList[0])); + Assert.AreEqual(HostDistance.Remote, policy.Distance(metadataMock, hostList[1])); } [Test] - public void DCAwareRoundRobinPolicyNeverHitsRemote() + public async Task DCAwareRoundRobinPolicyNeverHitsRemote() { byte hostLength = 5; var hostList = new List(); @@ -153,13 +148,13 @@ public void DCAwareRoundRobinPolicyNeverHitsRemote() //Add another remote host at the end hostList.AddRange(GetHostList(2, 2, "remote")); - var clusterMock = CreateClusterMock(hostList); + var metadataMock = CreateMetadataMock(hostList); //Initialize the balancing policy //0 used nodes per remote dc var policy = new DCAwareRoundRobinPolicy("local"); - policy.Initialize(clusterMock); - var balancedHosts = policy.NewQueryPlan(null, new SimpleStatement()); + await policy.InitializeAsync(metadataMock).ConfigureAwait(false); + var balancedHosts = policy.NewQueryPlan(metadataMock.Cluster, null, new SimpleStatement()); var firstRound = balancedHosts.ToList(); //Returns only local hosts @@ -170,27 +165,27 @@ public void DCAwareRoundRobinPolicyNeverHitsRemote() var followingRounds = new List(); for (var i = 0; i < 10; i++) { - followingRounds.AddRange(policy.NewQueryPlan(null, new SimpleStatement()).ToList()); + followingRounds.AddRange(policy.NewQueryPlan(metadataMock.Cluster, null, new SimpleStatement()).ToList()); } Assert.AreEqual(10 * (hostLength - 2), followingRounds.Count); - + //Check that there aren't remote nodes. Assert.AreEqual(0, followingRounds.Count(h => h.Datacenter != "local")); } - + [Test] - public void DCAwareRoundRobinInitializeUsesBuilderLocalDc() + public async Task DCAwareRoundRobinInitializeUsesBuilderLocalDc() { var hostList = new List { TestHelper.CreateHost("0.0.0.1", "dc1"), TestHelper.CreateHost("0.0.0.2", "dc2") }; - var clusterMock = CreateClusterMock(hostList, "dc2"); + var metadataMock = CreateMetadataMock(hostList, "dc2"); var policy = new DCAwareRoundRobinPolicy(); - policy.Initialize(clusterMock); - Assert.AreEqual(HostDistance.Remote, policy.Distance(hostList[0])); - Assert.AreEqual(HostDistance.Local, policy.Distance(hostList[1])); + await policy.InitializeAsync(metadataMock).ConfigureAwait(false); + Assert.AreEqual(HostDistance.Remote, policy.Distance(metadataMock, hostList[0])); + Assert.AreEqual(HostDistance.Local, policy.Distance(metadataMock, hostList[1])); } [Test] @@ -201,30 +196,30 @@ public void DCAwareRoundRobinInitializeDoesNotInferLocalDcWhenNotImplicitContact TestHelper.CreateHost("0.0.0.1", "dc1"), TestHelper.CreateHost("0.0.0.2", "dc2") }; - var clusterMock = CreateClusterMock(hostList, implicitContactPoint: false); + var metadataMock = CreateMetadataMock(hostList, implicitContactPoint: false); var policy = new DCAwareRoundRobinPolicy(); - var ex = Assert.Throws(() => policy.Initialize(clusterMock)); + var ex = Assert.ThrowsAsync(() => policy.InitializeAsync(metadataMock)); Assert.AreEqual( "Since you provided explicit contact points, the local datacenter " + "must be explicitly set. It can be specified in the load balancing " + "policy constructor or via the Builder.WithLocalDatacenter() method." + - " Available datacenters: dc1, dc2.", + " Available datacenters: dc1, dc2.", ex.Message); } [Test] - public void DCAwareRoundRobinInitializeInfersLocalDcImplicitContactPoint() + public async Task DCAwareRoundRobinInitializeInfersLocalDcImplicitContactPoint() { var hostList = new List { TestHelper.CreateHost("0.0.0.1", "dc1"), TestHelper.CreateHost("0.0.0.2", "dc2") }; - var clusterMock = CreateClusterMock(hostList, implicitContactPoint: true); + var metadataMock = CreateMetadataMock(hostList, implicitContactPoint: true); var policy = new DCAwareRoundRobinPolicy(); - policy.Initialize(clusterMock); - Assert.AreEqual(HostDistance.Local, policy.Distance(hostList[0])); - Assert.AreEqual(HostDistance.Remote, policy.Distance(hostList[1])); + await policy.InitializeAsync(metadataMock).ConfigureAwait(false); + Assert.AreEqual(HostDistance.Local, policy.Distance(metadataMock, hostList[0])); + Assert.AreEqual(HostDistance.Remote, policy.Distance(metadataMock, hostList[1])); } [Test] @@ -235,14 +230,14 @@ public void DCAwareRoundRobinInitializeNotMatchingDcThrows() TestHelper.CreateHost("0.0.0.1", "dc1"), TestHelper.CreateHost("0.0.0.2", "dc2") }; - var clusterMock = CreateClusterMock(hostList); + var clusterMock = CreateMetadataMock(hostList); var policy = new DCAwareRoundRobinPolicy("not_valid_dc"); - var ex = Assert.Throws(() => policy.Initialize(clusterMock)); + var ex = Assert.ThrowsAsync(() => policy.InitializeAsync(clusterMock)); Assert.IsTrue( - ex.Message.Contains("Datacenter not_valid_dc does not match any of the nodes, available datacenters:"), + ex.Message.Contains("Datacenter not_valid_dc does not match any of the nodes, available datacenters:"), ex.Message); } - + [Test] public void DCAwareRoundRobinInitializeNotMatchingDcFromBuilderLocalDcThrows() { @@ -251,16 +246,16 @@ public void DCAwareRoundRobinInitializeNotMatchingDcFromBuilderLocalDcThrows() TestHelper.CreateHost("0.0.0.1", "dc1"), TestHelper.CreateHost("0.0.0.2", "dc2") }; - var clusterMock = CreateClusterMock(hostList, "not_valid_dc"); + var metadataMock = CreateMetadataMock(hostList, "not_valid_dc"); var policy = new DCAwareRoundRobinPolicy(); - var ex = Assert.Throws(() => policy.Initialize(clusterMock)); + var ex = Assert.ThrowsAsync(() => policy.InitializeAsync(metadataMock)); Assert.IsTrue( - ex.Message.Contains("Datacenter not_valid_dc does not match any of the nodes, available datacenters:"), + ex.Message.Contains("Datacenter not_valid_dc does not match any of the nodes, available datacenters:"), ex.Message); } [Test] - public void DCAwareRoundRobinPolicyTestInParallel() + public async Task DCAwareRoundRobinPolicyTestInParallel() { var hostList = new List { @@ -278,17 +273,17 @@ public void DCAwareRoundRobinPolicyTestInParallel() var localHostsLength = hostList.Count(h => h.Datacenter == "dc1"); const string localDc = "dc1"; - var clusterMock = CreateClusterMock(hostList); + var metadataMock = CreateMetadataMock(hostList); //Initialize the balancing policy var policy = new DCAwareRoundRobinPolicy(localDc); - policy.Initialize(clusterMock); + await policy.InitializeAsync(metadataMock).ConfigureAwait(false); var allHosts = new ConcurrentBag(); var firstHosts = new ConcurrentBag(); Action action = () => { - var hosts = policy.NewQueryPlan(null, null).ToList(); + var hosts = policy.NewQueryPlan(metadataMock.Cluster, null, null).ToList(); //Check that the value is not repeated Assert.AreEqual(0, hosts.GroupBy(x => x) .Where(g => g.Count() > 1) @@ -315,18 +310,18 @@ public void DCAwareRoundRobinPolicyTestInParallel() { if (h.Datacenter == localDc) { - Assert.AreEqual(times/localHostsLength, firstHosts.Count(hc => hc == h)); + Assert.AreEqual(times / localHostsLength, firstHosts.Count(hc => hc == h)); } else { Assert.AreEqual(0, firstHosts.Count(hc => hc == h)); } } - Mock.Get(clusterMock).Verify(); + Mock.Get(metadataMock.InternalMetadata).Verify(); } [Test] - public void DCAwareRoundRobinPolicyCachesLocalNodes() + public async Task DCAwareRoundRobinPolicyCachesLocalNodes() { var hostList = new List { @@ -343,20 +338,20 @@ public void DCAwareRoundRobinPolicyCachesLocalNodes() }; const string localDc = "dc1"; - var clusterMock = CreateClusterMock(hostList); + var metadataMock = CreateMetadataMock(hostList); //Initialize the balancing policy var policy = new DCAwareRoundRobinPolicy(localDc); - policy.Initialize(clusterMock); + await policy.InitializeAsync(metadataMock).ConfigureAwait(false); var instances = new ConcurrentBag(); - Action action = () => instances.Add(policy.GetHosts()); + Action action = () => instances.Add(policy.GetHosts(metadataMock)); TestHelper.ParallelInvoke(action, 100); Assert.AreEqual(1, instances.GroupBy(i => i.GetHashCode()).Count()); } [Test] - public void DCAwareRoundRobinPolicyWithNodesChanging() + public async Task DCAwareRoundRobinPolicyWithNodesChanging() { var hostList = new ConcurrentBag { @@ -374,8 +369,8 @@ public void DCAwareRoundRobinPolicyWithNodesChanging() const string localDc = "dc1"; //to remove the host 3 var hostToRemove = hostList.First(h => TestHelper.GetLastAddressByte(h) == 3); - var clusterMock = CreateClusterMock(); - Mock.Get(clusterMock) + var metadataMock = CreateMetadataMock(); + Mock.Get(metadataMock.InternalMetadata) .Setup(c => c.AllHosts()) .Returns(() => { @@ -383,12 +378,11 @@ public void DCAwareRoundRobinPolicyWithNodesChanging() }); //Initialize the balancing policy - clusterMock.Configuration.LocalDatacenterProvider.Initialize(clusterMock); var policy = new DCAwareRoundRobinPolicy(localDc); - policy.Initialize(clusterMock); + await policy.InitializeAsync(metadataMock).ConfigureAwait(false); var hostYielded = new ConcurrentBag>(); - Action action = () => hostYielded.Add(policy.NewQueryPlan(null, null).ToList()); + Action action = () => hostYielded.Add(policy.NewQueryPlan(metadataMock.Cluster, null, null).ToList()); //Invoke without nodes changing TestHelper.ParallelInvoke(action, 100); @@ -400,7 +394,7 @@ public void DCAwareRoundRobinPolicyWithNodesChanging() { var host = TestHelper.CreateHost("0.0.0.11", "dc1"); //raise event and then add - Mock.Get(clusterMock).Raise(c => c.HostAdded += null, host); + Mock.Get(metadataMock.InternalMetadata).Raise(c => c.HostAdded += null, host); hostList.Add(host); }); actionList.Insert(400, () => @@ -408,14 +402,14 @@ public void DCAwareRoundRobinPolicyWithNodesChanging() var host = TestHelper.CreateHost("0.0.0.12", "dc1"); //first add and then raise event hostList.Add(host); - Mock.Get(clusterMock).Raise(c => c.HostAdded += null, host); + Mock.Get(metadataMock.InternalMetadata).Raise(c => c.HostAdded += null, host); }); - + actionList.Insert(400, () => { var host = hostToRemove; hostList = new ConcurrentBag(hostList.Where(h => h != hostToRemove)); - Mock.Get(clusterMock).Raise(c => c.HostRemoved += null, host); + Mock.Get(metadataMock.InternalMetadata).Raise(c => c.HostRemoved += null, host); }); //Invoke it with nodes being modified @@ -463,7 +457,7 @@ public void DowngradingConsistencyRetryTest() [Test] public void FixedReconnectionPolicyTests() { - var delays = new long[] {0, 2, 100, 200, 500, 1000}; + var delays = new long[] { 0, 2, 100, 200, 500, 1000 }; var policy = new FixedReconnectionPolicy(delays); var schedule = policy.NewSchedule(); const int times = 30; @@ -479,7 +473,7 @@ public void FixedReconnectionPolicyTests() } [Test] - public void TokenAwarePolicyReturnsLocalReplicasOnly() + public async Task TokenAwarePolicyReturnsLocalReplicasOnly() { var hostList = new List { @@ -495,8 +489,8 @@ public void TokenAwarePolicyReturnsLocalReplicasOnly() TestHelper.CreateHost("0.0.0.9", "dc1") }; var n = 2; - var clusterMock = CreateClusterMock(hostList); - Mock.Get(clusterMock) + var metadataMock = CreateMetadataMock(hostList); + Mock.Get(metadataMock.InternalMetadata) .Setup(c => c.GetReplicas(It.IsAny(), It.IsAny())) .Returns((keyspace, key) => { @@ -511,33 +505,33 @@ public void TokenAwarePolicyReturnsLocalReplicasOnly() .Verifiable(); var policy = new TokenAwarePolicy(new DCAwareRoundRobinPolicy("dc1")); - policy.Initialize(clusterMock); + await policy.InitializeAsync(metadataMock).ConfigureAwait(false); //key for host :::1 and :::3 var k = new RoutingKey { RawRoutingKey = new byte[] { 1 } }; - var hosts = policy.NewQueryPlan(null, new SimpleStatement().SetRoutingKey(k)).ToList(); + var hosts = policy.NewQueryPlan(metadataMock.Cluster, null, new SimpleStatement().SetRoutingKey(k)).ToList(); //5 local hosts Assert.AreEqual(5, hosts.Count); //local replica first Assert.AreEqual(1, TestHelper.GetLastAddressByte(hosts[0])); - Mock.Get(clusterMock).Verify(); + Mock.Get(metadataMock).Verify(); //key for host :::2 and :::5 k = new RoutingKey { RawRoutingKey = new byte[] { 2 } }; n = 3; - hosts = policy.NewQueryPlan(null, new SimpleStatement().SetRoutingKey(k)).ToList(); + hosts = policy.NewQueryPlan(metadataMock.Cluster, null, new SimpleStatement().SetRoutingKey(k)).ToList(); Assert.AreEqual(5, hosts.Count); //local replicas first - CollectionAssert.AreEquivalent(new[] { 2, 5}, hosts.Take(2).Select(TestHelper.GetLastAddressByte)); + CollectionAssert.AreEquivalent(new[] { 2, 5 }, hosts.Take(2).Select(TestHelper.GetLastAddressByte)); //next should be local nodes Assert.AreEqual("dc1", hosts[2].Datacenter); Assert.AreEqual("dc1", hosts[3].Datacenter); Assert.AreEqual("dc1", hosts[4].Datacenter); - Mock.Get(clusterMock).Verify(); + Mock.Get(metadataMock.InternalMetadata).Verify(); } [Test] - public void TokenAwarePolicyRoundRobinsOnLocalReplicas() + public async Task TokenAwarePolicyRoundRobinsOnLocalReplicas() { var hostList = new List { @@ -552,8 +546,8 @@ public void TokenAwarePolicyRoundRobinsOnLocalReplicas() TestHelper.CreateHost("0.0.0.8", "dc2"), TestHelper.CreateHost("0.0.0.9", "dc1") }; - var clusterMock = CreateClusterMock(hostList); - Mock.Get(clusterMock) + var metadataMock = CreateMetadataMock(hostList); + Mock.Get(metadataMock.InternalMetadata) .Setup(c => c.GetReplicas(It.IsAny(), It.IsAny())) .Returns((keyspace, key) => { @@ -568,7 +562,7 @@ public void TokenAwarePolicyRoundRobinsOnLocalReplicas() .Verifiable(); var policy = new TokenAwarePolicy(new DCAwareRoundRobinPolicy("dc1")); - policy.Initialize(clusterMock); + await policy.InitializeAsync(metadataMock).ConfigureAwait(false); var firstHosts = new ConcurrentBag(); var k = new RoutingKey { RawRoutingKey = new byte[] { 1 } }; @@ -576,7 +570,7 @@ public void TokenAwarePolicyRoundRobinsOnLocalReplicas() const int times = 10000; Action action = () => { - var h = policy.NewQueryPlan(null, new SimpleStatement().SetRoutingKey(k)).First(); + var h = policy.NewQueryPlan(metadataMock.Cluster, null, new SimpleStatement().SetRoutingKey(k)).First(); firstHosts.Add(h); }; TestHelper.ParallelInvoke(action, times); @@ -587,11 +581,11 @@ public void TokenAwarePolicyRoundRobinsOnLocalReplicas() // Around half will to one and half to the other Assert.That(queryPlansWithHost1AsFirst / times, Is.GreaterThan(0.45).And.LessThan(0.55)); Assert.That(queryPlansWithHost2AsFirst / times, Is.GreaterThan(0.45).And.LessThan(0.55)); - Mock.Get(clusterMock).Verify(); + Mock.Get(metadataMock.InternalMetadata).Verify(); } [Test] - public void TokenAwarePolicyReturnsChildHostsIfNoRoutingKey() + public async Task TokenAwarePolicyReturnsChildHostsIfNoRoutingKey() { var hostList = new List { @@ -600,24 +594,24 @@ public void TokenAwarePolicyReturnsChildHostsIfNoRoutingKey() TestHelper.CreateHost("0.0.0.3", "dc2"), TestHelper.CreateHost("0.0.0.4", "dc2") }; - var clusterMock = CreateClusterMock(hostList); + var metadataMock = CreateMetadataMock(hostList); var policy = new TokenAwarePolicy(new DCAwareRoundRobinPolicy("dc1")); - policy.Initialize(clusterMock); + await policy.InitializeAsync(metadataMock).ConfigureAwait(false); //No routing key - var hosts = policy.NewQueryPlan(null, new SimpleStatement()).ToList(); + var hosts = policy.NewQueryPlan(metadataMock.Cluster, null, new SimpleStatement()).ToList(); //2 localhosts - Assert.AreEqual(2, hosts.Count(h => policy.Distance(h) == HostDistance.Local)); + Assert.AreEqual(2, hosts.Count(h => policy.Distance(metadataMock, h) == HostDistance.Local)); Assert.AreEqual("dc1", hosts[0].Datacenter); Assert.AreEqual("dc1", hosts[1].Datacenter); - Mock.Get(clusterMock).Verify(); + Mock.Get(metadataMock).Verify(); //No statement - hosts = policy.NewQueryPlan(null, null).ToList(); + hosts = policy.NewQueryPlan(metadataMock.Cluster, null, null).ToList(); //2 localhosts - Assert.AreEqual(2, hosts.Count(h => policy.Distance(h) == HostDistance.Local)); + Assert.AreEqual(2, hosts.Count(h => policy.Distance(metadataMock, h) == HostDistance.Local)); Assert.AreEqual("dc1", hosts[0].Datacenter); Assert.AreEqual("dc1", hosts[1].Datacenter); - Mock.Get(clusterMock).Verify(); + Mock.Get(metadataMock).Verify(); } [Test] @@ -668,24 +662,28 @@ public void IdempotenceAwareRetryPolicy_Should_Rethrow_OnWriteTimeout_With_Non_I Assert.AreEqual(0, testPolicy.UnavailableCounter); } - private IInternalCluster CreateClusterMock( - ICollection hostList = null, - string localDc = null, + private FakeMetadata CreateMetadataMock( + ICollection hostList = null, + string localDc = null, bool implicitContactPoint = false) { var config = new TestConfigurationBuilder { LocalDatacenter = localDc }.Build(); var cluster = Mock.Of(); + var internalMetadata = Mock.Of(); + var metadata = new FakeMetadata(cluster, internalMetadata); Mock.Get(cluster).SetupGet(c => c.Configuration).Returns(config); + Mock.Get(cluster).SetupGet(c => c.InternalMetadata).Returns(internalMetadata); + Mock.Get(cluster).SetupGet(c => c.Metadata).Returns(metadata); Mock.Get(cluster).SetupGet(c => c.ImplicitContactPoint).Returns(implicitContactPoint); if (hostList != null) { var cc = Mock.Of(); Mock.Get(cc).SetupGet(c => c.Host).Returns(hostList.First()); - Mock.Get(cluster).Setup(c => c.AllHosts()).Returns(hostList); - Mock.Get(cluster).Setup(c => c.GetControlConnection()).Returns(cc); - config.LocalDatacenterProvider.Initialize(cluster); + Mock.Get(internalMetadata).SetupGet(m => m.ControlConnection).Returns(cc); + Mock.Get(internalMetadata).SetupGet(m => m.AllHosts()).Returns(hostList); + config.LocalDatacenterProvider.Initialize(cluster, internalMetadata); } - return cluster; + return metadata; } /// diff --git a/src/Cassandra.Tests/ProtocolTests.cs b/src/Cassandra.Tests/ProtocolTests.cs index 6540f154c..54dbbb3e1 100644 --- a/src/Cassandra.Tests/ProtocolTests.cs +++ b/src/Cassandra.Tests/ProtocolTests.cs @@ -22,9 +22,6 @@ namespace Cassandra.Tests [TestFixture] public class ProtocolTests { - private readonly Configuration _config = new TestConfigurationBuilder { AllowBetaProtocolVersions = false }.Build(); - private readonly Configuration _configBeta = new TestConfigurationBuilder { AllowBetaProtocolVersions = true }.Build(); - [TestCase(ProtocolVersion.V4, ProtocolVersion.V5)] [TestCase(ProtocolVersion.V2, ProtocolVersion.V3)] [TestCase(ProtocolVersion.V3, ProtocolVersion.V4)] @@ -36,7 +33,7 @@ public class ProtocolTests public void GetLowerSupported_Should_NotSkipBetaVersions_When_AllowBetaProtocolVersionsTrue( ProtocolVersion version, ProtocolVersion initialVersion) { - Assert.AreEqual(version, initialVersion.GetLowerSupported(_configBeta)); + Assert.AreEqual(version, initialVersion.GetLowerSupported(true)); } [TestCase(ProtocolVersion.V4, ProtocolVersion.V5)] @@ -50,7 +47,7 @@ public void GetLowerSupported_Should_NotSkipBetaVersions_When_AllowBetaProtocolV public void GetLowerSupported_Should_SkipBetaVersions_When_AllowBetaProtocolVersionsFalse( ProtocolVersion version, ProtocolVersion initialVersion) { - Assert.AreEqual(version, initialVersion.GetLowerSupported(_config)); + Assert.AreEqual(version, initialVersion.GetLowerSupported(false)); } [TestCase(ProtocolVersion.V4, "4.0.0", "1.2.19")] @@ -65,7 +62,7 @@ public void GetLowerSupported_Should_SkipBetaVersions_When_AllowBetaProtocolVers public void GetHighestCommon_Should_Downgrade_To_Protocol_VX_With_Hosts(ProtocolVersion version, params string[] cassandraVersions) { - Assert.AreEqual(version, ProtocolVersion.MaxSupported.GetHighestCommon(_config, cassandraVersions.Select(GetHost))); + Assert.AreEqual(version, ProtocolVersion.MaxSupported.GetHighestCommon(false, cassandraVersions.Select(GetHost))); } @@ -81,7 +78,7 @@ public void GetHighestCommon_Should_Downgrade_To_Protocol_VX_With_Hosts(Protocol public void GetHighestCommon_Should_NotSkipBeta_When_AllowBetaVersionIsTrue(ProtocolVersion version, params string[] cassandraVersions) { - Assert.AreEqual(version, ProtocolVersion.MaxSupported.GetHighestCommon(_configBeta, cassandraVersions.Select(GetHost))); + Assert.AreEqual(version, ProtocolVersion.MaxSupported.GetHighestCommon(true, cassandraVersions.Select(GetHost))); } [TestCase(ProtocolVersion.V3, "6.0/3.10.2", "4.8.1/2.1.17", "5.1/3.0.13")] @@ -89,7 +86,7 @@ public void GetHighestCommon_Should_NotSkipBeta_When_AllowBetaVersionIsTrue(Prot public void GetHighestCommon_Should_Downgrade_To_Protocol_VX_With_Dse_Hosts(ProtocolVersion version, params string[] cassandraVersions) { - Assert.AreEqual(version, ProtocolVersion.MaxSupported.GetHighestCommon(_config, cassandraVersions.Select(GetHost))); + Assert.AreEqual(version, ProtocolVersion.MaxSupported.GetHighestCommon(false, cassandraVersions.Select(GetHost))); } [TestCase(ProtocolVersion.V4, "4.0.0")] @@ -102,7 +99,7 @@ public void GetHighestCommon_Should_Downgrade_To_Protocol_VX_With_Dse_Hosts(Prot public void GetHighestCommon_Should_Not_Downgrade_Protocol_With_Hosts(ProtocolVersion version, params string[] cassandraVersions) { - Assert.AreEqual(version, version.GetHighestCommon(_config, cassandraVersions.Select(GetHost))); + Assert.AreEqual(version, version.GetHighestCommon(false, cassandraVersions.Select(GetHost))); } [TestCase(ProtocolVersion.V5, "4.0.0")] @@ -115,7 +112,7 @@ public void GetHighestCommon_Should_Not_Downgrade_Protocol_With_Hosts(ProtocolVe public void GetHighestCommon_Should_Not_Downgrade_Protocol_With_Hosts_When_AllowBetaVersionIsTrue(ProtocolVersion version, params string[] cassandraVersions) { - Assert.AreEqual(version, version.GetHighestCommon(_configBeta, cassandraVersions.Select(GetHost))); + Assert.AreEqual(version, version.GetHighestCommon(true, cassandraVersions.Select(GetHost))); } [TestCase(ProtocolVersion.V4, "5.1.7/3.0.13", "5.0.13/3.0.11", "2.2.9")] @@ -125,7 +122,7 @@ public void GetHighestCommon_Should_Not_Downgrade_Protocol_With_Hosts_When_Allow public void GetHighestCommon_Should_Not_Downgrade_Protocol_With_Dse_Hosts(ProtocolVersion version, params string[] cassandraVersions) { - Assert.AreEqual(version, version.GetHighestCommon(_config, cassandraVersions.Select(GetHost))); + Assert.AreEqual(version, version.GetHighestCommon(false, cassandraVersions.Select(GetHost))); } private static Host GetHost(string cassandraVersion, int index) diff --git a/src/Cassandra.Tests/RequestExecutionTests.cs b/src/Cassandra.Tests/RequestExecutionTests.cs index 20be99187..bdc629fa3 100644 --- a/src/Cassandra.Tests/RequestExecutionTests.cs +++ b/src/Cassandra.Tests/RequestExecutionTests.cs @@ -18,6 +18,7 @@ using System.Collections.Generic; using System.Net; using Cassandra.Connections; +using Cassandra.Connections.Control; using Cassandra.ExecutionProfiles; using Cassandra.Observers; using Cassandra.Requests; @@ -40,6 +41,7 @@ public void Should_ThrowException_When_NoValidHosts(bool currentHostRetry) Mock.Get(requestHandlerFactory) .Setup(r => r.Create( It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), @@ -68,6 +70,7 @@ public void Should_NotThrowException_When_AValidHostIsObtained(bool currentHostR Mock.Get(requestHandlerFactory) .Setup(r => r.Create( It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), @@ -103,6 +106,7 @@ public void Should_SendRequest_When_AConnectionIsObtained(bool currentHostRetry) Mock.Get(requestHandlerFactory) .Setup(r => r.Create( It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), @@ -152,6 +156,7 @@ public void Should_RetryRequestToSameHost_When_ConnectionFailsAndRetryDecisionIs Mock.Get(requestHandlerFactory) .Setup(r => r.Create( It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), diff --git a/src/Cassandra.Tests/RequestHandlerMockTests.cs b/src/Cassandra.Tests/RequestHandlerMockTests.cs index 70b169155..37d2741a2 100644 --- a/src/Cassandra.Tests/RequestHandlerMockTests.cs +++ b/src/Cassandra.Tests/RequestHandlerMockTests.cs @@ -18,6 +18,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Net; +using Cassandra.Connections.Control; using Cassandra.Metrics; using Cassandra.Metrics.Internal; using Cassandra.Metrics.Providers.Null; @@ -42,7 +43,14 @@ private static IInternalSession GetMockInternalSession() var clusterMock = new Mock(); sessionMock.Setup(x => x.InternalCluster).Returns(clusterMock.Object); sessionMock.Setup(x => x.ObserverFactory) - .Returns(new MetricsObserverFactory(new MetricsManager(new NullDriverMetricsProvider(), new DriverMetricsOptions(), true, "s1"))); + .Returns( + new MetricsObserverFactory( + new MetricsManager( + sessionMock.Object, + new NullDriverMetricsProvider(), + new DriverMetricsOptions(), + true, + "s1"))); return sessionMock.Object; } private static Configuration GetConfig(ILoadBalancingPolicy lbp) @@ -67,6 +75,7 @@ private static Configuration GetConfig(ILoadBalancingPolicy lbp) public void Should_ThrowNoHostAvailableException_When_QueryPlanMoveNextReturnsFalse() { var sessionMock = GetMockInternalSession(); + var metadata = Mock.Of(); var lbpMock = Mock.Of(); Mock.Get(sessionMock).SetupGet(m => m.Cluster.Configuration).Returns(RequestHandlerMockTests.GetConfig(lbpMock)); var enumerable = Mock.Of>(); @@ -75,11 +84,11 @@ public void Should_ThrowNoHostAvailableException_When_QueryPlanMoveNextReturnsFa Mock.Get(enumerator).Setup(m => m.MoveNext()).Returns(false); Mock.Get(enumerable).Setup(m => m.GetEnumerator()).Returns(enumerator); Mock.Get(lbpMock) - .Setup(m => m.NewQueryPlan(It.IsAny(), It.IsAny())) + .Setup(m => m.NewQueryPlan(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(enumerable); var triedHosts = new Dictionary(); - var sut = new RequestHandler(sessionMock, new SerializerManager(ProtocolVersion.V4).GetCurrentSerializer()); + var sut = new RequestHandler(sessionMock, metadata, new SerializerManager(ProtocolVersion.V4).GetCurrentSerializer()); Assert.Throws(() => sut.GetNextValidHost(triedHosts)); } @@ -87,6 +96,7 @@ public void Should_ThrowNoHostAvailableException_When_QueryPlanMoveNextReturnsFa public void Should_ThrowNoHostAvailableException_When_QueryPlanMoveNextReturnsTrueButCurrentReturnsNull() { var sessionMock = GetMockInternalSession(); + var metadata = Mock.Of(); var lbpMock = Mock.Of(); Mock.Get(sessionMock).SetupGet(m => m.Cluster.Configuration).Returns(RequestHandlerMockTests.GetConfig(lbpMock)); var enumerable = Mock.Of>(); @@ -96,11 +106,11 @@ public void Should_ThrowNoHostAvailableException_When_QueryPlanMoveNextReturnsTr Mock.Get(enumerator).SetupGet(m => m.Current).Returns((Host)null); Mock.Get(enumerable).Setup(m => m.GetEnumerator()).Returns(enumerator); Mock.Get(lbpMock) - .Setup(m => m.NewQueryPlan(It.IsAny(), It.IsAny())) + .Setup(m => m.NewQueryPlan(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(enumerable); var triedHosts = new Dictionary(); - var sut = new RequestHandler(sessionMock, new SerializerManager(ProtocolVersion.V4).GetCurrentSerializer()); + var sut = new RequestHandler(sessionMock, metadata, new SerializerManager(ProtocolVersion.V4).GetCurrentSerializer()); Assert.Throws(() => sut.GetNextValidHost(triedHosts)); } @@ -108,6 +118,7 @@ public void Should_ThrowNoHostAvailableException_When_QueryPlanMoveNextReturnsTr public void Should_ReturnHost_When_QueryPlanMoveNextReturnsTrueAndCurrentReturnsHost() { var sessionMock = GetMockInternalSession(); + var metadata = Mock.Of(); var lbpMock = Mock.Of(); Mock.Get(sessionMock).SetupGet(m => m.Cluster.Configuration).Returns(RequestHandlerMockTests.GetConfig(lbpMock)); var enumerable = Mock.Of>(); @@ -117,12 +128,12 @@ public void Should_ReturnHost_When_QueryPlanMoveNextReturnsTrueAndCurrentReturns Mock.Get(enumerator).SetupGet(m => m.Current).Returns(host); Mock.Get(enumerable).Setup(m => m.GetEnumerator()).Returns(enumerator); Mock.Get(lbpMock) - .Setup(m => m.NewQueryPlan(It.IsAny(), It.IsAny())) + .Setup(m => m.NewQueryPlan(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(enumerable); - Mock.Get(lbpMock).Setup(m => m.Distance(host)).Returns(HostDistance.Local); + Mock.Get(lbpMock).Setup(m => m.Distance(It.IsAny(), host)).Returns(HostDistance.Local); var triedHosts = new Dictionary(); - var sut = new RequestHandler(sessionMock, new SerializerManager(ProtocolVersion.V4).GetCurrentSerializer()); + var sut = new RequestHandler(sessionMock, metadata, new SerializerManager(ProtocolVersion.V4).GetCurrentSerializer()); var validHost = sut.GetNextValidHost(triedHosts); Assert.NotNull(validHost); Assert.AreEqual(host, validHost.Host); diff --git a/src/Cassandra.Tests/Requests/FakeRequestHandlerFactory.cs b/src/Cassandra.Tests/Requests/FakeRequestHandlerFactory.cs index 26ec65020..306860647 100644 --- a/src/Cassandra.Tests/Requests/FakeRequestHandlerFactory.cs +++ b/src/Cassandra.Tests/Requests/FakeRequestHandlerFactory.cs @@ -16,10 +16,12 @@ using System; using System.Threading.Tasks; +using Cassandra.Connections.Control; using Cassandra.ExecutionProfiles; using Cassandra.Requests; using Cassandra.Serialization; using Cassandra.SessionManagement; + using Moq; namespace Cassandra.Tests.Requests @@ -28,24 +30,35 @@ internal class FakeRequestHandlerFactory : IRequestHandlerFactory { private readonly Action _executeCallback; private readonly Func _rs; - + public FakeRequestHandlerFactory(Action executeCallback, Func rs = null) { _executeCallback = executeCallback; _rs = rs ?? (stmt => new RowSet()); } - public IRequestHandler Create(IInternalSession session, ISerializer serializer, IRequest request, IStatement statement, IRequestOptions options) + public IRequestHandler Create( + IInternalSession session, + IInternalMetadata internalMetadata, + ISerializer serializer, + IRequest request, + IStatement statement, + IRequestOptions options) { return CreateMockHandler(statement); } - public IRequestHandler Create(IInternalSession session, ISerializer serializer, IStatement statement, IRequestOptions options) + public IRequestHandler Create( + IInternalSession session, + IInternalMetadata internalMetadata, + ISerializer serializer, + IStatement statement, + IRequestOptions options) { return CreateMockHandler(statement); } - public IRequestHandler Create(IInternalSession session, ISerializer serializer) + public IRequestHandler Create(IInternalSession session, IInternalMetadata internalMetadata, ISerializer serializer) { return CreateMockHandler(); } @@ -68,7 +81,7 @@ private IRequestHandler CreateMockHandler(IStatement statement = null) return handler; } - + private static Task TaskOf(T value) { var tcs = new TaskCompletionSource(); diff --git a/src/Cassandra.Tests/Requests/PrepareHandlerTests.cs b/src/Cassandra.Tests/Requests/PrepareHandlerTests.cs index f1fc60888..26ae01fe0 100644 --- a/src/Cassandra.Tests/Requests/PrepareHandlerTests.cs +++ b/src/Cassandra.Tests/Requests/PrepareHandlerTests.cs @@ -27,6 +27,7 @@ using Cassandra.Responses; using Cassandra.Serialization; using Cassandra.SessionManagement; +using Cassandra.Tasks; using Cassandra.Tests.Connections.TestHelpers; using Moq; @@ -85,7 +86,7 @@ public async Task Should_NotSendRequestToSecondHost_When_SecondHostDoesntHavePoo var distanceCount = Interlocked.Read(ref lbpCluster.DistanceCount); var request = new PrepareRequest(_serializer, "TEST", null, null); - await mockResult.PrepareHandler.Prepare( + await mockResult.PrepareHandler.PrepareAsync( request, mockResult.Session, queryPlan.GetEnumerator()).ConfigureAwait(false); @@ -156,7 +157,7 @@ public async Task Should_NotSendRequestToSecondHost_When_SecondHostPoolDoesNotHa var distanceCount = Interlocked.Read(ref lbpCluster.DistanceCount); var request = new PrepareRequest(_serializer, "TEST", null, null); - await mockResult.PrepareHandler.Prepare( + await mockResult.PrepareHandler.PrepareAsync( request, mockResult.Session, queryPlan.GetEnumerator()).ConfigureAwait(false); @@ -228,7 +229,7 @@ public async Task Should_SendRequestToAllHosts_When_AllHostsHaveConnections() var distanceCount = Interlocked.Read(ref lbpCluster.DistanceCount); var request = new PrepareRequest(_serializer, "TEST", null, null); - await mockResult.PrepareHandler.Prepare( + await mockResult.PrepareHandler.PrepareAsync( request, mockResult.Session, queryPlan.GetEnumerator()).ConfigureAwait(false); @@ -299,7 +300,7 @@ public async Task Should_SendRequestToAllHosts_When_AllHostsHaveConnectionsButFi var distanceCount = Interlocked.Read(ref lbpCluster.DistanceCount); var request = new PrepareRequest(_serializer, "TEST", null, null); - await mockResult.PrepareHandler.Prepare( + await mockResult.PrepareHandler.PrepareAsync( request, mockResult.Session, queryPlan.GetEnumerator()).ConfigureAwait(false); @@ -370,7 +371,7 @@ public async Task Should_SendRequestToAllHosts_When_AllHostsHaveConnectionsButFi var distanceCount = Interlocked.Read(ref lbpCluster.DistanceCount); var request = new PrepareRequest(_serializer, "TEST", null, null); - await mockResult.PrepareHandler.Prepare( + await mockResult.PrepareHandler.PrepareAsync( request, mockResult.Session, queryPlan.GetEnumerator()).ConfigureAwait(false); @@ -442,7 +443,7 @@ public async Task Should_SendRequestToFirstHostOnly_When_PrepareOnAllHostsIsFals var distanceCount = Interlocked.Read(ref lbpCluster.DistanceCount); var request = new PrepareRequest(_serializer, "TEST", null, null); - await mockResult.PrepareHandler.Prepare( + await mockResult.PrepareHandler.PrepareAsync( request, mockResult.Session, queryPlan.GetEnumerator()).ConfigureAwait(false); @@ -500,7 +501,7 @@ private PrepareHandlerMockResult BuildPrepareHandler(Action NewQueryPlan(string keyspace, IStatement query) + public IEnumerable NewQueryPlan(ICluster cluster, string keyspace, IStatement query) { Interlocked.Increment(ref NewQueryPlanCount); throw new NotImplementedException(); diff --git a/src/Cassandra.Tests/SchemaParserTests.cs b/src/Cassandra.Tests/SchemaParserTests.cs index 6f0ea484a..6474b9782 100644 --- a/src/Cassandra.Tests/SchemaParserTests.cs +++ b/src/Cassandra.Tests/SchemaParserTests.cs @@ -19,10 +19,15 @@ using System.Linq; using System.Net; using System.Threading.Tasks; + using Cassandra.Connections.Control; +using Cassandra.SessionManagement; using Cassandra.Tasks; + using Moq; + using NUnit.Framework; + using SortOrder = Cassandra.DataCollectionMetadata.SortOrder; namespace Cassandra.Tests @@ -32,24 +37,18 @@ public class SchemaParserTests { private static SchemaParserV1 GetV1Instance(IControlConnection cc) { - var metadata = new Metadata(new Configuration()) - { - ControlConnection = cc - }; + var metadata = new InternalMetadata(cc, new Configuration()); metadata.SetCassandraVersion(new Version(2, 0)); return new SchemaParserV1(metadata); } private static SchemaParserV2 GetV2Instance(IControlConnection cc, Func> udtResolver = null) { - var metadata = new Metadata(new Configuration()) - { - ControlConnection = cc - }; + var metadata = new InternalMetadata(cc, new Configuration()); metadata.SetCassandraVersion(new Version(3, 0)); return new SchemaParserV2(metadata, udtResolver); } - + /// /// Helper method to get a QueryTrace instance.. /// @@ -57,7 +56,7 @@ private static QueryTrace GetQueryTrace() { var clusterMock = new Mock(); clusterMock.Setup(c => c.Configuration).Returns(new Configuration()); - var sessionMock = new Mock(MockBehavior.Strict); + var sessionMock = new Mock(MockBehavior.Strict); sessionMock.Setup(s => s.Cluster).Returns(clusterMock.Object); return new QueryTrace(Guid.NewGuid(), sessionMock.Object); } @@ -96,7 +95,7 @@ public void SchemaParserV1_GetKeyspace_Should_Retrieve_And_Parse_Keyspace_With_S Assert.AreEqual(ksName, ks.Name); Assert.AreEqual(true, ks.DurableWrites); Assert.AreEqual("Simple", ks.StrategyClass); - CollectionAssert.AreEqual(new Dictionary {{"replication_factor", 4}}, ks.Replication); + CollectionAssert.AreEqual(new Dictionary { { "replication_factor", 4 } }, ks.Replication); } [Test] @@ -135,7 +134,7 @@ public void SchemaParserV1_GetTable_Should_Parse_2_0_Table_With_Compact_Storage( {"type","Standard"}, {"value_alias",null} }); - var columnRows = new [] { + var columnRows = new[] { new Dictionary{{"keyspace_name","ks_tbl_meta"},{"columnfamily_name","tbl1"},{"column_name","id" },{"component_index",null},{"index_name",null},{"index_options",null},{"index_type",null},{"type","partition_key"},{"validator","org.apache.cassandra.db.marshal.UUIDType"}}, new Dictionary{{"keyspace_name","ks_tbl_meta"},{"columnfamily_name","tbl1"},{"column_name","text1"},{"component_index",null},{"index_name",null},{"index_options",null},{"index_type",null},{"type","regular" },{"validator","org.apache.cassandra.db.marshal.UTF8Type"}}, new Dictionary{{"keyspace_name","ks_tbl_meta"},{"columnfamily_name","tbl1"},{"column_name","text2"},{"component_index",null},{"index_name",null},{"index_options",null},{"index_type",null},{"type","regular" },{"validator","org.apache.cassandra.db.marshal.UTF8Type"}} @@ -247,14 +246,14 @@ public void SchemaParserV1_GetTable_Should_Parse_2_0_Table_With_StaticColumn() [Test] public void SchemaParserV1_GetTable_Should_Parse_1_2_Table_With_Partition_And_Clustering_Keys() { - var tableRow = TestHelper.CreateRow(new Dictionary - { - {"keyspace_name", "ks_tbl_meta"}, {"columnfamily_name", "tbl1"}, {"bloom_filter_fp_chance", 0.01}, {"caching", "KEYS_ONLY"}, + var tableRow = TestHelper.CreateRow(new Dictionary + { + {"keyspace_name", "ks_tbl_meta"}, {"columnfamily_name", "tbl1"}, {"bloom_filter_fp_chance", 0.01}, {"caching", "KEYS_ONLY"}, {"column_aliases", "[\"zck\"]"}, {"comment", ""}, {"compaction_strategy_class", "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy"}, {"compaction_strategy_options", "{}"}, {"comparator", "org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.TimeUUIDType,org.apache.cassandra.db.marshal.UTF8Type)"}, {"compression_parameters", "{\"sstable_compression\":\"org.apache.cassandra.io.compress.SnappyCompressor\"}"}, {"default_validator", "org.apache.cassandra.db.marshal.BytesType"}, {"gc_grace_seconds", 864000}, {"id", null}, {"key_alias", null}, {"key_aliases", "[\"pk1\",\"apk2\"]"}, {"key_validator", "org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.UUIDType,org.apache.cassandra.db.marshal.UTF8Type)"}, {"local_read_repair_chance", 0D}, {"max_compaction_threshold", 32}, {"min_compaction_threshold", 4}, {"populate_io_cache_on_flush", false}, {"read_repair_chance", 0.1}, {"replicate_on_write", true}, {"subcomparator", null}, {"type", "Standard"}, {"value_alias", null} }); - var columnRows = new [] + var columnRows = new[] { new Dictionary { {"keyspace_name", "ks_tbl_meta"}, {"columnfamily_name", "tbl1"}, {"column_name", "val2" }, {"component_index", 1}, {"index_name", null}, {"index_options", null}, {"index_type", null}, {"validator", "org.apache.cassandra.db.marshal.BytesType"}}, new Dictionary { {"keyspace_name", "ks_tbl_meta"}, {"columnfamily_name", "tbl1"}, {"column_name", "valz1"}, {"component_index", 1}, {"index_name", null}, {"index_options", null}, {"index_type", null}, {"validator", "org.apache.cassandra.db.marshal.Int32Type"}} @@ -636,13 +635,13 @@ public void SchemaParserV2_GetTable_Should_Parse_3_0_Table_With_SecondaryIndexes .Returns(() => TestHelper.DelayedTask(columnRows)); queryProviderMock .Setup(cc => cc.QueryAsync(It.IsRegex("system_schema\\.indexes.*ks1"), It.IsAny())) - .Returns(() => TestHelper.DelayedTask>(new[] {indexRow})); + .Returns(() => TestHelper.DelayedTask>(new[] { indexRow })); var parser = GetV2Instance(queryProviderMock.Object); var ks = TaskHelper.WaitToComplete(parser.GetKeyspaceAsync("ks1")); Assert.NotNull(ks); var table = ks.GetTableMetadata("tbl4"); Assert.False(table.Options.IsCompactStorage); - CollectionAssert.AreEquivalent(new[] { "pk", "ck", "val"}, table.TableColumns.Select(c => c.Name)); + CollectionAssert.AreEquivalent(new[] { "pk", "ck", "val" }, table.TableColumns.Select(c => c.Name)); CollectionAssert.AreEqual(new[] { "pk" }, table.PartitionKeys.Select(c => c.Name)); CollectionAssert.AreEqual(new[] { "ck" }, table.ClusteringKeys.Select(c => c.Item1.Name)); CollectionAssert.AreEqual(new[] { SortOrder.Descending }, table.ClusteringKeys.Select(c => c.Item2)); @@ -672,7 +671,7 @@ public void SchemaParserV2_GetTable_Should_Propagate_Exceptions_From_Query_Provi //This will cause the task to be faulted queryProviderMock .Setup(cc => cc.QueryAsync(It.IsRegex("system_schema\\.columns.*ks1"), It.IsAny())) - .Returns(() => TaskHelper.FromException>(new NoHostAvailableException(new Dictionary()))); + .Returns(() => TaskHelper.FromException>(new NoHostAvailableException(new Dictionary()))); queryProviderMock .Setup(cc => cc.QueryAsync(It.IsRegex("system_schema\\.indexes.*ks1"), It.IsAny())) .Returns(() => TestHelper.DelayedTask(Enumerable.Empty())); @@ -784,7 +783,7 @@ public void SchemaParser_GetQueryTrace_Should_Try_Multiple_Times_To_Get_The_Trac {"parameters", null}, {"started_at", DateTimeOffset.Now} }); - return TestHelper.DelayedTask>(new[] {sessionRow}); + return TestHelper.DelayedTask>(new[] { sessionRow }); }); queryProviderMock .Setup(cc => cc.QueryAsync(It.IsRegex("system_traces\\.events"), It.IsAny())) diff --git a/src/Cassandra.Tests/SessionTests.cs b/src/Cassandra.Tests/SessionTests.cs index 4657b03ae..222026e4e 100644 --- a/src/Cassandra.Tests/SessionTests.cs +++ b/src/Cassandra.Tests/SessionTests.cs @@ -34,8 +34,11 @@ public void Should_GenerateNewSessionId_When_SessionIsCreated() var sessionNames = new ConcurrentQueue(); var sessionFactoryMock = Mock.Of(); Mock.Get(sessionFactoryMock).Setup(s => - s.CreateSessionAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .ReturnsAsync(Mock.Of()) + s.CreateSession( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(Mock.Of()) .Callback((c, ks, serializer, name) => { sessionNames.Enqueue(name); }); var config = new TestConfigurationBuilder diff --git a/src/Cassandra.Tests/StatementTests.cs b/src/Cassandra.Tests/StatementTests.cs index 69a99142b..ec6769d7e 100644 --- a/src/Cassandra.Tests/StatementTests.cs +++ b/src/Cassandra.Tests/StatementTests.cs @@ -17,6 +17,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; +using Cassandra.Connections.Control; using Cassandra.Requests; using Cassandra.Serialization; using Cassandra.SessionManagement; @@ -325,25 +327,27 @@ public void BatchStatement_Should_Use_Routing_Key_Of_First_Statement_With_Statem } [Test] - public void BatchStatement_Should_UseRoutingKeyAndKeyspaceOfFirstStatement_When_TokenAwareLbpIsUsed() + public async Task BatchStatement_Should_UseRoutingKeyAndKeyspaceOfFirstStatement_When_TokenAwareLbpIsUsed() { var rawRoutingKey = new byte[] {1, 2, 3, 4}; var lbp = new TokenAwarePolicy(new ClusterTests.FakeLoadBalancingPolicy()); - var clusterMock = Mock.Of(); - Mock.Get(clusterMock).Setup(c => c.GetReplicas(It.IsAny(), It.IsAny())) + var metadataMock = Mock.Of(); + Mock.Get(metadataMock).Setup(c => c.GetReplicas(It.IsAny(), It.IsAny())) .Returns(new List()); - Mock.Get(clusterMock).Setup(c => c.AllHosts()) + Mock.Get(metadataMock).Setup(c => c.AllHosts()) .Returns(new List()); - lbp.Initialize(clusterMock); + var clusterMock = Mock.Of(); + Mock.Get(clusterMock).SetupGet(c => c.Metadata).Returns(metadataMock); + await lbp.InitializeAsync(metadataMock).ConfigureAwait(false); var s1Mock = new Mock(MockBehavior.Loose); s1Mock.Setup(s => s.RoutingKey).Returns(new RoutingKey(rawRoutingKey)); s1Mock.Setup(s => s.Keyspace).Returns("ks1"); var batch = new BatchStatement().Add(s1Mock.Object); - var _ = lbp.NewQueryPlan("ks2", batch).ToList(); + var _ = lbp.NewQueryPlan(clusterMock, "ks2", batch).ToList(); - Mock.Get(clusterMock).Verify(c => c.GetReplicas("ks1", rawRoutingKey), Times.Once); + Mock.Get(metadataMock).Verify(c => c.GetReplicas("ks1", rawRoutingKey), Times.Once); } } } diff --git a/src/Cassandra.Tests/TestConfigurationBuilder.cs b/src/Cassandra.Tests/TestConfigurationBuilder.cs index d7daed61c..86c09a28e 100644 --- a/src/Cassandra.Tests/TestConfigurationBuilder.cs +++ b/src/Cassandra.Tests/TestConfigurationBuilder.cs @@ -98,6 +98,8 @@ internal class TestConfigurationBuilder public string LocalDatacenter { get; set; } + public long? InitializationTimeoutMs { get; set; } + public IContactPointParser ContactPointParser { get; set; } public ILocalDatacenterProvider LocalDatacenterProvider { get; set; } = new LocalDatacenterProvider(); @@ -106,6 +108,8 @@ internal class TestConfigurationBuilder public IDnsResolver DnsResolver { get; set; } + public ISerializerManager SerializerManager { get; set; } + public IMetadataRequestHandler MetadataRequestHandler { get; set; } = new MetadataRequestHandler(); public ITopologyRefresherFactory TopologyRefresherFactory { get; set; } = new TopologyRefresherFactory(); @@ -177,7 +181,9 @@ public Configuration Build() supportedOptionsInitializerFactory: SupportedOptionsInitializerFactory, protocolVersionNegotiator: ProtocolVersionNegotiator, serverEventsSubscriber: ServerEventsSubscriber, - localDatacenterProvider: LocalDatacenterProvider); + localDatacenterProvider: LocalDatacenterProvider, + serializerManager: SerializerManager, + initializationTimeoutMs: InitializationTimeoutMs); } } } \ No newline at end of file diff --git a/src/Cassandra.Tests/TestHelper.cs b/src/Cassandra.Tests/TestHelper.cs index 08db9850e..d2b7e934c 100644 --- a/src/Cassandra.Tests/TestHelper.cs +++ b/src/Cassandra.Tests/TestHelper.cs @@ -684,6 +684,10 @@ public void Error(string message, Exception ex = null) { } + public void Error(Exception ex, string message, params object[] args) + { + } + public void Error(string message, params object[] args) { } @@ -728,20 +732,20 @@ public OrderedLoadBalancingPolicy() _childPolicy = new RoundRobinPolicy(); } - public void Initialize(ICluster cluster) + public Task InitializeAsync(IMetadataSnapshotProvider metadata) { - _hosts = cluster.AllHosts(); - _childPolicy.Initialize(cluster); + _hosts = metadata.AllHostsSnapshot(); + return _childPolicy.InitializeAsync(metadata); } - public HostDistance Distance(Host host) + public HostDistance Distance(IMetadataSnapshotProvider metadata, Host host) { - return _childPolicy.Distance(host); + return _childPolicy.Distance(metadata, host); } - public IEnumerable NewQueryPlan(string keyspace, IStatement query) + public IEnumerable NewQueryPlan(ICluster cluster, string keyspace, IStatement query) { - return !_useRoundRobin ? _hosts : _childPolicy.NewQueryPlan(keyspace, query); + return !_useRoundRobin ? _hosts : _childPolicy.NewQueryPlan(cluster, keyspace, query); } } @@ -751,31 +755,32 @@ public IEnumerable NewQueryPlan(string keyspace, IStatement query) /// internal class CustomLoadBalancingPolicy : ILoadBalancingPolicy { - private ICluster _cluster; - private readonly Func _distanceHandler; + private volatile IMetadataSnapshotProvider _metadata; + private readonly Func _distanceHandler; private readonly Func> _queryPlanHandler; public CustomLoadBalancingPolicy( Func> queryPlanHandler = null, - Func distanceHandler = null) + Func distanceHandler = null) { - _queryPlanHandler = queryPlanHandler ?? ((cluster, ks, statement) => cluster.AllHosts()); + _queryPlanHandler = queryPlanHandler ?? ((cluster, ks, statement) => cluster.Metadata.AllHostsSnapshot()); _distanceHandler = distanceHandler ?? ((_, __) => HostDistance.Local); } - public void Initialize(ICluster cluster) + public Task InitializeAsync(IMetadataSnapshotProvider metadata) { - _cluster = cluster; + _metadata = metadata; + return TaskHelper.Completed; } - public HostDistance Distance(Host host) + public HostDistance Distance(IMetadataSnapshotProvider metadata, Host host) { - return _distanceHandler(_cluster, host); + return _distanceHandler(metadata, host); } - public IEnumerable NewQueryPlan(string keyspace, IStatement query) + public IEnumerable NewQueryPlan(ICluster cluster, string keyspace, IStatement query) { - return _queryPlanHandler(_cluster, keyspace, query); + return _queryPlanHandler(cluster, keyspace, query); } } } diff --git a/src/Cassandra.Tests/TokenTests.cs b/src/Cassandra.Tests/TokenTests.cs index 9173b6a34..f88fdf8b4 100644 --- a/src/Cassandra.Tests/TokenTests.cs +++ b/src/Cassandra.Tests/TokenTests.cs @@ -417,7 +417,7 @@ public void Build_Should_OnlyCallOncePerReplicationConfiguration_When_MultipleKe [Test] [Repeat(1)] - public void Should_UpdateKeyspacesAndTokenMapCorrectly_When_MultipleThreadsCallingRefreshKeyspace() + public async Task Should_UpdateKeyspacesAndTokenMapCorrectly_When_MultipleThreadsCallingRefreshKeyspace() { var keyspaces = new ConcurrentDictionary(); @@ -439,31 +439,30 @@ public void Should_UpdateKeyspacesAndTokenMapCorrectly_When_MultipleThreadsCalli var schemaParser = new FakeSchemaParser(keyspaces); var config = new TestConfigurationBuilder { - ConnectionFactory = new FakeConnectionFactory() + ConnectionFactory = new FakeConnectionFactory(), + MetadataSyncOptions = new MetadataSyncOptions().SetRefreshSchemaDelayIncrement(20).SetMaxTotalRefreshSchemaDelay(100) }.Build(); - var metadata = new Metadata(config, schemaParser) {Partitioner = "Murmur3Partitioner"}; - metadata.ControlConnection = new ControlConnection( - Mock.Of(), - new ProtocolEventDebouncer(new TaskBasedTimerFactory(), TimeSpan.FromMilliseconds(20), TimeSpan.FromSeconds(100)), - ProtocolVersion.V3, + var internalMetadata = new InternalMetadata( + Mock.Of(), config, - metadata, new List { new IpLiteralContactPoint(IPAddress.Parse("127.0.0.1"), config.ProtocolOptions, config.ServerNameResolver) - }); - metadata.Hosts.Add(new IPEndPoint(IPAddress.Parse("192.168.0.1"), 9042)); - metadata.Hosts.Add(new IPEndPoint(IPAddress.Parse("192.168.0.2"), 9042)); - metadata.Hosts.Add(new IPEndPoint(IPAddress.Parse("192.168.0.3"), 9042)); - metadata.Hosts.Add(new IPEndPoint(IPAddress.Parse("192.168.0.4"), 9042)); - metadata.Hosts.Add(new IPEndPoint(IPAddress.Parse("192.168.0.5"), 9042)); - metadata.Hosts.Add(new IPEndPoint(IPAddress.Parse("192.168.0.6"), 9042)); - metadata.Hosts.Add(new IPEndPoint(IPAddress.Parse("192.168.0.7"), 9042)); - metadata.Hosts.Add(new IPEndPoint(IPAddress.Parse("192.168.0.8"), 9042)); - metadata.Hosts.Add(new IPEndPoint(IPAddress.Parse("192.168.0.9"), 9042)); - metadata.Hosts.Add(new IPEndPoint(IPAddress.Parse("192.168.0.10"), 9042)); + }, + schemaParser); + internalMetadata.SetPartitioner("Murmur3Partitioner"); + internalMetadata.Hosts.Add(new IPEndPoint(IPAddress.Parse("192.168.0.1"), 9042)); + internalMetadata.Hosts.Add(new IPEndPoint(IPAddress.Parse("192.168.0.2"), 9042)); + internalMetadata.Hosts.Add(new IPEndPoint(IPAddress.Parse("192.168.0.3"), 9042)); + internalMetadata.Hosts.Add(new IPEndPoint(IPAddress.Parse("192.168.0.4"), 9042)); + internalMetadata.Hosts.Add(new IPEndPoint(IPAddress.Parse("192.168.0.5"), 9042)); + internalMetadata.Hosts.Add(new IPEndPoint(IPAddress.Parse("192.168.0.6"), 9042)); + internalMetadata.Hosts.Add(new IPEndPoint(IPAddress.Parse("192.168.0.7"), 9042)); + internalMetadata.Hosts.Add(new IPEndPoint(IPAddress.Parse("192.168.0.8"), 9042)); + internalMetadata.Hosts.Add(new IPEndPoint(IPAddress.Parse("192.168.0.9"), 9042)); + internalMetadata.Hosts.Add(new IPEndPoint(IPAddress.Parse("192.168.0.10"), 9042)); var initialToken = 1; - foreach (var h in metadata.Hosts) + foreach (var h in internalMetadata.Hosts) { h.SetInfo(new TestHelper.DictionaryBasedRow(new Dictionary { @@ -474,8 +473,8 @@ public void Should_UpdateKeyspacesAndTokenMapCorrectly_When_MultipleThreadsCalli })); initialToken++; } - metadata.RebuildTokenMapAsync(false, true).GetAwaiter().GetResult(); - var expectedTokenMap = metadata.TokenToReplicasMap; + internalMetadata.RebuildTokenMapAsync(false, true).GetAwaiter().GetResult(); + var expectedTokenMap = internalMetadata.TokenToReplicasMap; Assert.NotNull(expectedTokenMap); var bag = new ConcurrentBag(); var tasks = new List(); @@ -483,13 +482,13 @@ public void Should_UpdateKeyspacesAndTokenMapCorrectly_When_MultipleThreadsCalli { var index = i; tasks.Add(Task.Factory.StartNew( - () => + async () => { for (var j = 0; j < 35; j++) { if (j % 10 == 0 && index % 2 == 0) { - metadata.RefreshSchemaAsync().GetAwaiter().GetResult(); + await internalMetadata.RefreshSchemaAsync().ConfigureAwait(false); } else if (j % 16 == 0) { @@ -497,8 +496,8 @@ public void Should_UpdateKeyspacesAndTokenMapCorrectly_When_MultipleThreadsCalli { if (keyspaces.TryRemove(ksName, out var ks)) { - metadata.RefreshSchemaAsync(ksName).GetAwaiter().GetResult(); - ks = metadata.GetKeyspace(ksName); + await internalMetadata.RefreshSchemaAsync(ksName).ConfigureAwait(false); + ks = await internalMetadata.GetKeyspaceAsync(ksName).ConfigureAwait(false); if (ks != null) { throw new Exception($"refresh for {ks.Name} returned non null after refresh single."); @@ -513,8 +512,8 @@ public void Should_UpdateKeyspacesAndTokenMapCorrectly_When_MultipleThreadsCalli { if (keyspaces.TryRemove(ksName, out var ks)) { - metadata.ControlConnection.HandleKeyspaceRefreshLaterAsync(ks.Name).GetAwaiter().GetResult(); - ks = metadata.GetKeyspace(ksName); + await internalMetadata.ControlConnection.HandleKeyspaceRefreshLaterAsync(ks.Name).ConfigureAwait(false); + ks = await internalMetadata.GetKeyspaceAsync(ksName).ConfigureAwait(false); if (ks != null) { throw new Exception($"refresh for {ks.Name} returned non null after remove."); @@ -530,8 +529,8 @@ public void Should_UpdateKeyspacesAndTokenMapCorrectly_When_MultipleThreadsCalli keyspaceName, ks, (s, keyspaceMetadata) => ks); - metadata.ControlConnection.HandleKeyspaceRefreshLaterAsync(ks.Name).GetAwaiter().GetResult(); - ks = metadata.GetKeyspace(ks.Name); + await internalMetadata.ControlConnection.HandleKeyspaceRefreshLaterAsync(ks.Name).ConfigureAwait(false); + ks = await internalMetadata.GetKeyspaceAsync(ks.Name).ConfigureAwait(false); if (ks == null) { throw new Exception($"refresh for {keyspaceName} returned null after add."); @@ -540,19 +539,21 @@ public void Should_UpdateKeyspacesAndTokenMapCorrectly_When_MultipleThreadsCalli } } }, - TaskCreationOptions.LongRunning | TaskCreationOptions.DenyChildAttach)); + TaskCreationOptions.LongRunning | TaskCreationOptions.DenyChildAttach).Unwrap()); } - Task.WaitAll(tasks.ToArray()); - AssertSameReplicas(keyspaces.Values, expectedTokenMap, metadata.TokenToReplicasMap); + await Task.WhenAll(tasks).ConfigureAwait(false); + AssertSameReplicas(keyspaces.Values, expectedTokenMap, internalMetadata.TokenToReplicasMap); } [Test] - public void RefreshSingleKeyspace_Should_BuildTokenMap_When_TokenMapIsNull() + public async Task RefreshSingleKeyspace_Should_BuildTokenMap_When_TokenMapIsNull() { var keyspaces = new ConcurrentDictionary(); keyspaces.GetOrAdd("ks1", FakeSchemaParserFactory.CreateSimpleKeyspace("ks1", 1)); var schemaParser = new FakeSchemaParser(keyspaces); - var metadata = new Metadata(new Configuration(), schemaParser) { Partitioner = "Murmur3Partitioner" }; + var metadata = new InternalMetadata( + Mock.Of(), new Configuration(), new List(), schemaParser); + metadata.SetPartitioner("Murmur3Partitioner"); metadata.Hosts.Add(new IPEndPoint(IPAddress.Parse("192.168.0.1"), 9042)); ; metadata.Hosts.First().SetInfo(new TestHelper.DictionaryBasedRow(new Dictionary { @@ -563,11 +564,12 @@ public void RefreshSingleKeyspace_Should_BuildTokenMap_When_TokenMapIsNull() })); Assert.IsNull(metadata.TokenToReplicasMap); - metadata.RefreshSingleKeyspace("ks1").GetAwaiter().GetResult(); + await metadata.RefreshSingleKeyspaceAsync("ks1").ConfigureAwait(false); Assert.NotNull(metadata.TokenToReplicasMap); } - private void AssertSameReplicas(IEnumerable keyspaces, IReadOnlyTokenMap expectedTokenMap, IReadOnlyTokenMap actualTokenMap) + private void AssertSameReplicas( + IEnumerable keyspaces, IReadOnlyTokenMap expectedTokenMap, IReadOnlyTokenMap actualTokenMap) { foreach (var k in keyspaces) { @@ -598,7 +600,8 @@ private void AssertSameReplicas(IEnumerable keyspaces, IReadOn } } - private void AssertOnlyOneStrategyIsCalled(IList strategies, params int[] equalStrategiesIndexes) + private void AssertOnlyOneStrategyIsCalled( + IList strategies, params int[] equalStrategiesIndexes) { var sameStrategies = equalStrategiesIndexes.Select(t => strategies[t]).ToList(); Assert.AreEqual(1, sameStrategies.Count(strategy => strategy.Calls == 1)); diff --git a/src/Cassandra.sln b/src/Cassandra.sln index 36e9ff758..8fed67441 100644 --- a/src/Cassandra.sln +++ b/src/Cassandra.sln @@ -15,12 +15,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cassandra.AppMetrics", "Extensions\Cassandra.AppMetrics\Cassandra.AppMetrics.csproj", "{93FA7B24-7B58-42E5-89E6-7F8023B03BA3}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B18A61A0-8552-476C-AE41-ED8F39396754}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - stylecop.json = stylecop.json - EndProjectSection -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/src/Cassandra/Builder.cs b/src/Cassandra/Builder.cs index b931d3ff1..f9623c4fb 100644 --- a/src/Cassandra/Builder.cs +++ b/src/Cassandra/Builder.cs @@ -81,6 +81,7 @@ public class Builder : IInitializer private bool? _keepContactPointsUnresolved; private bool? _allowBetaProtocolVersions; private string _localDatacenter; + private long? _initializationTimeoutMs; public Builder() { @@ -204,7 +205,8 @@ public Configuration GetConfiguration() typeSerializerDefinitions, _keepContactPointsUnresolved, _allowBetaProtocolVersions, - _localDatacenter); + _localDatacenter, + _initializationTimeoutMs); return config; } @@ -1050,7 +1052,7 @@ public Builder WithExecutionProfiles(Action profileOpt /// specifies what is the default for each option. /// /// - /// In case you disable Metadata synchronization, please ensure you invoke in order to keep the token metadata up to date + /// In case you disable Metadata synchronization, please ensure you invoke (accessible via ) in order to keep the token metadata up to date /// otherwise you will not be getting everything you can out of token aware routing, i.e. , which is enabled by the default. /// /// @@ -1061,7 +1063,7 @@ public Builder WithExecutionProfiles(Action profileOpt /// /// Token metadata will not be computed and stored. /// This means that token aware routing (, enabled by default) will only work correctly - /// if you keep the token metadata up to date using the method. + /// if you keep the token metadata up to date using the method (accessible via ). /// If you wish to go this route of manually refreshing the metadata then /// it's recommended to refresh only the keyspaces that this application will use, by passing the keyspace parameter. /// @@ -1182,6 +1184,12 @@ public Builder WithMonitorReporting(bool enabled) return WithMonitorReporting(_monitorReportingOptions.SetMonitorReportingEnabled(enabled)); } + public Builder WithInitializationTimeout(long milliseconds) + { + _initializationTimeoutMs = milliseconds; + return this; + } + /// /// Configures options related to Monitor Reporting for the new cluster. /// By default, Monitor Reporting is enabled server types and versions that support it. diff --git a/src/Cassandra/Cluster.cs b/src/Cassandra/Cluster.cs index 6ce58e10b..711ae96d4 100644 --- a/src/Cassandra/Cluster.cs +++ b/src/Cassandra/Cluster.cs @@ -28,9 +28,7 @@ using Cassandra.Connections; using Cassandra.Connections.Control; using Cassandra.Helpers; -using Cassandra.ProtocolEvents; using Cassandra.Requests; -using Cassandra.Serialization; using Cassandra.SessionManagement; using Cassandra.Tasks; @@ -39,36 +37,32 @@ namespace Cassandra /// public class Cluster : IInternalCluster { + private const int Disposed = 10; + private const int Initialized = 5; + private const int Initializing = 1; + private static readonly IPEndPoint DefaultContactPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9042); - private static ProtocolVersion _maxProtocolVersion = ProtocolVersion.MaxSupported; internal static readonly Logger Logger = new Logger(typeof(Cluster)); private readonly CopyOnWriteList _connectedSessions = new CopyOnWriteList(); - private readonly IControlConnection _controlConnection; - private volatile bool _initialized; - private volatile Exception _initException; - private readonly SemaphoreSlim _initLock = new SemaphoreSlim(1, 1); - private long _sessionCounter = -1; private readonly bool _implicitContactPoint = false; + private volatile TaskCompletionSource _initTaskCompletionSource + = new TaskCompletionSource(); + private readonly SemaphoreSlim _sessionCreateLock = new SemaphoreSlim(1, 1); + private long _sessionCounter = -1; - private readonly Metadata _metadata; - private readonly IProtocolEventDebouncer _protocolEventDebouncer; - private IReadOnlyList _loadBalancingPolicies; - - /// - public event Action HostAdded; - - /// - public event Action HostRemoved; + private readonly IInternalMetadata _internalMetadata; + private readonly Metadata _lazyMetadata; + private readonly IClusterInitializer _clusterInitializer; + private readonly IReadOnlyList _loadBalancingPolicies; + private readonly IReadOnlyList _speculativeExecutionPolicies; internal IInternalCluster InternalRef => this; - /// - IControlConnection IInternalCluster.GetControlConnection() - { - return _controlConnection; - } + IInternalMetadata IInternalCluster.InternalMetadata => _internalMetadata; + IClusterInitializer IInternalCluster.ClusterInitializer => _clusterInitializer; + /// ConcurrentDictionary IInternalCluster.PreparedQueries { get; } = new ConcurrentDictionary(new ByteArrayComparer()); @@ -110,62 +104,31 @@ public static Builder Builder() return new Builder(); } - /// - /// Gets or sets the maximum protocol version used by this driver. - /// - /// While property value is maintained for backward-compatibility, - /// use to set the maximum protocol version used by the driver. - /// - /// - /// Protocol version used can not be higher than . - /// - /// - public static int MaxProtocolVersion - { - get { return (int)_maxProtocolVersion; } - set - { - if (value > (int)ProtocolVersion.MaxSupported) - { - // Ignore - return; - } - _maxProtocolVersion = (ProtocolVersion)value; - } - } - /// - public Configuration Configuration { get; private set; } + public Configuration Configuration { get; } /// // ReSharper disable once ConvertToAutoProperty, reviewed bool IInternalCluster.ImplicitContactPoint => _implicitContactPoint; /// - public Metadata Metadata - { - get - { - TaskHelper.WaitToComplete(Init()); - return _metadata; - } - } + public IMetadata Metadata => _lazyMetadata; private Cluster(IEnumerable contactPoints, Configuration configuration) { Configuration = configuration; - _metadata = new Metadata(configuration); - var protocolVersion = _maxProtocolVersion; - if (Configuration.ProtocolOptions.MaxProtocolVersionValue != null && - Configuration.ProtocolOptions.MaxProtocolVersionValue.Value.IsSupported(configuration)) + + // Collect all policies in collections + var loadBalancingPolicies = new HashSet(new ReferenceEqualityComparer()); + var speculativeExecutionPolicies = new HashSet(new ReferenceEqualityComparer()); + foreach (var options in Configuration.RequestOptions.Values) { - protocolVersion = Configuration.ProtocolOptions.MaxProtocolVersionValue.Value; + loadBalancingPolicies.Add(options.LoadBalancingPolicy); + speculativeExecutionPolicies.Add(options.SpeculativeExecutionPolicy); } - _protocolEventDebouncer = new ProtocolEventDebouncer( - configuration.TimerFactory, - TimeSpan.FromMilliseconds(configuration.MetadataSyncOptions.RefreshSchemaDelayIncrement), - TimeSpan.FromMilliseconds(configuration.MetadataSyncOptions.MaxTotalRefreshSchemaDelay)); + _loadBalancingPolicies = loadBalancingPolicies.ToList(); + _speculativeExecutionPolicies = speculativeExecutionPolicies.ToList(); var contactPointsList = contactPoints.ToList(); if (contactPointsList.Count == 0) @@ -176,158 +139,70 @@ private Cluster(IEnumerable contactPoints, Configuration configuration) } var parsedContactPoints = configuration.ContactPointParser.ParseContactPoints(contactPointsList); + + _internalMetadata = new InternalMetadata(this, configuration, parsedContactPoints); + _clusterInitializer = new ClusterInitializer(this, _internalMetadata); + _lazyMetadata = new Metadata(_clusterInitializer, _internalMetadata); - _controlConnection = configuration.ControlConnectionFactory.Create( - this, - _protocolEventDebouncer, - protocolVersion, - Configuration, - _metadata, - parsedContactPoints); - - _metadata.ControlConnection = _controlConnection; + _clusterInitializer.Initialize(); } - - /// - /// Initializes once (Thread-safe) the control connection and metadata associated with the Cluster instance - /// - private async Task Init() + + async Task IInternalCluster.PostInitializeAsync() { - if (_initialized) + _lazyMetadata.SetupEventForwarding(); + SetMetadataDependentOptions(); + + // initialize the local datacenter provider + Configuration.LocalDatacenterProvider.Initialize(this, _internalMetadata); + + // Initialize policies + foreach (var lbp in _loadBalancingPolicies) { - //It was already initialized - return; + await lbp.InitializeAsync(Metadata).ConfigureAwait(false); } - await _initLock.WaitAsync().ConfigureAwait(false); - try - { - if (_initialized) - { - //It was initialized when waiting on the lock - return; - } - if (_initException != null) - { - //There was an exception that is not possible to recover from - throw _initException; - } - Cluster.Logger.Info("Connecting to cluster using {0}", GetAssemblyInfo()); - try - { - // Collect all policies in collections - var loadBalancingPolicies = new HashSet(new ReferenceEqualityComparer()); - var speculativeExecutionPolicies = new HashSet(new ReferenceEqualityComparer()); - foreach (var options in Configuration.RequestOptions.Values) - { - loadBalancingPolicies.Add(options.LoadBalancingPolicy); - speculativeExecutionPolicies.Add(options.SpeculativeExecutionPolicy); - } - - _loadBalancingPolicies = loadBalancingPolicies.ToList(); - - // Only abort the async operations when at least twice the time for ConnectTimeout per host passed - var initialAbortTimeout = Configuration.SocketOptions.ConnectTimeoutMillis * 2 * _metadata.Hosts.Count; - initialAbortTimeout = Math.Max(initialAbortTimeout, Configuration.SocketOptions.MetadataAbortTimeout); - var initTask = _controlConnection.InitAsync(); - try - { - await initTask.WaitToCompleteAsync(initialAbortTimeout).ConfigureAwait(false); - } - catch (TimeoutException ex) - { - var newEx = new TimeoutException( - "Cluster initialization was aborted after timing out. This mechanism is put in place to" + - " avoid blocking the calling thread forever. This usually caused by a networking issue" + - " between the client driver instance and the cluster. You can increase this timeout via " + - "the SocketOptions.ConnectTimeoutMillis config setting. This can also be related to deadlocks " + - "caused by mixing synchronous and asynchronous code.", ex); - _initException = new InitFatalErrorException(newEx); - initTask.ContinueWith(t => - { - if (t.IsFaulted && t.Exception != null) - { - _initException = new InitFatalErrorException(t.Exception.InnerException); - } - }, TaskContinuationOptions.ExecuteSynchronously).Forget(); - throw newEx; - } - // initialize the local datacenter provider - Configuration.LocalDatacenterProvider.Initialize(this); - - // Initialize policies - foreach (var lbp in loadBalancingPolicies) - { - lbp.Initialize(this); - } - - foreach (var sep in speculativeExecutionPolicies) - { - sep.Initialize(this); - } + foreach (var sep in _speculativeExecutionPolicies) + { + await sep.InitializeAsync(Metadata).ConfigureAwait(false); + } - InitializeHostDistances(); + InitializeHostDistances(); - // Set metadata dependent options - SetMetadataDependentOptions(); - } - catch (NoHostAvailableException) - { - //No host available now, maybe later it can recover from - throw; - } - catch (TimeoutException) - { - throw; - } - catch (Exception ex) - { - //There was an error that the driver is not able to recover from - //Store the exception for the following times - _initException = new InitFatalErrorException(ex); - //Throw the actual exception for the first time - throw; - } - Cluster.Logger.Info("Cluster Connected using binary protocol version: [" + _controlConnection.Serializer.CurrentProtocolVersion + "]"); - _initialized = true; - _metadata.Hosts.Added += OnHostAdded; - _metadata.Hosts.Removed += OnHostRemoved; - _metadata.Hosts.Up += OnHostUp; - } - finally + _internalMetadata.Hosts.Up += OnHostUp; + } + + private void SetMetadataDependentOptions() + { + if (_internalMetadata.IsDbaas) { - _initLock.Release(); + Configuration.SetDefaultConsistencyLevel(ConsistencyLevel.LocalQuorum); } - - Cluster.Logger.Info("Cluster [" + Metadata.ClusterName + "] has been initialized."); - return; } private void InitializeHostDistances() { - foreach (var host in AllHosts()) + foreach (var host in _internalMetadata.AllHosts()) { InternalRef.RetrieveAndSetDistance(host); } } - - private static string GetAssemblyInfo() + + TimeSpan IInternalCluster.GetInitTimeout() { - var assembly = typeof(ISession).GetTypeInfo().Assembly; - var info = FileVersionInfo.GetVersionInfo(assembly.Location); - return $"{info.ProductName} v{info.FileVersion}"; - } + if (Configuration.InitializationTimeoutMs.HasValue) + { + return TimeSpan.FromMilliseconds(Configuration.InitializationTimeoutMs.Value); + } - IReadOnlyDictionary> IInternalCluster.GetResolvedEndpoints() - { - return _metadata.ResolvedContactPoints; + // Only abort the async operations when at least twice the time for ConnectTimeout per host passed + var initialAbortTimeoutMs = Configuration.SocketOptions.ConnectTimeoutMillis * 2 * _internalMetadata.Hosts.Count; + initialAbortTimeoutMs = Math.Max(initialAbortTimeoutMs, Configuration.SocketOptions.MetadataAbortTimeoutMs); + return TimeSpan.FromMilliseconds(initialAbortTimeoutMs); } - - /// - public ICollection AllHosts() + + IReadOnlyDictionary> IInternalCluster.GetResolvedEndpoints() { - //Do not connect at first - return _metadata.AllHosts(); + return _internalMetadata.ResolvedContactPoints; } /// @@ -361,13 +236,24 @@ public ISession Connect(string keyspace) /// Case-sensitive keyspace name to use public async Task ConnectAsync(string keyspace) { - await Init().ConfigureAwait(false); - var newSessionName = GetNewSessionName(); - var session = await Configuration.SessionFactory.CreateSessionAsync(this, keyspace, _controlConnection.Serializer, newSessionName).ConfigureAwait(false); - await session.Init().ConfigureAwait(false); - _connectedSessions.Add(session); - Cluster.Logger.Info("Session connected ({0})", session.GetHashCode()); - return session; + await _sessionCreateLock.WaitAsync().ConfigureAwait(false); + try + { + if (_clusterInitializer.IsDisposed) + { + throw new ObjectDisposedException("This cluster instance is disposed."); + } + + var newSessionName = GetNewSessionName(); + var session = + Configuration.SessionFactory.CreateSession(this, keyspace, newSessionName); + _connectedSessions.Add(session); + return session; + } + finally + { + _sessionCreateLock.Release(); + } } private string GetNewSessionName() @@ -390,14 +276,6 @@ private long GetAndIncrementSessionCounter() return newCounter < 0 ? Math.Abs(newCounter) : newCounter; } - private void SetMetadataDependentOptions() - { - if (_metadata.IsDbaas) - { - Configuration.SetDefaultConsistencyLevel(ConsistencyLevel.LocalQuorum); - } - } - /// /// Creates new session on this cluster, and sets it to default keyspace. /// If default keyspace does not exist then it will be created and session will be set to it. @@ -426,34 +304,6 @@ public void Dispose() Shutdown(); } - /// - public Host GetHost(IPEndPoint address) - { - return Metadata.GetHost(address); - } - - /// - public ICollection GetReplicas(byte[] partitionKey) - { - return Metadata.GetReplicas(partitionKey); - } - - /// - public ICollection GetReplicas(string keyspace, byte[] partitionKey) - { - return Metadata.GetReplicas(keyspace, partitionKey); - } - - private void OnHostRemoved(Host h) - { - HostRemoved?.Invoke(h); - } - - private void OnHostAdded(Host h) - { - HostAdded?.Invoke(h); - } - private async void OnHostUp(Host h) { try @@ -475,31 +325,30 @@ private async void OnHostUp(Host h) } /// - public bool RefreshSchema(string keyspace = null, string table = null) - { - return Metadata.RefreshSchema(keyspace, table); - } - - /// - public Task RefreshSchemaAsync(string keyspace = null, string table = null) + public void Shutdown(int timeoutMs = Timeout.Infinite) { - return Metadata.RefreshSchemaAsync(keyspace, table); + ShutdownAsync(timeoutMs).GetAwaiter().GetResult(); } /// - public void Shutdown(int timeoutMs = Timeout.Infinite) + public Task ShutdownAsync(int timeoutMs = Timeout.Infinite) { - ShutdownAsync(timeoutMs).GetAwaiter().GetResult(); + return _clusterInitializer.ShutdownAsync(timeoutMs); } - /// - public async Task ShutdownAsync(int timeoutMs = Timeout.Infinite) + async Task IInternalCluster.PreShutdownAsync(int timeoutMs) { - if (!_initialized) + IEnumerable sessions; + await _sessionCreateLock.WaitAsync().ConfigureAwait(false); + try { - return; + sessions = _connectedSessions.ClearAndGet(); + } + finally + { + _sessionCreateLock.Release(); } - var sessions = _connectedSessions.ClearAndGet(); + try { var tasks = new List(); @@ -510,44 +359,32 @@ public async Task ShutdownAsync(int timeoutMs = Timeout.Infinite) await Task.WhenAll(tasks).WaitToCompleteAsync(timeoutMs).ConfigureAwait(false); } - catch (AggregateException ex) + catch (Exception ex) { - if (ex.InnerExceptions.Count == 1) - { - throw ex.InnerExceptions[0]; - } - throw; + Cluster.Logger.Warning("Exception occured while disposing session instances: {0}", ex.ToString()); } - _metadata.ShutDown(timeoutMs); - _controlConnection.Dispose(); - await _protocolEventDebouncer.ShutdownAsync().ConfigureAwait(false); + } + + async Task IInternalCluster.PostShutdownAsync() + { Configuration.Timer.Dispose(); // Dispose policies - var speculativeExecutionPolicies = new HashSet(new ReferenceEqualityComparer()); - foreach (var options in Configuration.RequestOptions.Values) - { - speculativeExecutionPolicies.Add(options.SpeculativeExecutionPolicy); - } - - foreach (var sep in speculativeExecutionPolicies) + foreach (var sep in _speculativeExecutionPolicies) { - sep.Dispose(); + await sep.ShutdownAsync().ConfigureAwait(false); } - - Cluster.Logger.Info("Cluster [" + Metadata.ClusterName + "] has been shut down."); - return; } /// HostDistance IInternalCluster.RetrieveAndSetDistance(Host host) { - var distance = _loadBalancingPolicies[0].Distance(host); + var distance = _loadBalancingPolicies[0].Distance(_lazyMetadata, host); for (var i = 1; i < _loadBalancingPolicies.Count; i++) { var lbp = _loadBalancingPolicies[i]; - var lbpDistance = lbp.Distance(host); + var lbpDistance = lbp.Distance(_lazyMetadata, host); if (lbpDistance < distance) { distance = lbpDistance; @@ -558,18 +395,60 @@ HostDistance IInternalCluster.RetrieveAndSetDistance(Host host) return distance; } + async Task IInternalCluster.OnSessionShutdownAsync(IInternalSession session) + { + await _sessionCreateLock.WaitAsync().ConfigureAwait(false); + try + { + var list = new List(); + foreach (var s in _connectedSessions) + { + if (!ReferenceEquals(s, session)) + { + list.Add(s); + } + } + + _connectedSessions.ClearAndGet(); + _connectedSessions.AddRange(list.ToArray()); + } + finally + { + _sessionCreateLock.Release(); + } + } + /// - async Task IInternalCluster.Prepare( - IInternalSession session, ISerializerManager serializerManager, PrepareRequest request) + async Task IInternalCluster.PrepareAsync( + IInternalSession session, string cqlQuery, string keyspace, IDictionary customPayload) + { + var serializer = _internalMetadata.SerializerManager.GetCurrentSerializer(); + var currentVersion = serializer.ProtocolVersion; + if (!currentVersion.SupportsKeyspaceInRequest() && keyspace != null) + { + // Validate protocol version here and not at PrepareRequest level, as PrepareRequest can be issued + // in the background (prepare and retry, prepare on up, ...) + throw new NotSupportedException($"Protocol version {currentVersion} does not support" + + " setting the keyspace as part of the PREPARE request"); + } + var request = new PrepareRequest(serializer, cqlQuery, keyspace, customPayload); + + return await PrepareAsync(session, request).ConfigureAwait(false); + } + + private async Task PrepareAsync(IInternalSession session, PrepareRequest request) { var lbp = session.Cluster.Configuration.DefaultRequestOptions.LoadBalancingPolicy; - var handler = InternalRef.Configuration.PrepareHandlerFactory.CreatePrepareHandler(serializerManager, this); - var ps = await handler.Prepare(request, session, lbp.NewQueryPlan(session.Keyspace, null).GetEnumerator()).ConfigureAwait(false); + var handler = InternalRef.Configuration.PrepareHandlerFactory.CreatePrepareHandler(Configuration.SerializerManager, this); + var ps = await handler.PrepareAsync( + request, + session, + lbp.NewQueryPlan(this, session.Keyspace, null).GetEnumerator()).ConfigureAwait(false); var psAdded = InternalRef.PreparedQueries.GetOrAdd(ps.Id, ps); if (ps != psAdded) { PrepareHandler.Logger.Warning("Re-preparing already prepared query is generally an anti-pattern and will likely " + - "affect performance. Consider preparing the statement only once. Query='{0}'", ps.Cql); + "affect performance. Consider preparing the statement only once. Query='{0}'", ps.Cql); ps = psAdded; } @@ -585,7 +464,7 @@ private async Task ReprepareAllQueries(Host host) { return; } - + // Get the first pool for that host that has open connections var pool = sessions.Select(s => s.GetExistingPool(host.Address)).Where(p => p != null).FirstOrDefault(p => p.HasConnections); if (pool == null) @@ -597,7 +476,7 @@ private async Task ReprepareAllQueries(Host host) PrepareHandler.Logger.Info($"Re-preparing {preparedQueries.Count} queries on {host.Address}"); var tasks = new List(preparedQueries.Count); var handler = InternalRef.Configuration.PrepareHandlerFactory.CreateReprepareHandler(); - var serializer = _metadata.ControlConnection.Serializer.GetCurrentSerializer(); + var serializer = Configuration.SerializerManager.GetCurrentSerializer(); using (var semaphore = new SemaphoreSlim(64, 64)) { foreach (var ps in preparedQueries) @@ -605,10 +484,10 @@ private async Task ReprepareAllQueries(Host host) var request = new PrepareRequest(serializer, ps.Cql, ps.Keyspace, null); await semaphore.WaitAsync().ConfigureAwait(false); tasks.Add(Task.Run(() => handler.ReprepareOnSingleNodeAsync( - new KeyValuePair(host, pool), - ps, - request, - semaphore, + new KeyValuePair(host, pool), + ps, + request, + semaphore, true))); } diff --git a/src/Cassandra/ClusterDescription.cs b/src/Cassandra/ClusterDescription.cs new file mode 100644 index 000000000..d7e161c3c --- /dev/null +++ b/src/Cassandra/ClusterDescription.cs @@ -0,0 +1,51 @@ +// +// Copyright (C) DataStax Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using Cassandra.Connections.Control; + +namespace Cassandra +{ + /// + /// Provides a snapshot of cluster metadata properties. + /// + public class ClusterDescription + { + internal ClusterDescription( + IInternalMetadata internalMetadata) + { + ClusterName = internalMetadata.ClusterName; + IsDbaas = internalMetadata.IsDbaas; + ProtocolVersion = internalMetadata.ProtocolVersion; + } + + /// + /// Returns the name of currently connected cluster. + /// + /// the Cassandra name of currently connected cluster. + public string ClusterName { get; } + + /// + /// Determines whether the cluster is provided as a service (DataStax Astra). + /// + public bool IsDbaas { get; } + + /// + /// Gets the Cassandra native binary protocol version that was + /// negotiated between the driver and the Cassandra node. + /// + public ProtocolVersion ProtocolVersion { get; } + } +} \ No newline at end of file diff --git a/src/Cassandra/Configuration.cs b/src/Cassandra/Configuration.cs index e1669f869..aa2821570 100644 --- a/src/Cassandra/Configuration.cs +++ b/src/Cassandra/Configuration.cs @@ -56,6 +56,8 @@ public class Configuration internal const string DefaultExecutionProfileName = "default"; internal const string DefaultSessionName = "s"; + internal static readonly ProtocolVersion MaxProtocolVersion = ProtocolVersion.MaxSupported; + /// /// Gets the policies set for the cluster. /// @@ -125,6 +127,8 @@ public class Configuration /// public string LocalDatacenter { get; } + public long? InitializationTimeoutMs { get; } + /// /// Shared reusable timer /// @@ -140,6 +144,8 @@ public class Configuration /// internal IEnumerable TypeSerializers { get; set; } + internal ISerializerManager SerializerManager { get; } + internal MetadataSyncOptions MetadataSyncOptions { get; } internal IStartupOptionsFactory StartupOptionsFactory { get; } @@ -284,6 +290,8 @@ public class Configuration internal ILocalDatacenterProvider LocalDatacenterProvider { get; } + internal IProtocolEventDebouncer ProtocolEventDebouncer { get; } + internal Configuration() : this(policies: Policies.DefaultPolicies, protocolOptions: new ProtocolOptions(), @@ -308,7 +316,8 @@ internal Configuration() : typeSerializerDefinitions: null, keepContactPointsUnresolved: null, allowBetaProtocolVersions: null, - localDatacenter: null) + localDatacenter: null, + initializationTimeoutMs: null) { } @@ -340,6 +349,7 @@ internal Configuration(Policies policies, bool? keepContactPointsUnresolved, bool? allowBetaProtocolVersions, string localDatacenter, + long? initializationTimeoutMs, ISessionFactory sessionFactory = null, IRequestOptionsMapper requestOptionsMapper = null, IStartupOptionsFactory startupOptionsFactory = null, @@ -362,7 +372,9 @@ internal Configuration(Policies policies, ISupportedOptionsInitializerFactory supportedOptionsInitializerFactory = null, IProtocolVersionNegotiator protocolVersionNegotiator = null, IServerEventsSubscriber serverEventsSubscriber = null, - ILocalDatacenterProvider localDatacenterProvider = null) + ILocalDatacenterProvider localDatacenterProvider = null, + ISerializerManager serializerManager = null, + IProtocolEventDebouncer protocolEventDebouncer = null) { AddressTranslator = addressTranslator ?? throw new ArgumentNullException(nameof(addressTranslator)); QueryOptions = queryOptions ?? throw new ArgumentNullException(nameof(queryOptions)); @@ -412,7 +424,8 @@ internal Configuration(Policies policies, RequestOptions = RequestOptionsMapper.BuildRequestOptionsDictionary(executionProfiles, policies, socketOptions, clientOptions, queryOptions, GraphOptions); ExecutionProfiles = BuildExecutionProfilesDictionary(executionProfiles, RequestOptions); LocalDatacenter = localDatacenter; - + InitializationTimeoutMs = initializationTimeoutMs; + MonitorReportingOptions = monitorReportingOptions ?? new MonitorReportingOptions(); InsightsSupportVerifier = insightsSupportVerifier ?? Configuration.DefaultInsightsSupportVerifier; InsightsClientFactory = insightsClientFactory ?? Configuration.DefaultInsightsClientFactory; @@ -420,12 +433,26 @@ internal Configuration(Policies policies, EndPointResolver = endPointResolver ?? new EndPointResolver(ServerNameResolver); ContactPointParser = contactPointParser ?? new ContactPointParser(DnsResolver, ProtocolOptions, ServerNameResolver, KeepContactPointsUnresolved); LocalDatacenterProvider = localDatacenterProvider ?? new LocalDatacenterProvider(); - + // Create the buffer pool with 16KB for small buffers and 256Kb for large buffers. // The pool does not eagerly reserve the buffers, so it doesn't take unnecessary memory // to create the instance. BufferPool = new RecyclableMemoryStreamManager(16 * 1024, 256 * 1024, ProtocolOptions.MaximumFrameLength); Timer = new HashedWheelTimer(); + + var protocolVersion = Configuration.MaxProtocolVersion; + if (ProtocolOptions.MaxProtocolVersionValue != null && + ProtocolOptions.MaxProtocolVersionValue.Value.IsSupported(AllowBetaProtocolVersions)) + { + protocolVersion = ProtocolOptions.MaxProtocolVersionValue.Value; + } + + SerializerManager = serializerManager ?? new SerializerManager(protocolVersion, TypeSerializers); + + ProtocolEventDebouncer = protocolEventDebouncer ?? new ProtocolEventDebouncer( + TimerFactory, + TimeSpan.FromMilliseconds(MetadataSyncOptions.RefreshSchemaDelayIncrement), + TimeSpan.FromMilliseconds(MetadataSyncOptions.MaxTotalRefreshSchemaDelay)); } /// diff --git a/src/Cassandra/Connections/Control/ControlConnection.cs b/src/Cassandra/Connections/Control/ControlConnection.cs index b3cf92782..180e0c5c6 100644 --- a/src/Cassandra/Connections/Control/ControlConnection.cs +++ b/src/Cassandra/Connections/Control/ControlConnection.cs @@ -22,9 +22,9 @@ using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; +using Cassandra.Helpers; using Cassandra.ProtocolEvents; using Cassandra.Responses; -using Cassandra.Serialization; using Cassandra.SessionManagement; using Cassandra.Tasks; @@ -33,32 +33,27 @@ namespace Cassandra.Connections.Control internal class ControlConnection : IControlConnection { private readonly IInternalCluster _cluster; - private readonly Metadata _metadata; - private volatile Host _host; - private volatile IConnectionEndPoint _currentConnectionEndPoint; - private volatile IConnection _connection; + private readonly IInternalMetadata _internalMetadata; + private readonly IEnumerable _contactPoints; + private readonly ITopologyRefresher _topologyRefresher; + private readonly ISupportedOptionsInitializer _supportedOptionsInitializer; + private readonly Configuration _config; + private readonly IReconnectionPolicy _reconnectionPolicy; + private readonly Timer _reconnectionTimer; internal static readonly Logger Logger = new Logger(typeof(ControlConnection)); - private readonly Configuration _config; - private readonly IReconnectionPolicy _reconnectionPolicy; private IReconnectionSchedule _reconnectionSchedule; - private readonly Timer _reconnectionTimer; private long _isShutdown; private int _refreshFlag; private Task _reconnectTask; - private readonly ISerializerManager _serializer; - private readonly IProtocolEventDebouncer _eventDebouncer; - private readonly IEnumerable _contactPoints; - private readonly ITopologyRefresher _topologyRefresher; - private readonly ISupportedOptionsInitializer _supportedOptionsInitializer; - private bool IsShutdown => Interlocked.Read(ref _isShutdown) > 0L; + private volatile Host _host; + private volatile IConnectionEndPoint _currentConnectionEndPoint; + private volatile IConnection _connection; + private volatile IClusterInitializer _clusterInitializer; - /// - /// Gets the binary protocol version to be used for this cluster. - /// - public ProtocolVersion ProtocolVersion => _serializer.CurrentProtocolVersion; + private bool IsShutdown => Interlocked.Read(ref _isShutdown) > 0L; /// public Host Host @@ -71,27 +66,21 @@ public Host Host public IPEndPoint LocalAddress => _connection?.LocalAddress; - public ISerializerManager Serializer => _serializer; - internal ControlConnection( IInternalCluster cluster, - IProtocolEventDebouncer eventDebouncer, - ProtocolVersion initialProtocolVersion, Configuration config, - Metadata metadata, + IInternalMetadata internalMetadata, IEnumerable contactPoints) { _cluster = cluster; - _metadata = metadata; + _internalMetadata = internalMetadata; _reconnectionPolicy = config.Policies.ReconnectionPolicy; _reconnectionSchedule = _reconnectionPolicy.NewSchedule(); _reconnectionTimer = new Timer(ReconnectEventHandler, null, Timeout.Infinite, Timeout.Infinite); _config = config; - _serializer = new SerializerManager(initialProtocolVersion, config.TypeSerializers); - _eventDebouncer = eventDebouncer; _contactPoints = contactPoints; - _topologyRefresher = config.TopologyRefresherFactory.Create(metadata, config); - _supportedOptionsInitializer = config.SupportedOptionsInitializerFactory.Create(metadata); + _topologyRefresher = config.TopologyRefresherFactory.Create(_internalMetadata, config); + _supportedOptionsInitializer = config.SupportedOptionsInitializerFactory.Create(_internalMetadata); if (!_config.KeepContactPointsUnresolved) { @@ -105,15 +94,16 @@ public void Dispose() } /// - public async Task InitAsync() + public async Task InitAsync(IClusterInitializer clusterInitializer, CancellationToken token = default) { + _clusterInitializer = clusterInitializer; ControlConnection.Logger.Info("Trying to connect the ControlConnection"); - await Connect(true).ConfigureAwait(false); + await Connect(true, token).ConfigureAwait(false); } /// /// Resolves the contact points to a read only list of which will be used - /// during initialization. Also sets . + /// during initialization. Also sets . /// private async Task InitialContactPointResolutionAsync() { @@ -123,7 +113,7 @@ private async Task InitialContactPointResolutionAsync() var resolvedContactPoints = tasksDictionary.ToDictionary(t => t.Key, t => t.Value.Result); - _metadata.SetResolvedContactPoints(resolvedContactPoints); + _internalMetadata.SetResolvedContactPoints(resolvedContactPoints); if (!resolvedContactPoints.Any(kvp => kvp.Value.Any())) { @@ -134,7 +124,7 @@ private async Task InitialContactPointResolutionAsync() private bool TotalConnectivityLoss() { - var currentHosts = _metadata.AllHosts(); + var currentHosts = _internalMetadata.AllHosts(); return currentHosts.Count(h => h.IsUp) == 0 || currentHosts.All(h => !_cluster.AnyOpenConnections(h)); } @@ -150,7 +140,7 @@ private async Task> ResolveContactPoint(IContac var endpoints = await contactPoint.GetConnectionEndPointsAsync( _config.KeepContactPointsUnresolved || connectivityLoss).ConfigureAwait(false); - return _metadata.UpdateResolvedContactPoint(contactPoint, endpoints); + return _internalMetadata.UpdateResolvedContactPoint(contactPoint, endpoints); } private async Task> ResolveHostContactPointOrConnectionEndpointAsync( @@ -183,38 +173,36 @@ private IEnumerable>> ContactPointResoluti private IEnumerable>> AllHostsEndPointResolutionTasksEnumerable( ConcurrentDictionary attemptedContactPoints, - ConcurrentDictionary attemptedHosts, - bool isInitializing) + ConcurrentDictionary attemptedHosts) { foreach (var host in GetHostEnumerable()) { if (attemptedHosts.TryAdd(host, null)) { - if (!IsHostValid(host, isInitializing)) + if (!IsHostValid(host, true)) { continue; } - yield return ResolveHostContactPointOrConnectionEndpointAsync(attemptedContactPoints, host, isInitializing); + yield return ResolveHostContactPointOrConnectionEndpointAsync(attemptedContactPoints, host, true); } } } private IEnumerable>> DefaultLbpHostsEnumerable( ConcurrentDictionary attemptedContactPoints, - ConcurrentDictionary attemptedHosts, - bool isInitializing) + ConcurrentDictionary attemptedHosts) { - foreach (var host in _config.DefaultRequestOptions.LoadBalancingPolicy.NewQueryPlan(null, null)) + foreach (var host in _config.DefaultRequestOptions.LoadBalancingPolicy.NewQueryPlan(_cluster, null, null)) { if (attemptedHosts.TryAdd(host, null)) { - if (!IsHostValid(host, isInitializing)) + if (!IsHostValid(host, false)) { continue; } - yield return ResolveHostContactPointOrConnectionEndpointAsync(attemptedContactPoints, host, isInitializing); + yield return ResolveHostContactPointOrConnectionEndpointAsync(attemptedContactPoints, host, false); } } } @@ -247,11 +235,12 @@ private bool IsHostValid(Host host, bool initializing) /// and Connection events. /// /// - /// Determines whether the ControlConnection is connecting for the first time as part of the initialization. + /// Whether this connection attempt is part of the initialization process. /// + /// Used to cancel initialization /// /// - private async Task Connect(bool isInitializing) + private async Task Connect(bool isInitializing, CancellationToken token = default) { // lazy iterator of endpoints to try for the control connection IEnumerable>> endPointResolutionTasksLazyIterator = @@ -263,7 +252,7 @@ private async Task Connect(bool isInitializing) // start with endpoints from the default LBP if it is already initialized if (!isInitializing) { - endPointResolutionTasksLazyIterator = DefaultLbpHostsEnumerable(attemptedContactPoints, attemptedHosts, isInitializing); + endPointResolutionTasksLazyIterator = DefaultLbpHostsEnumerable(attemptedContactPoints, attemptedHosts); } // add contact points next @@ -274,7 +263,7 @@ private async Task Connect(bool isInitializing) if (isInitializing) { endPointResolutionTasksLazyIterator = endPointResolutionTasksLazyIterator.Concat( - AllHostsEndPointResolutionTasksEnumerable(attemptedContactPoints, attemptedHosts, isInitializing)); + AllHostsEndPointResolutionTasksEnumerable(attemptedContactPoints, attemptedHosts)); } var triedHosts = new Dictionary(); @@ -283,11 +272,16 @@ private async Task Connect(bool isInitializing) var endPoints = await endPointResolutionTask.ConfigureAwait(false); foreach (var endPoint in endPoints) { + if (token.IsCancellationRequested) + { + throw new TaskCanceledException(); + } + ControlConnection.Logger.Verbose("Attempting to connect to {0}.", endPoint.EndpointFriendlyName); - var connection = _config.ConnectionFactory.CreateUnobserved(_serializer.GetCurrentSerializer(), endPoint, _config); + var connection = _config.ConnectionFactory.CreateUnobserved(_config.SerializerManager.GetCurrentSerializer(), endPoint, _config); try { - var version = _serializer.CurrentProtocolVersion; + var version = _config.SerializerManager.CurrentProtocolVersion; try { await connection.Open().ConfigureAwait(false); @@ -307,7 +301,6 @@ private async Task Connect(bool isInitializing) connection = await _config.ProtocolVersionNegotiator.ChangeProtocolVersion( _config, - _serializer, ex.ResponseProtocolVersion, connection, ex, @@ -331,29 +324,34 @@ await _config.ProtocolVersionNegotiator.ChangeProtocolVersion( ControlConnection.Logger.Info( "Connection established to {0} using protocol version {1}.", connection.EndPoint.EndpointFriendlyName, - _serializer.CurrentProtocolVersion.ToString("D")); + _config.SerializerManager.CurrentProtocolVersion.ToString("D")); if (isInitializing) { await _supportedOptionsInitializer.ApplySupportedOptionsAsync(connection).ConfigureAwait(false); } - + + await _config.ServerEventsSubscriber.SubscribeToServerEvents(connection, OnConnectionCassandraEvent).ConfigureAwait(false); var currentHost = await _topologyRefresher.RefreshNodeListAsync( - endPoint, connection, _serializer.GetCurrentSerializer()).ConfigureAwait(false); + endPoint, connection, _config.SerializerManager.GetCurrentSerializer()).ConfigureAwait(false); SetCurrentConnection(currentHost, endPoint); if (isInitializing) { await _config.ProtocolVersionNegotiator.NegotiateVersionAsync( - _config, _metadata, connection, _serializer).ConfigureAwait(false); + _config, _internalMetadata, connection).ConfigureAwait(false); } + + await _internalMetadata.RebuildTokenMapAsync(false, _config.MetadataSyncOptions.MetadataSyncEnabled).ConfigureAwait(false); - await _config.ServerEventsSubscriber.SubscribeToServerEvents(connection, OnConnectionCassandraEvent).ConfigureAwait(false); - await _metadata.RebuildTokenMapAsync(false, _config.MetadataSyncOptions.MetadataSyncEnabled).ConfigureAwait(false); + if (isInitializing) + { + await _clusterInitializer.PostInitializeAsync().ConfigureAwait(false); + } _host.Down += OnHostDown; - + return; } catch (Exception ex) @@ -474,11 +472,11 @@ private async Task Refresh() { var currentEndPoint = _currentConnectionEndPoint; var currentHost = await _topologyRefresher.RefreshNodeListAsync( - currentEndPoint, _connection, _serializer.GetCurrentSerializer()).ConfigureAwait(false); - + currentEndPoint, _connection, _config.SerializerManager.GetCurrentSerializer()).ConfigureAwait(false); + SetCurrentConnection(currentHost, currentEndPoint); - await _metadata.RebuildTokenMapAsync(false, _config.MetadataSyncOptions.MetadataSyncEnabled).ConfigureAwait(false); + await _internalMetadata.RebuildTokenMapAsync(false, _config.MetadataSyncOptions.MetadataSyncEnabled).ConfigureAwait(false); _reconnectionSchedule = _reconnectionPolicy.NewSchedule(); } catch (SocketException ex) @@ -541,6 +539,9 @@ private async void OnConnectionCassandraEvent(object sender, CassandraEventArgs { try { + // make sure the cluster object has finished initializing before processing protocol events + await _clusterInitializer.WaitInitAsync().ConfigureAwait(false); + //This event is invoked from a worker thread (not a IO thread) if (e is TopologyChangeEventArgs tce) { @@ -583,18 +584,18 @@ public Task HandleSchemaChangeEvent(SchemaChangeEventArgs ssc, bool processNow) handler = () => { //Can be either a table or a view - _metadata.ClearTable(ssc.Keyspace, ssc.Table); - _metadata.ClearView(ssc.Keyspace, ssc.Table); + _internalMetadata.ClearTable(ssc.Keyspace, ssc.Table); + _internalMetadata.ClearView(ssc.Keyspace, ssc.Table); return TaskHelper.Completed; }; } else if (ssc.FunctionName != null) { - handler = TaskHelper.ActionToAsync(() => _metadata.ClearFunction(ssc.Keyspace, ssc.FunctionName, ssc.Signature)); + handler = TaskHelper.ActionToAsync(() => _internalMetadata.ClearFunction(ssc.Keyspace, ssc.FunctionName, ssc.Signature)); } else if (ssc.AggregateName != null) { - handler = TaskHelper.ActionToAsync(() => _metadata.ClearAggregate(ssc.Keyspace, ssc.AggregateName, ssc.Signature)); + handler = TaskHelper.ActionToAsync(() => _internalMetadata.ClearAggregate(ssc.Keyspace, ssc.AggregateName, ssc.Signature)); } else if (ssc.Type != null) { @@ -602,7 +603,7 @@ public Task HandleSchemaChangeEvent(SchemaChangeEventArgs ssc, bool processNow) } else if (ssc.What == SchemaChangeEventArgs.Reason.Dropped) { - handler = TaskHelper.ActionToAsync(() => _metadata.RemoveKeyspace(ssc.Keyspace)); + handler = TaskHelper.ActionToAsync(() => _internalMetadata.RemoveKeyspace(ssc.Keyspace)); } else { @@ -617,7 +618,7 @@ private void HandleStatusChangeEvent(StatusChangeEventArgs e) //The address in the Cassandra event message needs to be translated var address = TranslateAddress(e.Address); ControlConnection.Logger.Info("Received Node status change event: host {0} is {1}", address, e.What.ToString().ToUpper()); - if (!_metadata.Hosts.TryGet(address, out var host)) + if (!_internalMetadata.Hosts.TryGet(address, out var host)) { ControlConnection.Logger.Info("Received status change event for host {0} but it was not found", address); return; @@ -646,17 +647,9 @@ private void SetCurrentConnection(Host host, IConnectionEndPoint endPoint) { _host = host; _currentConnectionEndPoint = endPoint; - _metadata.SetCassandraVersion(host.CassandraVersion); - } - - /// - /// Uses the active connection to execute a query - /// - public IEnumerable Query(string cqlQuery, bool retry = false) - { - return TaskHelper.WaitToComplete(QueryAsync(cqlQuery, retry), _config.SocketOptions.MetadataAbortTimeout); + _internalMetadata.SetCassandraVersion(host.CassandraVersion); } - + public async Task> QueryAsync(string cqlQuery, bool retry = false) { return _config.MetadataRequestHandler.GetRowSet( @@ -669,7 +662,7 @@ public async Task SendQueryRequestAsync(string cqlQuery, bool retry, Q try { response = await _config.MetadataRequestHandler.SendMetadataRequestAsync( - _connection, _serializer.GetCurrentSerializer(), cqlQuery, queryProtocolOptions).ConfigureAwait(false); + _connection, _config.SerializerManager.GetCurrentSerializer(), cqlQuery, queryProtocolOptions).ConfigureAwait(false); } catch (SocketException) { @@ -691,7 +684,7 @@ public async Task SendQueryRequestAsync(string cqlQuery, bool retry, Q public Task UnsafeSendQueryRequestAsync(string cqlQuery, QueryProtocolOptions queryProtocolOptions) { return _config.MetadataRequestHandler.UnsafeSendQueryRequestAsync( - _connection, _serializer.GetCurrentSerializer(), cqlQuery, queryProtocolOptions); + _connection, _config.SerializerManager.GetCurrentSerializer(), cqlQuery, queryProtocolOptions); } /// @@ -700,12 +693,12 @@ public Task UnsafeSendQueryRequestAsync(string cqlQuery, QueryProtocol private IEnumerable GetHostEnumerable() { var index = 0; - var hosts = _metadata.Hosts.ToArray(); + var hosts = _internalMetadata.Hosts.ToArray(); while (index < hosts.Length) { yield return hosts[index++]; // Check that the collection changed - var newHosts = _metadata.Hosts.ToCollection(); + var newHosts = _internalMetadata.Hosts.ToCollection(); if (newHosts.Count != hosts.Length) { index = 0; @@ -719,40 +712,40 @@ public async Task HandleKeyspaceRefreshLaterAsync(string keyspace) { var @event = new KeyspaceProtocolEvent(true, keyspace, async () => { - await _metadata.RefreshSingleKeyspace(keyspace).ConfigureAwait(false); + await _internalMetadata.RefreshSingleKeyspaceAsync(keyspace).ConfigureAwait(false); }); - await _eventDebouncer.HandleEventAsync(@event, false).ConfigureAwait(false); + await _config.ProtocolEventDebouncer.HandleEventAsync(@event, false).ConfigureAwait(false); } /// public Task ScheduleKeyspaceRefreshAsync(string keyspace, bool processNow) { - var @event = new KeyspaceProtocolEvent(true, keyspace, () => _metadata.RefreshSingleKeyspace(keyspace)); + var @event = new KeyspaceProtocolEvent(true, keyspace, () => _internalMetadata.RefreshSingleKeyspaceAsync(keyspace)); return processNow - ? _eventDebouncer.HandleEventAsync(@event, true) - : _eventDebouncer.ScheduleEventAsync(@event, false); + ? _config.ProtocolEventDebouncer.HandleEventAsync(@event, true) + : _config.ProtocolEventDebouncer.ScheduleEventAsync(@event, false); } private Task ScheduleObjectRefreshAsync(string keyspace, bool processNow, Func handler) { var @event = new KeyspaceProtocolEvent(false, keyspace, handler); return processNow - ? _eventDebouncer.HandleEventAsync(@event, true) - : _eventDebouncer.ScheduleEventAsync(@event, false); + ? _config.ProtocolEventDebouncer.HandleEventAsync(@event, true) + : _config.ProtocolEventDebouncer.ScheduleEventAsync(@event, false); } private Task ScheduleHostsRefreshAsync() { - return _eventDebouncer.ScheduleEventAsync(new ProtocolEvent(Refresh), false); + return _config.ProtocolEventDebouncer.ScheduleEventAsync(new ProtocolEvent(Refresh), false); } /// public Task ScheduleAllKeyspacesRefreshAsync(bool processNow) { - var @event = new ProtocolEvent(() => _metadata.RebuildTokenMapAsync(false, true)); + var @event = new ProtocolEvent(() => _internalMetadata.RebuildTokenMapAsync(false, true)); return processNow - ? _eventDebouncer.HandleEventAsync(@event, true) - : _eventDebouncer.ScheduleEventAsync(@event, false); + ? _config.ProtocolEventDebouncer.HandleEventAsync(@event, true) + : _config.ProtocolEventDebouncer.ScheduleEventAsync(@event, false); } } } \ No newline at end of file diff --git a/src/Cassandra/Connections/Control/ControlConnectionFactory.cs b/src/Cassandra/Connections/Control/ControlConnectionFactory.cs index 3490389c7..37b6f0196 100644 --- a/src/Cassandra/Connections/Control/ControlConnectionFactory.cs +++ b/src/Cassandra/Connections/Control/ControlConnectionFactory.cs @@ -15,7 +15,7 @@ // using System.Collections.Generic; -using Cassandra.ProtocolEvents; + using Cassandra.SessionManagement; namespace Cassandra.Connections.Control @@ -24,18 +24,14 @@ internal class ControlConnectionFactory : IControlConnectionFactory { public IControlConnection Create( IInternalCluster cluster, - IProtocolEventDebouncer protocolEventDebouncer, - ProtocolVersion initialProtocolVersion, - Configuration config, - Metadata metadata, + Configuration config, + IInternalMetadata internalMetadata, IEnumerable contactPoints) { return new ControlConnection( cluster, - protocolEventDebouncer, - initialProtocolVersion, - config, - metadata, + config, + internalMetadata, contactPoints); } } diff --git a/src/Cassandra/Connections/Control/IControlConnection.cs b/src/Cassandra/Connections/Control/IControlConnection.cs index 54cfcf585..f407f7fc1 100644 --- a/src/Cassandra/Connections/Control/IControlConnection.cs +++ b/src/Cassandra/Connections/Control/IControlConnection.cs @@ -15,7 +15,9 @@ // using System; +using System.Threading; using System.Threading.Tasks; +using Cassandra.Helpers; namespace Cassandra.Connections.Control { @@ -33,7 +35,7 @@ internal interface IControlConnection : IMetadataQueryProvider, IDisposable /// /// /// - Task InitAsync(); + Task InitAsync(IClusterInitializer clusterInitializer, CancellationToken token = default); /// /// Updates keyspace metadata and token map if necessary. diff --git a/src/Cassandra/Connections/Control/IControlConnectionFactory.cs b/src/Cassandra/Connections/Control/IControlConnectionFactory.cs index ac8af1cd5..1a7b0bc56 100644 --- a/src/Cassandra/Connections/Control/IControlConnectionFactory.cs +++ b/src/Cassandra/Connections/Control/IControlConnectionFactory.cs @@ -15,7 +15,7 @@ // using System.Collections.Generic; -using Cassandra.ProtocolEvents; + using Cassandra.SessionManagement; namespace Cassandra.Connections.Control @@ -24,10 +24,8 @@ internal interface IControlConnectionFactory { IControlConnection Create( IInternalCluster cluster, - IProtocolEventDebouncer protocolEventDebouncer, - ProtocolVersion initialProtocolVersion, - Configuration config, - Metadata metadata, + Configuration config, + IInternalMetadata internalMetadata, IEnumerable contactPoints); } } \ No newline at end of file diff --git a/src/Cassandra/Connections/Control/IInternalMetadata.cs b/src/Cassandra/Connections/Control/IInternalMetadata.cs new file mode 100644 index 000000000..230faf3dc --- /dev/null +++ b/src/Cassandra/Connections/Control/IInternalMetadata.cs @@ -0,0 +1,235 @@ +// +// Copyright (C) DataStax Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Cassandra.MetadataHelpers; +using Cassandra.Serialization; + +namespace Cassandra.Connections.Control +{ + internal interface IInternalMetadata + { + event HostsEventHandler HostsEvent; + + event SchemaChangedEventHandler SchemaChangedEvent; + + event Action HostAdded; + + event Action HostRemoved; + + /// + /// Gets the configuration associated with this instance. + /// + Configuration Configuration { get; } + + /// + /// Control connection to be used to execute the queries to retrieve the metadata + /// + IControlConnection ControlConnection { get; } + + ISerializerManager SerializerManager { get; } + + ISchemaParser SchemaParser { get; } + + string Partitioner { get; } + + Hosts Hosts { get; } + + IReadOnlyDictionary> ResolvedContactPoints { get; } + + IReadOnlyTokenMap TokenToReplicasMap { get; } + + bool IsDbaas { get; } + + string ClusterName { get; } + + ProtocolVersion ProtocolVersion { get; } + + KeyValuePair[] KeyspacesSnapshot { get; } + + void SetResolvedContactPoints( + IDictionary> resolvedContactPoints); + + void OnHostRemoved(Host h); + + void OnHostAdded(Host h); + + Host GetHost(IPEndPoint address); + + Host AddHost(IPEndPoint address); + + Host AddHost(IPEndPoint address, IContactPoint contactPoint); + + void RemoveHost(IPEndPoint address); + + void FireSchemaChangedEvent(SchemaChangedEventArgs.Kind what, string keyspace, string table, object sender = null); + + void OnHostDown(Host h); + + void OnHostUp(Host h); + + /// + /// Returns all known hosts of this cluster. + /// + /// collection of all known hosts of this cluster. + ICollection AllHosts(); + + IEnumerable AllReplicas(); + + Task RebuildTokenMapAsync(bool retry, bool fetchKeyspaces); + + /// + /// this method should be called by the event debouncer + /// + bool RemoveKeyspaceFromTokenMap(string name); + + Task UpdateTokenMapForKeyspace(string name); + + /// + /// Get the replicas for a given partition key and keyspace + /// + ICollection GetReplicas(string keyspaceName, byte[] partitionKey); + + /// + /// Get the replicas for a given partition key + /// + ICollection GetReplicas(byte[] partitionKey); + + /// + /// Returns metadata of specified keyspace. + /// + /// the name of the keyspace for which metadata should be + /// returned. + /// the metadata of the requested keyspace or null if + /// * keyspace is not a known keyspace. + Task GetKeyspaceAsync(string keyspace); + + /// + /// Returns a collection of all defined keyspaces names. + /// + /// a collection of all defined keyspaces names. + Task> GetKeyspacesAsync(); + + /// + /// Returns names of all tables which are defined within specified keyspace. + /// + /// the name of the keyspace for which all tables metadata should be + /// returned. + /// an ICollection of the metadata for the tables defined in this + /// keyspace. + Task> GetTablesAsync(string keyspace); + + /// + /// Returns TableMetadata for specified table in specified keyspace. + /// + /// name of the keyspace within specified table is defined. + /// name of table for which metadata should be returned. + /// a TableMetadata for the specified table in the specified keyspace. + Task GetTableAsync(string keyspace, string tableName); + + /// + /// Returns the view metadata for the provided view name in the keyspace. + /// + /// name of the keyspace within specified view is defined. + /// name of view. + /// a MaterializedViewMetadata for the view in the specified keyspace. + Task GetMaterializedViewAsync(string keyspace, string name); + + /// + /// Gets the definition associated with a User Defined Type from Cassandra + /// + Task GetUdtDefinitionAsync(string keyspace, string typeName); + + /// + /// Gets the definition associated with a User Defined Function from Cassandra + /// + /// The function metadata or null if not found. + Task GetFunctionAsync(string keyspace, string name, string[] signature); + + /// + /// Gets the definition associated with a aggregate from Cassandra + /// + /// The aggregate metadata or null if not found. + Task GetAggregateAsync(string keyspace, string name, string[] signature); + + /// + /// Gets the query trace. + /// + /// The query trace that contains the id, which properties are going to be populated. + /// + Task GetQueryTraceAsync(QueryTrace trace); + + /// + /// Updates keyspace metadata (including token metadata for token aware routing) for a given keyspace or a specific keyspace table. + /// If no keyspace is provided then this method will update the metadata and token map for all the keyspaces of the cluster. + /// + Task RefreshSchemaAsync(string keyspace = null, string table = null); + + /// + /// this method should be called by the event debouncer + /// + bool RemoveKeyspace(string name); + + /// + /// this method should be called by the event debouncer + /// + Task RefreshSingleKeyspaceAsync(string name); + + void ClearTable(string keyspaceName, string tableName); + + void ClearView(string keyspaceName, string name); + + void ClearFunction(string keyspaceName, string functionName, string[] signature); + + void ClearAggregate(string keyspaceName, string aggregateName, string[] signature); + + /// + /// Initiates a schema agreement check. + /// + /// Schema changes need to be propagated to all nodes in the cluster. + /// Once they have settled on a common version, we say that they are in agreement. + /// + /// This method does not perform retries so + /// does not apply. + /// + /// True if schema agreement was successful and false if it was not successful. + Task CheckSchemaAgreementAsync(); + + /// + /// Waits until that the schema version in all nodes is the same or the waiting time passed. + /// This method blocks the calling thread. + /// + Task WaitForSchemaAgreementAsync(IConnection connection); + + /// + /// Sets the Cassandra version in order to identify how to parse the metadata information + /// + /// + void SetCassandraVersion(Version version); + + void SetProductTypeAsDbaas(); + + void SetClusterName(string clusterName); + + void SetPartitioner(string partitioner); + + IEnumerable UpdateResolvedContactPoint( + IContactPoint contactPoint, IEnumerable endpoints); + } +} \ No newline at end of file diff --git a/src/Cassandra/Connections/Control/IProtocolVersionNegotiator.cs b/src/Cassandra/Connections/Control/IProtocolVersionNegotiator.cs index 12938263e..023208b63 100644 --- a/src/Cassandra/Connections/Control/IProtocolVersionNegotiator.cs +++ b/src/Cassandra/Connections/Control/IProtocolVersionNegotiator.cs @@ -1,12 +1,12 @@ -// +// // Copyright (C) DataStax Inc. -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,14 +14,19 @@ // limitations under the License. using System.Threading.Tasks; -using Cassandra.Serialization; namespace Cassandra.Connections.Control { internal interface IProtocolVersionNegotiator { - Task ChangeProtocolVersion(Configuration config, ISerializerManager serializer, ProtocolVersion nextVersion, IConnection previousConnection, UnsupportedProtocolVersionException ex = null, ProtocolVersion? previousVersion = null); - - Task NegotiateVersionAsync(Configuration config, Metadata metadata, IConnection connection, ISerializerManager serializer); + Task ChangeProtocolVersion( + Configuration config, + ProtocolVersion nextVersion, + IConnection previousConnection, + UnsupportedProtocolVersionException ex = null, + ProtocolVersion? previousVersion = null); + + Task NegotiateVersionAsync( + Configuration config, IInternalMetadata internalMetadata, IConnection connection); } } \ No newline at end of file diff --git a/src/Cassandra/Connections/Control/ISupportedOptionsInitializerFactory.cs b/src/Cassandra/Connections/Control/ISupportedOptionsInitializerFactory.cs index d5610f5a7..5950b2b8e 100644 --- a/src/Cassandra/Connections/Control/ISupportedOptionsInitializerFactory.cs +++ b/src/Cassandra/Connections/Control/ISupportedOptionsInitializerFactory.cs @@ -17,6 +17,6 @@ namespace Cassandra.Connections.Control { internal interface ISupportedOptionsInitializerFactory { - ISupportedOptionsInitializer Create(Metadata metadata); + ISupportedOptionsInitializer Create(IInternalMetadata internalMetadata); } } \ No newline at end of file diff --git a/src/Cassandra/Connections/Control/ITopologyMetadata.cs b/src/Cassandra/Connections/Control/ITopologyMetadata.cs new file mode 100644 index 000000000..105c6b9dc --- /dev/null +++ b/src/Cassandra/Connections/Control/ITopologyMetadata.cs @@ -0,0 +1,21 @@ +// +// Copyright (C) DataStax Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Cassandra.Connections.Control +{ + internal interface ITopologyMetadata + { + } +} \ No newline at end of file diff --git a/src/Cassandra/Connections/Control/ITopologyRefresher.cs b/src/Cassandra/Connections/Control/ITopologyRefresher.cs index 2f5b7d176..acdd3e56f 100644 --- a/src/Cassandra/Connections/Control/ITopologyRefresher.cs +++ b/src/Cassandra/Connections/Control/ITopologyRefresher.cs @@ -19,7 +19,7 @@ namespace Cassandra.Connections.Control { /// - /// Class that issues system table queries and updates the hosts collection on . + /// Class that issues system table queries and updates the hosts collection on . /// internal interface ITopologyRefresher { diff --git a/src/Cassandra/Connections/Control/ITopologyRefresherFactory.cs b/src/Cassandra/Connections/Control/ITopologyRefresherFactory.cs index 72c1feb32..b6fd2518c 100644 --- a/src/Cassandra/Connections/Control/ITopologyRefresherFactory.cs +++ b/src/Cassandra/Connections/Control/ITopologyRefresherFactory.cs @@ -17,6 +17,6 @@ namespace Cassandra.Connections.Control { internal interface ITopologyRefresherFactory { - ITopologyRefresher Create(Metadata metadata, Configuration config); + ITopologyRefresher Create(IInternalMetadata internalMetadata, Configuration config); } } \ No newline at end of file diff --git a/src/Cassandra/Connections/Control/InternalMetadata.cs b/src/Cassandra/Connections/Control/InternalMetadata.cs new file mode 100644 index 000000000..a533dd281 --- /dev/null +++ b/src/Cassandra/Connections/Control/InternalMetadata.cs @@ -0,0 +1,688 @@ +// +// Copyright (C) DataStax Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +using Cassandra.Collections; +using Cassandra.MetadataHelpers; +using Cassandra.Requests; +using Cassandra.Serialization; +using Cassandra.SessionManagement; + +namespace Cassandra.Connections.Control +{ + internal class InternalMetadata : IInternalMetadata + { + private const string SelectSchemaVersionPeers = "SELECT schema_version FROM system.peers"; + private const string SelectSchemaVersionLocal = "SELECT schema_version FROM system.local"; + + private static readonly Logger Logger = new Logger(typeof(ControlConnection)); + + private volatile TokenMap _tokenMap; + + private volatile ConcurrentDictionary _keyspaces = + new ConcurrentDictionary(); + + private volatile ISchemaParser _schemaParser; + + private volatile CopyOnWriteDictionary> _resolvedContactPoints = + new CopyOnWriteDictionary>(); + + private volatile bool _isDbaas = false; + private volatile string _clusterName; + private volatile string _partitioner; + + /// + public Configuration Configuration { get; } + + /// + public IControlConnection ControlConnection { get; } + + public ISerializerManager SerializerManager { get; } + + public ISchemaParser SchemaParser => _schemaParser; + + public string Partitioner => _partitioner; + + public Hosts Hosts { get; } + + public IReadOnlyDictionary> ResolvedContactPoints + => _resolvedContactPoints; + + public IReadOnlyTokenMap TokenToReplicasMap => _tokenMap; + + public bool IsDbaas => _isDbaas; + + public string ClusterName => _clusterName; + + public ProtocolVersion ProtocolVersion => SerializerManager.CurrentProtocolVersion; + + public event HostsEventHandler HostsEvent; + + public event SchemaChangedEventHandler SchemaChangedEvent; + + public event Action HostAdded; + + public event Action HostRemoved; + + internal InternalMetadata( + IInternalCluster cluster, + Configuration configuration, + IEnumerable parsedContactPoints, + SchemaParser schemaParser = null) + { + SerializerManager = configuration.SerializerManager; + Configuration = configuration; + Hosts = new Hosts(); + + ControlConnection = configuration.ControlConnectionFactory.Create( + cluster, + Configuration, + this, + parsedContactPoints); + + Hosts.Down += OnHostDown; + Hosts.Up += OnHostUp; + + _schemaParser = schemaParser; + } + + internal InternalMetadata( + IControlConnection cc, + Configuration configuration, + SchemaParser schemaParser = null) + { + SerializerManager = configuration.SerializerManager; + Configuration = configuration; + Hosts = new Hosts(); + + ControlConnection = cc; + + Hosts.Down += OnHostDown; + Hosts.Up += OnHostUp; + + _schemaParser = schemaParser; + } + + public void SetResolvedContactPoints( + IDictionary> resolvedContactPoints) + { + _resolvedContactPoints = + new CopyOnWriteDictionary>(resolvedContactPoints); + } + + public Host GetHost(IPEndPoint address) + { + return Hosts.TryGet(address, out var host) ? host : null; + } + + public Host AddHost(IPEndPoint address) + { + return Hosts.Add(address); + } + + public Host AddHost(IPEndPoint address, IContactPoint contactPoint) + { + return Hosts.Add(address, contactPoint); + } + + public void RemoveHost(IPEndPoint address) + { + Hosts.RemoveIfExists(address); + } + + public void OnHostRemoved(Host h) + { + HostRemoved?.Invoke(h); + } + + public void OnHostAdded(Host h) + { + HostAdded?.Invoke(h); + } + + public void FireSchemaChangedEvent(SchemaChangedEventArgs.Kind what, string keyspace, string table, object sender = null) + { + SchemaChangedEvent?.Invoke(sender ?? this, new SchemaChangedEventArgs { Keyspace = keyspace, What = what, Table = table }); + } + + public void OnHostDown(Host h) + { + HostsEvent?.Invoke(this, new HostsEventArgs { Address = h.Address, What = HostsEventArgs.Kind.Down }); + } + + public void OnHostUp(Host h) + { + HostsEvent?.Invoke(h, new HostsEventArgs { Address = h.Address, What = HostsEventArgs.Kind.Up }); + } + + /// + /// Returns all known hosts of this cluster. + /// + /// collection of all known hosts of this cluster. + public ICollection AllHosts() + { + return Hosts.ToCollection(); + } + + public IEnumerable AllReplicas() + { + return Hosts.AllEndPointsToCollection(); + } + + // for tests + public KeyValuePair[] KeyspacesSnapshot => _keyspaces.ToArray(); + + public async Task RebuildTokenMapAsync(bool retry, bool fetchKeyspaces) + { + IEnumerable ksList = null; + if (fetchKeyspaces) + { + InternalMetadata.Logger.Info("Retrieving keyspaces metadata"); + ksList = await _schemaParser.GetKeyspacesAsync(retry).ConfigureAwait(false); + } + + ConcurrentDictionary keyspaces; + if (ksList != null) + { + InternalMetadata.Logger.Info("Updating keyspaces metadata"); + var ksMap = ksList.Select(ks => new KeyValuePair(ks.Name, ks)); + keyspaces = new ConcurrentDictionary(ksMap); + } + else + { + keyspaces = _keyspaces; + } + + InternalMetadata.Logger.Info("Rebuilding token map"); + if (Partitioner == null) + { + throw new DriverInternalError("Partitioner can not be null"); + } + + var tokenMap = TokenMap.Build(Partitioner, Hosts.ToCollection(), keyspaces.Values); + _keyspaces = keyspaces; + _tokenMap = tokenMap; + } + + /// + /// this method should be called by the event debouncer + /// + public bool RemoveKeyspaceFromTokenMap(string name) + { + InternalMetadata.Logger.Verbose("Removing keyspace metadata: " + name); + var dropped = _keyspaces.TryRemove(name, out _); + _tokenMap?.RemoveKeyspace(name); + return dropped; + } + + public async Task UpdateTokenMapForKeyspace(string name) + { + var keyspaceMetadata = await _schemaParser.GetKeyspaceAsync(name).ConfigureAwait(false); + + var dropped = false; + var updated = false; + if (_tokenMap == null) + { + await RebuildTokenMapAsync(false, false).ConfigureAwait(false); + } + + if (keyspaceMetadata == null) + { + InternalMetadata.Logger.Verbose("Removing keyspace metadata: " + name); + dropped = _keyspaces.TryRemove(name, out _); + _tokenMap?.RemoveKeyspace(name); + } + else + { + InternalMetadata.Logger.Verbose("Updating keyspace metadata: " + name); + _keyspaces.AddOrUpdate(keyspaceMetadata.Name, keyspaceMetadata, (k, v) => + { + updated = true; + return keyspaceMetadata; + }); + InternalMetadata.Logger.Info("Rebuilding token map for keyspace {0}", keyspaceMetadata.Name); + if (Partitioner == null) + { + throw new DriverInternalError("Partitioner can not be null"); + } + + _tokenMap.UpdateKeyspace(keyspaceMetadata); + } + + if (Configuration.MetadataSyncOptions.MetadataSyncEnabled) + { + if (dropped) + { + FireSchemaChangedEvent(SchemaChangedEventArgs.Kind.Dropped, name, null, this); + } + else if (updated) + { + FireSchemaChangedEvent(SchemaChangedEventArgs.Kind.Updated, name, null, this); + } + else + { + FireSchemaChangedEvent(SchemaChangedEventArgs.Kind.Created, name, null, this); + } + } + + return keyspaceMetadata; + } + + /// + /// Get the replicas for a given partition key and keyspace + /// + public ICollection GetReplicas(string keyspaceName, byte[] partitionKey) + { + if (_tokenMap == null) + { + InternalMetadata.Logger.Warning("Metadata.GetReplicas was called but there was no token map."); + return new Host[0]; + } + + return _tokenMap.GetReplicas(keyspaceName, _tokenMap.Factory.Hash(partitionKey)); + } + + /// + /// Get the replicas for a given partition key + /// + public ICollection GetReplicas(byte[] partitionKey) + { + return GetReplicas(null, partitionKey); + } + + /// + /// Returns metadata of specified keyspace. + /// + /// the name of the keyspace for which metadata should be + /// returned. + /// the metadata of the requested keyspace or null if + /// * keyspace is not a known keyspace. + public Task GetKeyspaceAsync(string keyspace) + { + if (Configuration.MetadataSyncOptions.MetadataSyncEnabled) + { + //Use local cache + _keyspaces.TryGetValue(keyspace, out var ksInfo); + return Task.FromResult(ksInfo); + } + + return SchemaParser.GetKeyspaceAsync(keyspace); + } + + /// + /// Returns a collection of all defined keyspaces names. + /// + /// a collection of all defined keyspaces names. + public Task> GetKeyspacesAsync() + { + if (Configuration.MetadataSyncOptions.MetadataSyncEnabled) + { + //Use local cache + return Task.FromResult(_keyspaces.Keys); + } + + return SchemaParser.GetKeyspacesNamesAsync(); + } + + /// + /// Returns names of all tables which are defined within specified keyspace. + /// + /// the name of the keyspace for which all tables metadata should be + /// returned. + /// an ICollection of the metadata for the tables defined in this + /// keyspace. + public Task> GetTablesAsync(string keyspace) + { + if (Configuration.MetadataSyncOptions.MetadataSyncEnabled) + { + return !_keyspaces.TryGetValue(keyspace, out var ksMetadata) + ? Task.FromResult>(new string[0]) + : ksMetadata.GetTablesNamesAsync(); + } + + return SchemaParser.GetTableNamesAsync(keyspace); + } + + /// + /// Returns TableMetadata for specified table in specified keyspace. + /// + /// name of the keyspace within specified table is defined. + /// name of table for which metadata should be returned. + /// a TableMetadata for the specified table in the specified keyspace. + public Task GetTableAsync(string keyspace, string tableName) + { + if (Configuration.MetadataSyncOptions.MetadataSyncEnabled) + { + return !_keyspaces.TryGetValue(keyspace, out var ksMetadata) + ? Task.FromResult(null) + : ksMetadata.GetTableMetadataAsync(tableName); + } + + return SchemaParser.GetTableAsync(keyspace, tableName); + } + + /// + /// Returns the view metadata for the provided view name in the keyspace. + /// + /// name of the keyspace within specified view is defined. + /// name of view. + /// a MaterializedViewMetadata for the view in the specified keyspace. + public Task GetMaterializedViewAsync(string keyspace, string name) + { + if (Configuration.MetadataSyncOptions.MetadataSyncEnabled) + { + return !_keyspaces.TryGetValue(keyspace, out var ksMetadata) + ? null + : ksMetadata.GetMaterializedViewMetadataAsync(name); + } + + return SchemaParser.GetViewAsync(keyspace, name); + } + + /// + /// Gets the definition associated with a User Defined Type from Cassandra + /// + public Task GetUdtDefinitionAsync(string keyspace, string typeName) + { + if (Configuration.MetadataSyncOptions.MetadataSyncEnabled) + { + return !_keyspaces.TryGetValue(keyspace, out var ksMetadata) + ? Task.FromResult(null) + : ksMetadata.GetUdtDefinitionAsync(typeName); + } + + return SchemaParser.GetUdtDefinitionAsync(keyspace, typeName); + } + + /// + /// Gets the definition associated with a User Defined Function from Cassandra + /// + /// The function metadata or null if not found. + public Task GetFunctionAsync(string keyspace, string name, string[] signature) + { + if (Configuration.MetadataSyncOptions.MetadataSyncEnabled) + { + return !_keyspaces.TryGetValue(keyspace, out var ksMetadata) + ? Task.FromResult(null) + : ksMetadata.GetFunctionAsync(name, signature); + } + + var signatureString = SchemaParser.ComputeFunctionSignatureString(signature); + return SchemaParser.GetFunctionAsync(keyspace, name, signatureString); + } + + /// + /// Gets the definition associated with a aggregate from Cassandra + /// + /// The aggregate metadata or null if not found. + public Task GetAggregateAsync(string keyspace, string name, string[] signature) + { + if (Configuration.MetadataSyncOptions.MetadataSyncEnabled) + { + return !_keyspaces.TryGetValue(keyspace, out var ksMetadata) + ? Task.FromResult(null) + : ksMetadata.GetAggregateAsync(name, signature); + } + + var signatureString = SchemaParser.ComputeFunctionSignatureString(signature); + return SchemaParser.GetAggregateAsync(keyspace, name, signatureString); + } + + /// + /// Gets the query trace. + /// + /// The query trace that contains the id, which properties are going to be populated. + /// + public Task GetQueryTraceAsync(QueryTrace trace) + { + return _schemaParser.GetQueryTraceAsync(trace, Configuration.Timer); + } + + /// + /// Updates keyspace metadata (including token metadata for token aware routing) for a given keyspace or a specific keyspace table. + /// If no keyspace is provided then this method will update the metadata and token map for all the keyspaces of the cluster. + /// + public async Task RefreshSchemaAsync(string keyspace = null, string table = null) + { + if (keyspace == null) + { + await ControlConnection.ScheduleAllKeyspacesRefreshAsync(true).ConfigureAwait(false); + return true; + } + + await ControlConnection.ScheduleKeyspaceRefreshAsync(keyspace, true).ConfigureAwait(false); + _keyspaces.TryGetValue(keyspace, out var ks); + if (ks == null) + { + return false; + } + + if (table != null) + { + ks.ClearTableMetadata(table); + } + return true; + } + + /// + /// this method should be called by the event debouncer + /// + public bool RemoveKeyspace(string name) + { + var existed = RemoveKeyspaceFromTokenMap(name); + if (!existed) + { + return false; + } + + FireSchemaChangedEvent(SchemaChangedEventArgs.Kind.Dropped, name, null, this); + return true; + } + + /// + /// this method should be called by the event debouncer + /// + public Task RefreshSingleKeyspaceAsync(string name) + { + return UpdateTokenMapForKeyspace(name); + } + + public void ClearTable(string keyspaceName, string tableName) + { + if (_keyspaces.TryGetValue(keyspaceName, out var ksMetadata)) + { + ksMetadata.ClearTableMetadata(tableName); + } + } + + public void ClearView(string keyspaceName, string name) + { + if (_keyspaces.TryGetValue(keyspaceName, out var ksMetadata)) + { + ksMetadata.ClearViewMetadata(name); + } + } + + public void ClearFunction(string keyspaceName, string functionName, string[] signature) + { + if (_keyspaces.TryGetValue(keyspaceName, out var ksMetadata)) + { + ksMetadata.ClearFunction(functionName, signature); + } + } + + public void ClearAggregate(string keyspaceName, string aggregateName, string[] signature) + { + if (_keyspaces.TryGetValue(keyspaceName, out var ksMetadata)) + { + ksMetadata.ClearAggregate(aggregateName, signature); + } + } + + /// + /// Initiates a schema agreement check. + /// + /// Schema changes need to be propagated to all nodes in the cluster. + /// Once they have settled on a common version, we say that they are in agreement. + /// + /// This method does not perform retries so + /// does not apply. + /// + /// True if schema agreement was successful and false if it was not successful. + public async Task CheckSchemaAgreementAsync() + { + if (Hosts.Count == 1) + { + // If there is just one node, the schema is up to date in all nodes :) + return true; + } + + try + { + var queries = new[] + { + ControlConnection.QueryAsync(SelectSchemaVersionLocal), + ControlConnection.QueryAsync(SelectSchemaVersionPeers) + }; + + await Task.WhenAll(queries).ConfigureAwait(false); + + return CheckSchemaVersionResults(queries[0].Result, queries[1].Result); + } + catch (Exception ex) + { + Logger.Error("Error while checking schema agreement.", ex); + } + + return false; + } + + /// + /// Checks if there is only one schema version between the provided query results. + /// + /// + /// Results obtained from a query to system.local table. + /// Must contain the schema_version column. + /// + /// + /// Results obtained from a query to system.peers table. + /// Must contain the schema_version column. + /// + /// True if there is a schema agreement (only 1 schema version). False otherwise. + private static bool CheckSchemaVersionResults( + IEnumerable localVersionQuery, IEnumerable peerVersionsQuery) + { + return new HashSet( + peerVersionsQuery + .Concat(localVersionQuery) + .Select(r => r.GetValue("schema_version"))).Count == 1; + } + + /// + /// Waits until that the schema version in all nodes is the same or the waiting time passed. + /// This method blocks the calling thread. + /// + public async Task WaitForSchemaAgreementAsync(IConnection connection) + { + if (Hosts.Count == 1) + { + //If there is just one node, the schema is up to date in all nodes :) + return true; + } + var start = DateTime.Now; + var waitSeconds = Configuration.ProtocolOptions.MaxSchemaAgreementWaitSeconds; + InternalMetadata.Logger.Info("Waiting for schema agreement"); + try + { + var totalVersions = 0; + while (DateTime.Now.Subtract(start).TotalSeconds < waitSeconds) + { + var serializer = SerializerManager.GetCurrentSerializer(); + var schemaVersionLocalQuery = + new QueryRequest( + serializer, + InternalMetadata.SelectSchemaVersionLocal, + QueryProtocolOptions.Default, + false, + null); + var schemaVersionPeersQuery = + new QueryRequest( + serializer, + InternalMetadata.SelectSchemaVersionPeers, + QueryProtocolOptions.Default, + false, + null); + var queries = new[] { connection.Send(schemaVersionLocalQuery), connection.Send(schemaVersionPeersQuery) }; + // ReSharper disable once CoVariantArrayConversion + await Task.WhenAll(queries).ConfigureAwait(false); + + if (InternalMetadata.CheckSchemaVersionResults( + Configuration.MetadataRequestHandler.GetRowSet(await queries[0].ConfigureAwait(false)), + Configuration.MetadataRequestHandler.GetRowSet(await queries[1].ConfigureAwait(false)))) + { + return true; + } + + await Task.Delay(500).ConfigureAwait(false); + } + InternalMetadata.Logger.Info($"Waited for schema agreement, still {totalVersions} schema versions in the cluster."); + } + catch (Exception ex) + { + //Exceptions are not fatal + InternalMetadata.Logger.Error("There was an exception while trying to retrieve schema versions", ex); + } + + return false; + } + + /// + /// Sets the Cassandra version in order to identify how to parse the metadata information + /// + /// + public void SetCassandraVersion(Version version) + { + _schemaParser = Configuration.SchemaParserFactory.Create(version, this, GetUdtDefinitionAsync, _schemaParser); + } + + public void SetProductTypeAsDbaas() + { + _isDbaas = true; + } + + public void SetClusterName(string clusterName) + { + _clusterName = clusterName; + } + + public void SetPartitioner(string partitioner) + { + _partitioner = partitioner; + } + + public IEnumerable UpdateResolvedContactPoint( + IContactPoint contactPoint, IEnumerable endpoints) + { + return _resolvedContactPoints.AddOrUpdate(contactPoint, _ => endpoints, (_, __) => endpoints); + } + } +} \ No newline at end of file diff --git a/src/Cassandra/Connections/Control/ProtocolVersionNegotiator.cs b/src/Cassandra/Connections/Control/ProtocolVersionNegotiator.cs index 47b01644d..00c255f49 100644 --- a/src/Cassandra/Connections/Control/ProtocolVersionNegotiator.cs +++ b/src/Cassandra/Connections/Control/ProtocolVersionNegotiator.cs @@ -1,12 +1,12 @@ -// +// // Copyright (C) DataStax Inc. -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,7 +14,6 @@ // limitations under the License. using System.Threading.Tasks; -using Cassandra.Serialization; namespace Cassandra.Connections.Control { @@ -22,16 +21,16 @@ internal class ProtocolVersionNegotiator : IProtocolVersionNegotiator { public async Task NegotiateVersionAsync( Configuration config, - Metadata metadata, - IConnection connection, - ISerializerManager serializer) + IInternalMetadata internalMetadata, + IConnection connection) { - var commonVersion = serializer.CurrentProtocolVersion.GetHighestCommon(config, metadata.Hosts); - if (commonVersion != serializer.CurrentProtocolVersion) + var commonVersion = config.SerializerManager.CurrentProtocolVersion.GetHighestCommon( + config.AllowBetaProtocolVersions, internalMetadata.Hosts); + if (commonVersion != config.SerializerManager.CurrentProtocolVersion) { // Current connection will be closed and reopened connection = await ChangeProtocolVersion( - config, serializer, commonVersion, connection).ConfigureAwait(false); + config, commonVersion, connection).ConfigureAwait(false); } return connection; @@ -39,15 +38,14 @@ public async Task NegotiateVersionAsync( public async Task ChangeProtocolVersion( Configuration config, - ISerializerManager serializer, ProtocolVersion nextVersion, IConnection previousConnection, UnsupportedProtocolVersionException ex = null, ProtocolVersion? previousVersion = null) { - if (!nextVersion.IsSupported(config) || nextVersion == previousVersion) + if (!nextVersion.IsSupported(config.AllowBetaProtocolVersions) || nextVersion == previousVersion) { - nextVersion = nextVersion.GetLowerSupported(config); + nextVersion = nextVersion.GetLowerSupported(config.AllowBetaProtocolVersions); } if (nextVersion == 0) @@ -66,12 +64,12 @@ public async Task ChangeProtocolVersion( ? $"{ex.Message}, trying with version {nextVersion:D}" : $"Changing protocol version to {nextVersion:D}"); - serializer.ChangeProtocolVersion(nextVersion); + config.SerializerManager.ChangeProtocolVersion(nextVersion); previousConnection.Dispose(); var c = config.ConnectionFactory.CreateUnobserved( - serializer.GetCurrentSerializer(), + config.SerializerManager.GetCurrentSerializer(), previousConnection.EndPoint, config); try diff --git a/src/Cassandra/Connections/Control/SupportedOptionsInitializer.cs b/src/Cassandra/Connections/Control/SupportedOptionsInitializer.cs index e857a93d7..019f6fa7c 100644 --- a/src/Cassandra/Connections/Control/SupportedOptionsInitializer.cs +++ b/src/Cassandra/Connections/Control/SupportedOptionsInitializer.cs @@ -26,11 +26,11 @@ internal class SupportedOptionsInitializer : ISupportedOptionsInitializer private const string SupportedProductTypeKey = "PRODUCT_TYPE"; private const string SupportedDbaas = "DATASTAX_APOLLO"; - private readonly Metadata _metadata; + private readonly IInternalMetadata _internalMetadata; - public SupportedOptionsInitializer(Metadata metadata) + public SupportedOptionsInitializer(IInternalMetadata internalMetadata) { - _metadata = metadata; + _internalMetadata = internalMetadata; } public async Task ApplySupportedOptionsAsync(IConnection connection) @@ -65,7 +65,7 @@ private void ApplyProductTypeOption(IDictionary options) if (string.Compare(productTypeOptions[0], SupportedOptionsInitializer.SupportedDbaas, StringComparison.OrdinalIgnoreCase) == 0) { - _metadata.SetProductTypeAsDbaas(); + _internalMetadata.SetProductTypeAsDbaas(); } } } diff --git a/src/Cassandra/Connections/Control/SupportedOptionsInitializerFactory.cs b/src/Cassandra/Connections/Control/SupportedOptionsInitializerFactory.cs index ba080b28c..56b479bf5 100644 --- a/src/Cassandra/Connections/Control/SupportedOptionsInitializerFactory.cs +++ b/src/Cassandra/Connections/Control/SupportedOptionsInitializerFactory.cs @@ -17,9 +17,9 @@ namespace Cassandra.Connections.Control { internal class SupportedOptionsInitializerFactory : ISupportedOptionsInitializerFactory { - public ISupportedOptionsInitializer Create(Metadata metadata) + public ISupportedOptionsInitializer Create(IInternalMetadata internalMetadata) { - return new SupportedOptionsInitializer(metadata); + return new SupportedOptionsInitializer(internalMetadata); } } } \ No newline at end of file diff --git a/src/Cassandra/Connections/Control/TopologyRefresher.cs b/src/Cassandra/Connections/Control/TopologyRefresher.cs index 49f386e36..2252f4731 100644 --- a/src/Cassandra/Connections/Control/TopologyRefresher.cs +++ b/src/Cassandra/Connections/Control/TopologyRefresher.cs @@ -1,12 +1,12 @@ -// +// // Copyright (C) DataStax Inc. -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -18,9 +18,9 @@ using System.Linq; using System.Net; using System.Threading.Tasks; + using Cassandra.Responses; using Cassandra.Serialization; -using Cassandra.Tasks; namespace Cassandra.Connections.Control { @@ -34,16 +34,16 @@ internal class TopologyRefresher : ITopologyRefresher private static readonly IPAddress BindAllAddress = new IPAddress(new byte[4]); private readonly Configuration _config; - private readonly Metadata _metadata; + private readonly IInternalMetadata _internalMetadata; /// /// Once this is set to false, it will never be set to true again. /// private volatile bool _isPeersV2 = true; - public TopologyRefresher(Metadata metadata, Configuration config) + public TopologyRefresher(IInternalMetadata internalMetadata, Configuration config) { - _metadata = metadata ?? throw new ArgumentNullException(nameof(metadata)); + _internalMetadata = internalMetadata ?? throw new ArgumentNullException(nameof(internalMetadata)); _config = config ?? throw new ArgumentNullException(nameof(config)); } @@ -58,14 +58,14 @@ public async Task RefreshNodeListAsync( var localTask = SendSystemLocalRequestAsync(connection, serializer); var peersTask = SendSystemPeersRequestAsync(localIsPeersV2, connection, serializer); - + await Task.WhenAll(localTask, peersTask).ConfigureAwait(false); var peersResponse = peersTask.Result; localIsPeersV2 = peersResponse.IsPeersV2; var rsPeers = _config.MetadataRequestHandler.GetRowSet(peersResponse.Response); - + var localRow = _config.MetadataRequestHandler.GetRowSet(localTask.Result).FirstOrDefault(); if (localRow == null) { @@ -73,13 +73,13 @@ public async Task RefreshNodeListAsync( throw new DriverInternalError("Local host metadata could not be retrieved"); } - _metadata.Partitioner = localRow.GetValue("partitioner"); + _internalMetadata.SetPartitioner(localRow.GetValue("partitioner")); var host = GetAndUpdateLocalHost(currentEndPoint, localRow); UpdatePeersInfo(localIsPeersV2, rsPeers, host); ControlConnection.Logger.Info("Node list retrieved successfully"); return host; } - + private Task SendSystemLocalRequestAsync(IConnection connection, ISerializer serializer) { return _config.MetadataRequestHandler.SendMetadataRequestAsync( @@ -89,9 +89,9 @@ private Task SendSystemLocalRequestAsync(IConnection connection, ISeri private Task SendSystemPeersRequestAsync(bool isPeersV2, IConnection connection, ISerializer serializer) { var peersTask = _config.MetadataRequestHandler.SendMetadataRequestAsync( - connection, - serializer, - isPeersV2 ? TopologyRefresher.SelectPeersV2 : TopologyRefresher.SelectPeers, + connection, + serializer, + isPeersV2 ? TopologyRefresher.SelectPeersV2 : TopologyRefresher.SelectPeers, QueryProtocolOptions.Default); return GetPeersResponseAsync(isPeersV2, peersTask, connection, serializer); @@ -134,8 +134,8 @@ private async Task GetPeersResponseAsync( /// private Host GetAndUpdateLocalHost(IConnectionEndPoint endPoint, IRow row) { - var hostIpEndPoint = - endPoint.GetHostIpEndPoint() + var hostIpEndPoint = + endPoint.GetHostIpEndPoint() ?? GetRpcEndPoint(false, row, _config.AddressTranslator, _config.ProtocolOptions.Port); if (hostIpEndPoint == null) @@ -143,14 +143,14 @@ private Host GetAndUpdateLocalHost(IConnectionEndPoint endPoint, IRow row) throw new DriverInternalError("Could not parse the node's ip address from system tables."); } - var host = _metadata.GetHost(hostIpEndPoint) ?? _metadata.AddHost(hostIpEndPoint, endPoint.ContactPoint); + var host = _internalMetadata.GetHost(hostIpEndPoint) ?? _internalMetadata.AddHost(hostIpEndPoint, endPoint.ContactPoint); // Update cluster name, DC and rack for the one node we are connected to var clusterName = row.GetValue("cluster_name"); if (clusterName != null) { - _metadata.ClusterName = clusterName; + _internalMetadata.SetClusterName(clusterName); } host.SetInfo(row); @@ -173,20 +173,20 @@ private void UpdatePeersInfo(bool isPeersV2, IEnumerable peersRs, Host cur } foundPeers.Add(address); - var host = _metadata.GetHost(address) ?? _metadata.AddHost(address); + var host = _internalMetadata.GetHost(address) ?? _internalMetadata.AddHost(address); host.SetInfo(row); } // Removes all those that seems to have been removed (since we lost the control connection or not valid contact point) - foreach (var address in _metadata.AllReplicas()) + foreach (var address in _internalMetadata.AllReplicas()) { if (!address.Equals(currentHost.Address) && !foundPeers.Contains(address)) { - _metadata.RemoveHost(address); + _internalMetadata.RemoveHost(address); } } } - + /// /// Parses address from system table query response and translates it using the provided . /// @@ -225,17 +225,17 @@ internal IPEndPoint GetRpcEndPoint(bool isPeersV2, IRow row, IAddressTranslator return translator.Translate(new IPEndPoint(address, rpcPort)); } - + private IPAddress GetRpcAddressFromPeersV2(IRow row) { return row.GetValue("native_address"); } - + private IPAddress GetRpcAddressFromLocalPeersV1(IRow row) { return row.GetValue("rpc_address"); } - + private int? GetRpcPortFromPeersV2(IRow row) { return row.GetValue("native_port"); @@ -243,9 +243,9 @@ private IPAddress GetRpcAddressFromLocalPeersV1(IRow row) private class PeersResponse { - public bool IsPeersV2 { get; set; } + public bool IsPeersV2 { get; set; } - public Response Response { get; set; } + public Response Response { get; set; } } } } \ No newline at end of file diff --git a/src/Cassandra/Connections/Control/TopologyRefresherFactory.cs b/src/Cassandra/Connections/Control/TopologyRefresherFactory.cs index d6c969fa1..22331e3f2 100644 --- a/src/Cassandra/Connections/Control/TopologyRefresherFactory.cs +++ b/src/Cassandra/Connections/Control/TopologyRefresherFactory.cs @@ -17,9 +17,9 @@ namespace Cassandra.Connections.Control { internal class TopologyRefresherFactory : ITopologyRefresherFactory { - public ITopologyRefresher Create(Metadata metadata, Configuration config) + public ITopologyRefresher Create(IInternalMetadata internalMetadata, Configuration config) { - return new TopologyRefresher(metadata, config); + return new TopologyRefresher(internalMetadata, config); } } } \ No newline at end of file diff --git a/src/Cassandra/Connections/HostConnectionPool.cs b/src/Cassandra/Connections/HostConnectionPool.cs index f299772ed..429d7de35 100644 --- a/src/Cassandra/Connections/HostConnectionPool.cs +++ b/src/Cassandra/Connections/HostConnectionPool.cs @@ -107,9 +107,9 @@ private static class PoolState /// public IConnection[] ConnectionsSnapshot => _connections.GetSnapshot(); - - - public HostConnectionPool(Host host, Configuration config, ISerializerManager serializerManager, IObserverFactory observerFactory) + + public HostConnectionPool( + Host host, Configuration config, ISerializerManager serializerManager, IObserverFactory observerFactory) { _host = host; _host.Down += OnHostDown; diff --git a/src/Cassandra/Data/Linq/Batch.cs b/src/Cassandra/Data/Linq/Batch.cs index 7a05e9cf2..2889915c5 100644 --- a/src/Cassandra/Data/Linq/Batch.cs +++ b/src/Cassandra/Data/Linq/Batch.cs @@ -85,16 +85,16 @@ public void Execute() public void Execute(string executionProfile) { - WaitToCompleteWithMetrics(InternalExecuteAsync(executionProfile), QueryAbortTimeout); + InternalExecute(executionProfile); } - - protected abstract Task InternalExecuteAsync(); protected abstract Task InternalExecuteAsync(string executionProfile); + protected abstract RowSet InternalExecute(string executionProfile); + public Task ExecuteAsync() { - return InternalExecuteAsync(); + return InternalExecuteAsync(Configuration.DefaultExecutionProfileName); } public Task ExecuteAsync(string executionProfile) @@ -104,7 +104,7 @@ public Task ExecuteAsync(string executionProfile) public IAsyncResult BeginExecute(AsyncCallback callback, object state) { - return InternalExecuteAsync().ToApm(callback, state); + return InternalExecuteAsync(Configuration.DefaultExecutionProfileName).ToApm(callback, state); } public void EndExecute(IAsyncResult ar) @@ -125,10 +125,5 @@ protected string BatchTypeString() throw new ArgumentException(); } } - - internal T WaitToCompleteWithMetrics(Task task, int timeout = Timeout.Infinite) - { - return TaskHelper.WaitToCompleteWithMetrics(_metricsManager, task, timeout); - } } } diff --git a/src/Cassandra/Data/Linq/BatchV1.cs b/src/Cassandra/Data/Linq/BatchV1.cs index 9e6f44da4..cf23191de 100644 --- a/src/Cassandra/Data/Linq/BatchV1.cs +++ b/src/Cassandra/Data/Linq/BatchV1.cs @@ -16,7 +16,6 @@ using System.Text; using System.Threading.Tasks; -using Cassandra.Tasks; namespace Cassandra.Data.Linq { @@ -41,21 +40,28 @@ public override void Append(CqlCommand cqlCommand) _batchScript.AppendLine(";"); } - protected override Task InternalExecuteAsync() + protected override RowSet InternalExecute(string executionProfile) { - return InternalExecuteAsync(Configuration.DefaultExecutionProfileName); + var stmt = GetStatement(); + return _session.Execute(stmt, executionProfile); } - + protected override Task InternalExecuteAsync(string executionProfile) + { + var stmt = GetStatement(); + return _session.ExecuteAsync(stmt, executionProfile); + } + + private IStatement GetStatement() { if (_batchScript.Length == 0) { - return TaskHelper.FromException(new RequestInvalidException("The Batch must contain queries to execute")); + throw new RequestInvalidException("The Batch must contain queries to execute"); } string cqlQuery = GetCql(); var stmt = new SimpleStatement(cqlQuery); this.CopyQueryPropertiesTo(stmt); - return _session.ExecuteAsync(stmt, executionProfile); + return stmt; } private string GetCql() @@ -73,4 +79,4 @@ public override string ToString() return GetCql(); } } -} +} \ No newline at end of file diff --git a/src/Cassandra/Data/Linq/BatchV2.cs b/src/Cassandra/Data/Linq/BatchV2.cs index a9bcf8587..d503faed2 100644 --- a/src/Cassandra/Data/Linq/BatchV2.cs +++ b/src/Cassandra/Data/Linq/BatchV2.cs @@ -16,7 +16,6 @@ using System.Text; using System.Threading.Tasks; -using Cassandra.Tasks; namespace Cassandra.Data.Linq { @@ -41,17 +40,23 @@ public override void Append(CqlCommand cqlCommand) } _batchScript.Add(cqlCommand); } - - protected override Task InternalExecuteAsync() + + protected override RowSet InternalExecute(string executionProfile) { - return InternalExecuteAsync(Configuration.DefaultExecutionProfileName); + if (_batchScript.IsEmpty) + { + throw new RequestInvalidException("The Batch must contain queries to execute"); + } + _batchScript.SetBatchType(_batchType); + this.CopyQueryPropertiesTo(_batchScript); + return _session.Execute(_batchScript, executionProfile); } protected override Task InternalExecuteAsync(string executionProfile) { if (_batchScript.IsEmpty) { - return TaskHelper.FromException(new RequestInvalidException("The Batch must contain queries to execute")); + throw new RequestInvalidException("The Batch must contain queries to execute"); } _batchScript.SetBatchType(_batchType); this.CopyQueryPropertiesTo(_batchScript); @@ -68,4 +73,4 @@ public override string ToString() return sb.ToString(); } } -} +} \ No newline at end of file diff --git a/src/Cassandra/Data/Linq/CqlCommand.cs b/src/Cassandra/Data/Linq/CqlCommand.cs index 9ccc32c44..e9de3c2f8 100644 --- a/src/Cassandra/Data/Linq/CqlCommand.cs +++ b/src/Cassandra/Data/Linq/CqlCommand.cs @@ -114,6 +114,8 @@ public RowSet Execute(string executionProfile) { throw new ArgumentNullException(nameof(executionProfile)); } + + GetTable().GetSession().Connect(); return WaitToCompleteWithMetrics(ExecuteAsync(executionProfile), QueryAbortTimeout); } diff --git a/src/Cassandra/Data/Linq/CqlConditionalCommand.cs b/src/Cassandra/Data/Linq/CqlConditionalCommand.cs index 0c344698c..87b133958 100644 --- a/src/Cassandra/Data/Linq/CqlConditionalCommand.cs +++ b/src/Cassandra/Data/Linq/CqlConditionalCommand.cs @@ -89,8 +89,10 @@ protected internal override string GetCql(out object[] values) { throw new ArgumentNullException(nameof(executionProfile)); } - - var queryAbortTimeout = GetTable().GetSession().Cluster.Configuration.ClientOptions.QueryAbortTimeout; + + var session = GetTable().GetSession(); + session.Connect(); + var queryAbortTimeout = session.Cluster.Configuration.ClientOptions.QueryAbortTimeout; return WaitToCompleteWithMetrics(ExecuteAsync(executionProfile), queryAbortTimeout); } diff --git a/src/Cassandra/Data/Linq/CqlQuery.cs b/src/Cassandra/Data/Linq/CqlQuery.cs index 5ea17410f..e0060fa36 100644 --- a/src/Cassandra/Data/Linq/CqlQuery.cs +++ b/src/Cassandra/Data/Linq/CqlQuery.cs @@ -147,6 +147,7 @@ public IPage ExecutePaged(string executionProfile) throw new ArgumentNullException(nameof(executionProfile)); } + GetTable().GetSession().Connect(); return WaitToCompleteWithMetrics(ExecutePagedAsync(executionProfile), QueryAbortTimeout); } diff --git a/src/Cassandra/Data/Linq/CqlQueryBase.cs b/src/Cassandra/Data/Linq/CqlQueryBase.cs index a5eb77f9b..bd0747118 100644 --- a/src/Cassandra/Data/Linq/CqlQueryBase.cs +++ b/src/Cassandra/Data/Linq/CqlQueryBase.cs @@ -182,6 +182,7 @@ public IEnumerable EndExecute(IAsyncResult ar) private IEnumerable ExecuteCqlQuery(string executionProfile) { + GetTable().GetSession().Connect(); return WaitToCompleteWithMetrics(ExecuteCqlQueryAsync(executionProfile), QueryAbortTimeout); } @@ -197,5 +198,10 @@ internal T WaitToCompleteWithMetrics(Task task, int timeout = Timeout.Infi { return TaskHelper.WaitToCompleteWithMetrics(_metricsManager, task, timeout); } + + internal void WaitToCompleteWithMetrics(Task task, int timeout = Timeout.Infinite) + { + TaskHelper.WaitToCompleteWithMetrics(_metricsManager, task, timeout); + } } } \ No newline at end of file diff --git a/src/Cassandra/Data/Linq/CqlQuerySingleElement.cs b/src/Cassandra/Data/Linq/CqlQuerySingleElement.cs index 41917603d..66956cbf7 100644 --- a/src/Cassandra/Data/Linq/CqlQuerySingleElement.cs +++ b/src/Cassandra/Data/Linq/CqlQuerySingleElement.cs @@ -90,6 +90,7 @@ public override string ToString() /// public new TEntity Execute(string executionProfile) { + GetTable().GetSession().Connect(); return WaitToCompleteWithMetrics(ExecuteAsync(executionProfile), QueryAbortTimeout); } } diff --git a/src/Cassandra/Data/Linq/CqlScalar.cs b/src/Cassandra/Data/Linq/CqlScalar.cs index 2314fa7b0..1f2683de9 100644 --- a/src/Cassandra/Data/Linq/CqlScalar.cs +++ b/src/Cassandra/Data/Linq/CqlScalar.cs @@ -43,6 +43,7 @@ internal CqlScalar(Expression expression, ITable table, StatementFactory stmtFac public new TEntity Execute(string executionProfile) { + GetTable().GetSession().Connect(); return WaitToCompleteWithMetrics(ExecuteAsync(executionProfile), QueryAbortTimeout); } diff --git a/src/Cassandra/Data/Linq/SessionExtensions.cs b/src/Cassandra/Data/Linq/SessionExtensions.cs index 084c3a0fc..6e28e9df4 100644 --- a/src/Cassandra/Data/Linq/SessionExtensions.cs +++ b/src/Cassandra/Data/Linq/SessionExtensions.cs @@ -14,7 +14,9 @@ // limitations under the License. // +using System.Threading.Tasks; using Cassandra.Mapping; +using Cassandra.Tasks; namespace Cassandra.Data.Linq { @@ -45,17 +47,46 @@ public static Table GetTable(this ISession session, string tab return new Table(session, config, tableName, keyspaceName); } + /// + /// This method might block if there is no open connection. + /// Please use instead if your application + /// uses the Task Parallel Library (e.g. async/await). + /// public static Batch CreateBatch(this ISession session) { - return CreateBatch(session, BatchType.Logged); + return session.CreateBatch(BatchType.Logged); } + + /// + /// This method might block if there is no open connection. + /// Please use instead if your application + /// uses the Task Parallel Library (e.g. async/await). + /// public static Batch CreateBatch(this ISession session, BatchType batchType) { - if (session == null || session.BinaryProtocolVersion > 1) + var metadata = session.Cluster.Metadata.GetClusterDescription(); + return session.CreateBatch(metadata, batchType); + } + + public static Task CreateBatchAsync(this ISession session) + { + return session.CreateBatchAsync(BatchType.Logged); + } + + public static async Task CreateBatchAsync(this ISession session, BatchType batchType) + { + var metadata = await session.Cluster.Metadata.GetClusterDescriptionAsync().ConfigureAwait(false); + return session.CreateBatch(metadata, batchType); + } + + private static Batch CreateBatch(this ISession session, ClusterDescription description, BatchType batchType) + { + if (description.ProtocolVersion.SupportsBatch()) { return new BatchV2(session, batchType); } + return new BatchV1(session, batchType); } } diff --git a/src/Cassandra/Data/Linq/Table.cs b/src/Cassandra/Data/Linq/Table.cs index d49f91962..5fb0f9be0 100644 --- a/src/Cassandra/Data/Linq/Table.cs +++ b/src/Cassandra/Data/Linq/Table.cs @@ -18,6 +18,7 @@ using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; + using Cassandra.Mapping; using Cassandra.Mapping.Statements; @@ -68,10 +69,10 @@ public Table(ISession session, MappingConfiguration config, string tableName, st _keyspaceName = keyspaceName; //In case no mapping has been defined for the type, determine if the attributes used are Linq or Cassandra.Mapping //Linq attributes are marked as Obsolete - #pragma warning disable 612 +#pragma warning disable 612 config.MapperFactory.PocoDataFactory.AddDefinitionDefault(typeof(TEntity), () => LinqAttributeBasedTypeDefinition.DetermineAttributes(typeof(TEntity))); - #pragma warning restore 612 +#pragma warning restore 612 var pocoData = config.MapperFactory.GetPocoData(); InternalInitialize(Expression.Constant(this), this, config.MapperFactory, config.StatementFactory, pocoData); } @@ -90,7 +91,6 @@ public Table(ISession session, MappingConfiguration config, string tableName, st public Table(ISession session, MappingConfiguration config, string tableName) : this(session, config, tableName, null) { - } /// @@ -105,7 +105,6 @@ public Table(ISession session, MappingConfiguration config, string tableName) public Table(ISession session, MappingConfiguration config) : this(session, config, null, null) { - } /// @@ -145,17 +144,13 @@ object IQueryProvider.Execute(Expression expression) public Type GetEntityType() { - return typeof (TEntity); + return typeof(TEntity); } public void Create() { - var serializer = _session.Cluster.Metadata.ControlConnection.Serializer.GetCurrentSerializer(); - var cqlQueries = CqlGenerator.GetCreate(serializer, PocoData, Name, KeyspaceName, false); - foreach (var cql in cqlQueries) - { - _session.Execute(cql); - } + _session.Connect(); + WaitToCompleteWithMetrics(CreateAsync(), QueryAbortTimeout); } public void CreateIfNotExists() @@ -172,7 +167,9 @@ public void CreateIfNotExists() public async Task CreateAsync() { - var serializer = _session.Cluster.Metadata.ControlConnection.Serializer.GetCurrentSerializer(); + // ensure that session is connected before retrieving the current serializer (depends on negotiated protocol version) + await _session.ConnectAsync().ConfigureAwait(false); + var serializer = _session.Cluster.Configuration.SerializerManager.GetCurrentSerializer(); var cqlQueries = CqlGenerator.GetCreate(serializer, PocoData, Name, KeyspaceName, false); foreach (var cql in cqlQueries) { @@ -217,12 +214,12 @@ public CqlInsert Insert(TEntity entity) /// /// The entity to insert /// - /// Determines if the query must be generated using NULL values for null - /// entity members. + /// Determines if the query must be generated using NULL values for null + /// entity members. /// /// Use false if you don't want to consider null values for the INSERT /// operation (recommended). - /// + /// /// /// Use true if you want to override all the values in the table, /// generating tombstones for null values. @@ -233,4 +230,4 @@ public CqlInsert Insert(TEntity entity, bool insertNulls) return new CqlInsert(entity, insertNulls, this, StatementFactory, MapperFactory); } } -} +} \ No newline at end of file diff --git a/src/Cassandra/DataStax/Graph/GraphOptions.cs b/src/Cassandra/DataStax/Graph/GraphOptions.cs index a73e7ed1a..78b62f961 100644 --- a/src/Cassandra/DataStax/Graph/GraphOptions.cs +++ b/src/Cassandra/DataStax/Graph/GraphOptions.cs @@ -343,7 +343,7 @@ private static byte[] ToUtf8Buffer(string value) private static byte[] ToBuffer(long value) { var serializer = Serialization.TypeSerializer.PrimitiveLongSerializer; - return serializer.Serialize((ushort) Cluster.MaxProtocolVersion, value); + return serializer.Serialize((ushort) Configuration.MaxProtocolVersion, value); } } } diff --git a/src/Cassandra/DataStax/Insights/IInsightsClient.cs b/src/Cassandra/DataStax/Insights/IInsightsClient.cs index 6979f51ba..6d1ef6655 100644 --- a/src/Cassandra/DataStax/Insights/IInsightsClient.cs +++ b/src/Cassandra/DataStax/Insights/IInsightsClient.cs @@ -16,12 +16,13 @@ using System; using System.Threading.Tasks; +using Cassandra.Connections.Control; namespace Cassandra.DataStax.Insights { internal interface IInsightsClient : IDisposable { - void Init(); + void Initialize(IInternalMetadata internalMetadata); Task ShutdownAsync(); } diff --git a/src/Cassandra/DataStax/Insights/IInsightsSupportVerifier.cs b/src/Cassandra/DataStax/Insights/IInsightsSupportVerifier.cs index ebee85c22..01bba9a81 100644 --- a/src/Cassandra/DataStax/Insights/IInsightsSupportVerifier.cs +++ b/src/Cassandra/DataStax/Insights/IInsightsSupportVerifier.cs @@ -12,16 +12,16 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// +// using System; -using Cassandra.SessionManagement; +using Cassandra.Connections.Control; namespace Cassandra.DataStax.Insights { internal interface IInsightsSupportVerifier { - bool SupportsInsights(IInternalCluster cluster); + bool SupportsInsights(IInternalMetadata internalMetadata); bool DseVersionSupportsInsights(Version dseVersion); } diff --git a/src/Cassandra/DataStax/Insights/InfoProviders/IInsightsInfoProvider.cs b/src/Cassandra/DataStax/Insights/InfoProviders/IInsightsInfoProvider.cs index ddd20962a..e33ff0073 100644 --- a/src/Cassandra/DataStax/Insights/InfoProviders/IInsightsInfoProvider.cs +++ b/src/Cassandra/DataStax/Insights/InfoProviders/IInsightsInfoProvider.cs @@ -14,12 +14,13 @@ // limitations under the License. // +using Cassandra.Connections.Control; using Cassandra.SessionManagement; namespace Cassandra.DataStax.Insights.InfoProviders { internal interface IInsightsInfoProvider { - T GetInformation(IInternalCluster cluster, IInternalSession session); + T GetInformation(IInternalCluster cluster, IInternalSession session, IInternalMetadata internalMetadata); } } \ No newline at end of file diff --git a/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/AuthProviderInfoProvider.cs b/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/AuthProviderInfoProvider.cs index ccbc6085c..4654a952e 100644 --- a/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/AuthProviderInfoProvider.cs +++ b/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/AuthProviderInfoProvider.cs @@ -14,6 +14,7 @@ // limitations under the License. // +using Cassandra.Connections.Control; using Cassandra.DataStax.Insights.Schema.StartupMessage; using Cassandra.SessionManagement; @@ -21,7 +22,8 @@ namespace Cassandra.DataStax.Insights.InfoProviders.StartupMessage { internal class AuthProviderInfoProvider : IInsightsInfoProvider { - public AuthProviderInfo GetInformation(IInternalCluster cluster, IInternalSession session) + public AuthProviderInfo GetInformation( + IInternalCluster cluster, IInternalSession session, IInternalMetadata internalMetadata) { var type = cluster.Configuration.AuthProvider.GetType(); return new AuthProviderInfo diff --git a/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/ConfigAntiPatternsInfoProvider.cs b/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/ConfigAntiPatternsInfoProvider.cs index 44c152d64..7c80f1622 100644 --- a/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/ConfigAntiPatternsInfoProvider.cs +++ b/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/ConfigAntiPatternsInfoProvider.cs @@ -12,11 +12,13 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// +// using System; using System.Collections.Generic; using System.Linq; + +using Cassandra.Connections.Control; using Cassandra.SessionManagement; namespace Cassandra.DataStax.Insights.InfoProviders.StartupMessage @@ -27,7 +29,7 @@ static ConfigAntiPatternsInfoProvider() { ConfigAntiPatternsInfoProvider.AntiPatternsProviders = new Dictionary, Dictionary>> { - { + { #pragma warning disable 618 typeof(DowngradingConsistencyRetryPolicy), #pragma warning restore 618 @@ -37,16 +39,16 @@ static ConfigAntiPatternsInfoProvider() return antiPatterns; } }, - { - typeof(DefaultLoadBalancingPolicy), + { + typeof(DefaultLoadBalancingPolicy), (obj, antiPatterns) => { var typedPolicy = (DefaultLoadBalancingPolicy) obj; return ConfigAntiPatternsInfoProvider.AddAntiPatterns(typedPolicy.ChildPolicy, antiPatterns); } }, - { - typeof(RetryLoadBalancingPolicy), + { + typeof(RetryLoadBalancingPolicy), (obj, antiPatterns) => { var typedPolicy = (RetryLoadBalancingPolicy) obj; @@ -54,8 +56,8 @@ static ConfigAntiPatternsInfoProvider() return ConfigAntiPatternsInfoProvider.AddAntiPatterns(typedPolicy.LoadBalancingPolicy, antiPatterns); } }, - { - typeof(TokenAwarePolicy), + { + typeof(TokenAwarePolicy), (obj, antiPatterns) => { var typedPolicy = (TokenAwarePolicy) obj; @@ -92,11 +94,12 @@ static ConfigAntiPatternsInfoProvider() public static IReadOnlyDictionary, Dictionary>> AntiPatternsProviders { get; } - public Dictionary GetInformation(IInternalCluster cluster, IInternalSession session) + public Dictionary GetInformation( + IInternalCluster cluster, IInternalSession session, IInternalMetadata internalMetadata) { var antiPatterns = new Dictionary(); - var resolvedContactPoints = cluster.Metadata.ResolvedContactPoints; + var resolvedContactPoints = internalMetadata.ResolvedContactPoints; var contactPointsEndPoints = resolvedContactPoints .Values @@ -104,9 +107,9 @@ public Dictionary GetInformation(IInternalCluster cluster, IInte .Select(c => c.GetHostIpEndPointWithFallback()) .ToList(); - var contactPointsHosts = cluster + var contactPointsHosts = internalMetadata .AllHosts() - .Where(host => (host.ContactPoint != null && resolvedContactPoints.ContainsKey(host.ContactPoint)) + .Where(host => (host.ContactPoint != null && resolvedContactPoints.ContainsKey(host.ContactPoint)) || contactPointsEndPoints.Contains(host.Address)) .ToList(); @@ -126,8 +129,8 @@ public Dictionary GetInformation(IInternalCluster cluster, IInte private static Dictionary AddAntiPatterns(object obj, Dictionary antiPatterns) { - return ConfigAntiPatternsInfoProvider.AntiPatternsProviders.TryGetValue(obj.GetType(), out var provider) - ? provider.Invoke(obj, antiPatterns) + return ConfigAntiPatternsInfoProvider.AntiPatternsProviders.TryGetValue(obj.GetType(), out var provider) + ? provider.Invoke(obj, antiPatterns) : antiPatterns; } } diff --git a/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/DataCentersInfoProvider.cs b/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/DataCentersInfoProvider.cs index 577cc84c4..78ce92b7c 100644 --- a/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/DataCentersInfoProvider.cs +++ b/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/DataCentersInfoProvider.cs @@ -12,32 +12,35 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// +// using System.Collections.Generic; + +using Cassandra.Connections.Control; using Cassandra.SessionManagement; namespace Cassandra.DataStax.Insights.InfoProviders.StartupMessage { internal class DataCentersInfoProvider : IInsightsInfoProvider> { - public HashSet GetInformation(IInternalCluster cluster, IInternalSession session) + public HashSet GetInformation( + IInternalCluster cluster, IInternalSession session, IInternalMetadata internalMetadata) { var dataCenters = new HashSet(); var remoteConnectionsLength = cluster .Configuration - .GetOrCreatePoolingOptions(cluster.Metadata.ControlConnection.ProtocolVersion) + .GetOrCreatePoolingOptions(internalMetadata.ProtocolVersion) .GetCoreConnectionsPerHost(HostDistance.Remote); - foreach (var h in cluster.AllHosts()) + foreach (var h in internalMetadata.AllHosts()) { if (h.Datacenter == null) { continue; } - var distance = cluster.Configuration.Policies.LoadBalancingPolicy.Distance(h); + var distance = cluster.Configuration.Policies.LoadBalancingPolicy.Distance(cluster.Metadata, h); if (distance == HostDistance.Local || (distance == HostDistance.Remote && remoteConnectionsLength > 0)) { dataCenters.Add(h.Datacenter); diff --git a/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/DriverInfoProvider.cs b/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/DriverInfoProvider.cs index b83d0082a..4f6e392a2 100644 --- a/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/DriverInfoProvider.cs +++ b/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/DriverInfoProvider.cs @@ -14,6 +14,7 @@ // limitations under the License. // +using Cassandra.Connections.Control; using Cassandra.Helpers; using Cassandra.SessionManagement; @@ -21,7 +22,8 @@ namespace Cassandra.DataStax.Insights.InfoProviders.StartupMessage { internal class DriverInfoProvider : IInsightsInfoProvider { - public DriverInfo GetInformation(IInternalCluster cluster, IInternalSession session) + public DriverInfo GetInformation( + IInternalCluster cluster, IInternalSession session, IInternalMetadata internalMetadata) { return new DriverInfo { diff --git a/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/ExecutionProfileInfoProvider.cs b/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/ExecutionProfileInfoProvider.cs index ad69bb0c0..186fc00f3 100644 --- a/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/ExecutionProfileInfoProvider.cs +++ b/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/ExecutionProfileInfoProvider.cs @@ -16,6 +16,8 @@ using System.Collections.Generic; using System.Linq; + +using Cassandra.Connections.Control; using Cassandra.DataStax.Insights.Schema.Converters; using Cassandra.DataStax.Insights.Schema.StartupMessage; using Cassandra.SessionManagement; @@ -37,8 +39,9 @@ public ExecutionProfileInfoProvider( _speculativeExecutionPolicyInfoProvider = speculativeExecutionPolicyInfoProvider; _retryPolicyInfoProvider = retryPolicyInfoProvider; } - - public Dictionary GetInformation(IInternalCluster cluster, IInternalSession session) + + public Dictionary GetInformation( + IInternalCluster cluster, IInternalSession session, IInternalMetadata internalMetadata) { // add default first so that it is on top var dict = new Dictionary diff --git a/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/HostnameInfoProvider.cs b/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/HostnameInfoProvider.cs index 05319a4f3..0b1933423 100644 --- a/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/HostnameInfoProvider.cs +++ b/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/HostnameInfoProvider.cs @@ -12,16 +12,18 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// +// using System.Net; + +using Cassandra.Connections.Control; using Cassandra.SessionManagement; namespace Cassandra.DataStax.Insights.InfoProviders.StartupMessage { internal class HostnameInfoProvider : IInsightsInfoProvider { - public string GetInformation(IInternalCluster cluster, IInternalSession session) + public string GetInformation(IInternalCluster cluster, IInternalSession session, IInternalMetadata internalMetadata) { return Dns.GetHostName(); } diff --git a/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/OtherOptionsInfoProvider.cs b/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/OtherOptionsInfoProvider.cs index 1a47d9fc1..732b80448 100644 --- a/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/OtherOptionsInfoProvider.cs +++ b/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/OtherOptionsInfoProvider.cs @@ -12,16 +12,19 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// +// using System.Collections.Generic; + +using Cassandra.Connections.Control; using Cassandra.SessionManagement; namespace Cassandra.DataStax.Insights.InfoProviders.StartupMessage { internal class OtherOptionsInfoProvider : IInsightsInfoProvider> { - public Dictionary GetInformation(IInternalCluster cluster, IInternalSession session) + public Dictionary GetInformation( + IInternalCluster cluster, IInternalSession session, IInternalMetadata internalMetadata) { return null; } diff --git a/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/PlatformInfoProvider.cs b/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/PlatformInfoProvider.cs index dd9046004..01015ed12 100644 --- a/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/PlatformInfoProvider.cs +++ b/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/PlatformInfoProvider.cs @@ -18,6 +18,8 @@ using System.Linq; using System.Reflection; using System.Runtime.InteropServices; + +using Cassandra.Connections.Control; using Cassandra.DataStax.Insights.Schema.StartupMessage; using Cassandra.Helpers; using Cassandra.SessionManagement; @@ -26,7 +28,8 @@ namespace Cassandra.DataStax.Insights.InfoProviders.StartupMessage { internal class PlatformInfoProvider : IInsightsInfoProvider { - public InsightsPlatformInfo GetInformation(IInternalCluster cluster, IInternalSession session) + public InsightsPlatformInfo GetInformation( + IInternalCluster cluster, IInternalSession session, IInternalMetadata internalMetadata) { var cpuInfo = PlatformHelper.GetCpuInfo(); var dependencies = typeof(PlatformInfoProvider).GetTypeInfo().Assembly.GetReferencedAssemblies().Select(name => diff --git a/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/PoolSizeByHostDistanceInfoProvider.cs b/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/PoolSizeByHostDistanceInfoProvider.cs index 25043952d..3c6bdc1cf 100644 --- a/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/PoolSizeByHostDistanceInfoProvider.cs +++ b/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/PoolSizeByHostDistanceInfoProvider.cs @@ -14,6 +14,7 @@ // limitations under the License. // +using Cassandra.Connections.Control; using Cassandra.DataStax.Insights.Schema.StartupMessage; using Cassandra.SessionManagement; @@ -21,17 +22,18 @@ namespace Cassandra.DataStax.Insights.InfoProviders.StartupMessage { internal class PoolSizeByHostDistanceInfoProvider : IInsightsInfoProvider { - public PoolSizeByHostDistance GetInformation(IInternalCluster cluster, IInternalSession session) + public PoolSizeByHostDistance GetInformation( + IInternalCluster cluster, IInternalSession session, IInternalMetadata internalMetadata) { return new PoolSizeByHostDistance { Local = cluster .Configuration - .GetOrCreatePoolingOptions(cluster.Metadata.ControlConnection.ProtocolVersion) + .GetOrCreatePoolingOptions(internalMetadata.ProtocolVersion) .GetCoreConnectionsPerHost(HostDistance.Local), Remote = cluster .Configuration - .GetOrCreatePoolingOptions(cluster.Metadata.ControlConnection.ProtocolVersion) + .GetOrCreatePoolingOptions(internalMetadata.ProtocolVersion) .GetCoreConnectionsPerHost(HostDistance.Remote) }; } diff --git a/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/ReconnectionPolicyInfoProvider.cs b/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/ReconnectionPolicyInfoProvider.cs index 888fd30b4..7d589c90a 100644 --- a/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/ReconnectionPolicyInfoProvider.cs +++ b/src/Cassandra/DataStax/Insights/InfoProviders/StartupMessage/ReconnectionPolicyInfoProvider.cs @@ -12,10 +12,12 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// +// using System; using System.Collections.Generic; + +using Cassandra.Connections.Control; using Cassandra.DataStax.Insights.Schema.StartupMessage; using Cassandra.SessionManagement; @@ -27,24 +29,24 @@ static ReconnectionPolicyInfoProvider() { ReconnectionPolicyInfoProvider.PolicyOptionsProviders = new Dictionary>> { - { - typeof(ConstantReconnectionPolicy), + { + typeof(ConstantReconnectionPolicy), policy => { var typedPolicy = (ConstantReconnectionPolicy) policy; return new Dictionary {{ "constantDelayMs", typedPolicy.ConstantDelayMs }}; } }, - { - typeof(ExponentialReconnectionPolicy), + { + typeof(ExponentialReconnectionPolicy), policy => { var typedPolicy = (ExponentialReconnectionPolicy) policy; return new Dictionary {{ "baseDelayMs", typedPolicy.BaseDelayMs }, { "maxDelayMs", typedPolicy.MaxDelayMs }}; } }, - { - typeof(FixedReconnectionPolicy), + { + typeof(FixedReconnectionPolicy), policy => { var typedPolicy = (FixedReconnectionPolicy) policy; @@ -56,7 +58,8 @@ static ReconnectionPolicyInfoProvider() public static IReadOnlyDictionary>> PolicyOptionsProviders { get; } - public PolicyInfo GetInformation(IInternalCluster cluster, IInternalSession session) + public PolicyInfo GetInformation( + IInternalCluster cluster, IInternalSession session, IInternalMetadata internalMetadata) { var policy = cluster.Configuration.Policies.ReconnectionPolicy; var type = policy.GetType(); diff --git a/src/Cassandra/DataStax/Insights/InfoProviders/StatusMessage/NodeStatusInfoProvider.cs b/src/Cassandra/DataStax/Insights/InfoProviders/StatusMessage/NodeStatusInfoProvider.cs index 413a4efcc..c87024fce 100644 --- a/src/Cassandra/DataStax/Insights/InfoProviders/StatusMessage/NodeStatusInfoProvider.cs +++ b/src/Cassandra/DataStax/Insights/InfoProviders/StatusMessage/NodeStatusInfoProvider.cs @@ -15,6 +15,8 @@ // using System.Collections.Generic; + +using Cassandra.Connections.Control; using Cassandra.DataStax.Insights.Schema.StatusMessage; using Cassandra.SessionManagement; @@ -22,10 +24,11 @@ namespace Cassandra.DataStax.Insights.InfoProviders.StatusMessage { internal class NodeStatusInfoProvider : IInsightsInfoProvider> { - public Dictionary GetInformation(IInternalCluster cluster, IInternalSession session) + public Dictionary GetInformation( + IInternalCluster cluster, IInternalSession session, IInternalMetadata internalMetadata) { var nodeStatusDictionary = new Dictionary(); - var state = session.GetState(); + var state = session.GetState(internalMetadata); var connectedHosts = state.GetConnectedHosts(); foreach (var h in connectedHosts) diff --git a/src/Cassandra/DataStax/Insights/InsightsClient.cs b/src/Cassandra/DataStax/Insights/InsightsClient.cs index 393a06da8..a1557040b 100644 --- a/src/Cassandra/DataStax/Insights/InsightsClient.cs +++ b/src/Cassandra/DataStax/Insights/InsightsClient.cs @@ -17,12 +17,15 @@ using System; using System.Threading; using System.Threading.Tasks; + +using Cassandra.Connections.Control; using Cassandra.DataStax.Insights.MessageFactories; using Cassandra.DataStax.Insights.Schema.StartupMessage; using Cassandra.DataStax.Insights.Schema.StatusMessage; using Cassandra.Responses; using Cassandra.SessionManagement; using Cassandra.Tasks; + using Newtonsoft.Json; namespace Cassandra.DataStax.Insights @@ -59,17 +62,19 @@ public InsightsClient( private bool Initialized => _insightsTask != null; - private async Task SendStartupMessageAsync() + private async Task SendStartupMessageAsync(IInternalMetadata internalMetadata) { try { - await SendJsonMessageAsync(_startupMessageFactory.CreateMessage(_cluster, _session)).ConfigureAwait(false); + await SendJsonMessageAsync( + _startupMessageFactory.CreateMessage(_cluster, _session, internalMetadata), + internalMetadata).ConfigureAwait(false); _errorCount = 0; return true; } catch (Exception ex) { - if (_cancellationTokenSource.IsCancellationRequested || + if (_cancellationTokenSource.IsCancellationRequested || _errorCount >= InsightsClient.ErrorCountThresholdForLogging) { return false; @@ -81,17 +86,19 @@ private async Task SendStartupMessageAsync() } } - private async Task SendStatusMessageAsync() + private async Task SendStatusMessageAsync(IInternalMetadata internalMetadata) { try { - await SendJsonMessageAsync(_statusMessageFactory.CreateMessage(_cluster, _session)).ConfigureAwait(false); + await SendJsonMessageAsync( + _statusMessageFactory.CreateMessage(_cluster, _session, internalMetadata), + internalMetadata).ConfigureAwait(false); _errorCount = 0; return true; } catch (Exception ex) { - if (_cancellationTokenSource.IsCancellationRequested || + if (_cancellationTokenSource.IsCancellationRequested || _errorCount >= InsightsClient.ErrorCountThresholdForLogging) { return false; @@ -103,27 +110,30 @@ private async Task SendStatusMessageAsync() } } - public void Init() + public void Initialize(IInternalMetadata internalMetadata) { - if (!ShouldStartInsightsTask()) + if (!ShouldStartInsightsTask(internalMetadata)) { _insightsTask = null; return; } _cancellationTokenSource = new CancellationTokenSource(); - _insightsTask = Task.Run(MainLoopAsync); + _insightsTask = Task.Run(() => MainLoopAsync(internalMetadata)); } - public Task ShutdownAsync() + public async Task ShutdownAsync() { if (!Initialized) { - return TaskHelper.Completed; + return; } _cancellationTokenSource.Cancel(); - return _insightsTask; + + await _insightsTask.ConfigureAwait(false); + + _cancellationTokenSource.Dispose(); } public void Dispose() @@ -131,12 +141,13 @@ public void Dispose() ShutdownAsync().GetAwaiter().GetResult(); } - private bool ShouldStartInsightsTask() + private bool ShouldStartInsightsTask(IInternalMetadata internalMetadata) { - return _monitorReportingOptions.MonitorReportingEnabled && _cluster.Configuration.InsightsSupportVerifier.SupportsInsights(_cluster); + return _monitorReportingOptions.MonitorReportingEnabled + && _cluster.Configuration.InsightsSupportVerifier.SupportsInsights(internalMetadata); } - private async Task MainLoopAsync() + private async Task MainLoopAsync(IInternalMetadata metadata) { try { @@ -146,20 +157,20 @@ private async Task MainLoopAsync() // The initial delay should contain some random portion // Initial delay should be statusEventDelay - (0 to 10%) var percentageToSubtract = new Random(Guid.NewGuid().GetHashCode()).NextDouble() * 0.1; - var delay = _monitorReportingOptions.StatusEventDelayMilliseconds - + var delay = _monitorReportingOptions.StatusEventDelayMilliseconds - (_monitorReportingOptions.StatusEventDelayMilliseconds * percentageToSubtract); - + while (!_cancellationTokenSource.IsCancellationRequested) { if (!startupSent) { - startupSent = await SendStartupMessageAsync().ConfigureAwait(false); + startupSent = await SendStartupMessageAsync(metadata).ConfigureAwait(false); } else { - await SendStatusMessageAsync().ConfigureAwait(false); + await SendStatusMessageAsync(metadata).ConfigureAwait(false); } - + await TaskHelper.DelayWithCancellation( TimeSpan.FromMilliseconds(delay), _cancellationTokenSource.Token).ConfigureAwait(false); @@ -178,7 +189,7 @@ await TaskHelper.DelayWithCancellation( InsightsClient.Logger.Verbose("Insights task is ending."); } - private async Task SendJsonMessageAsync(T message) + private async Task SendJsonMessageAsync(T message, IInternalMetadata internalMetadata) { var queryProtocolOptions = new QueryProtocolOptions( ConsistencyLevel.One, @@ -188,9 +199,9 @@ private async Task SendJsonMessageAsync(T message) null, ConsistencyLevel.Any); - var response = await RunWithTokenAsync(() => - _cluster.Metadata.ControlConnection.UnsafeSendQueryRequestAsync( - InsightsClient.ReportInsightRpc, + var response = await RunWithTokenAsync(() => + internalMetadata.ControlConnection.UnsafeSendQueryRequestAsync( + InsightsClient.ReportInsightRpc, queryProtocolOptions)).ConfigureAwait(false); if (response == null) diff --git a/src/Cassandra/DataStax/Insights/InsightsSupportVerifier.cs b/src/Cassandra/DataStax/Insights/InsightsSupportVerifier.cs index f60bb72c3..c66034843 100644 --- a/src/Cassandra/DataStax/Insights/InsightsSupportVerifier.cs +++ b/src/Cassandra/DataStax/Insights/InsightsSupportVerifier.cs @@ -12,11 +12,11 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// +// using System; using System.Linq; -using Cassandra.SessionManagement; +using Cassandra.Connections.Control; namespace Cassandra.DataStax.Insights { @@ -26,12 +26,12 @@ internal class InsightsSupportVerifier : IInsightsSupportVerifier private static readonly Version MinDse51Version = new Version(5, 1, 13); private static readonly Version Dse600Version = new Version(6, 0, 0); - public bool SupportsInsights(IInternalCluster cluster) + public bool SupportsInsights(IInternalMetadata internalMetadata) { - var allHosts = cluster.AllHosts(); + var allHosts = internalMetadata.AllHosts(); return allHosts.Count != 0 && allHosts.All(h => DseVersionSupportsInsights(h.DseVersion)); } - + public bool DseVersionSupportsInsights(Version dseVersion) { if (dseVersion == null) @@ -54,6 +54,5 @@ public bool DseVersionSupportsInsights(Version dseVersion) return false; } - } } \ No newline at end of file diff --git a/src/Cassandra/DataStax/Insights/MessageFactories/IInsightsMessageFactory.cs b/src/Cassandra/DataStax/Insights/MessageFactories/IInsightsMessageFactory.cs index f9aaf1d09..45c98e5fc 100644 --- a/src/Cassandra/DataStax/Insights/MessageFactories/IInsightsMessageFactory.cs +++ b/src/Cassandra/DataStax/Insights/MessageFactories/IInsightsMessageFactory.cs @@ -12,8 +12,9 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// +// +using Cassandra.Connections.Control; using Cassandra.DataStax.Insights.Schema; using Cassandra.SessionManagement; @@ -21,6 +22,6 @@ namespace Cassandra.DataStax.Insights.MessageFactories { internal interface IInsightsMessageFactory { - Insight CreateMessage(IInternalCluster cluster, IInternalSession session); + Insight CreateMessage(IInternalCluster cluster, IInternalSession session, IInternalMetadata internalMetadata); } } \ No newline at end of file diff --git a/src/Cassandra/DataStax/Insights/MessageFactories/InsightsStartupMessageFactory.cs b/src/Cassandra/DataStax/Insights/MessageFactories/InsightsStartupMessageFactory.cs index 05944b4d4..13bec92f0 100644 --- a/src/Cassandra/DataStax/Insights/MessageFactories/InsightsStartupMessageFactory.cs +++ b/src/Cassandra/DataStax/Insights/MessageFactories/InsightsStartupMessageFactory.cs @@ -15,6 +15,8 @@ // using System.Linq; + +using Cassandra.Connections.Control; using Cassandra.DataStax.Insights.InfoProviders; using Cassandra.DataStax.Insights.Schema; using Cassandra.DataStax.Insights.Schema.StartupMessage; @@ -26,7 +28,7 @@ internal class InsightsStartupMessageFactory : IInsightsMessageFactory CreateMessage(IInternalCluster cluster, IInternalSession session) + public Insight CreateMessage(IInternalCluster cluster, IInternalSession session, IInternalMetadata internalMetadata) { - var metadata = _metadataFactory.CreateInsightsMetadata( + var insightsMetadata = _metadataFactory.CreateInsightsMetadata( InsightsStartupMessageFactory.StartupMessageName, InsightsStartupMessageFactory.StartupV1MappingId, InsightType.Event); - var driverInfo = _infoProviders.DriverInfoProvider.GetInformation(cluster, session); + var driverInfo = _infoProviders.DriverInfoProvider.GetInformation(cluster, session, internalMetadata); var startupData = new InsightsStartupData { ClientId = cluster.Configuration.ClusterId.ToString(), @@ -51,34 +53,34 @@ public Insight CreateMessage(IInternalCluster cluster, IInt ApplicationName = cluster.Configuration.ApplicationName, ApplicationVersion = cluster.Configuration.ApplicationVersion, ApplicationNameWasGenerated = cluster.Configuration.ApplicationNameWasGenerated, - ContactPoints = - cluster.Metadata.ResolvedContactPoints.ToDictionary( + ContactPoints = + internalMetadata.ResolvedContactPoints.ToDictionary( kvp => kvp.Key.StringRepresentation, kvp => kvp.Value.Select(ipEndPoint => ipEndPoint.GetHostIpEndPointWithFallback().ToString()).ToList()), - DataCenters = _infoProviders.DataCentersInfoProvider.GetInformation(cluster, session), - InitialControlConnection = cluster.Metadata.ControlConnection.EndPoint?.GetHostIpEndPointWithFallback().ToString(), - LocalAddress = cluster.Metadata.ControlConnection.LocalAddress?.ToString(), - HostName = _infoProviders.HostnameProvider.GetInformation(cluster, session), - ProtocolVersion = (byte)cluster.Metadata.ControlConnection.ProtocolVersion, - ExecutionProfiles = _infoProviders.ExecutionProfileInfoProvider.GetInformation(cluster, session), - PoolSizeByHostDistance = _infoProviders.PoolSizeByHostDistanceInfoProvider.GetInformation(cluster, session), - HeartbeatInterval = + DataCenters = _infoProviders.DataCentersInfoProvider.GetInformation(cluster, session, internalMetadata), + InitialControlConnection = internalMetadata.ControlConnection.EndPoint?.GetHostIpEndPointWithFallback().ToString(), + LocalAddress = internalMetadata.ControlConnection.LocalAddress?.ToString(), + HostName = _infoProviders.HostnameProvider.GetInformation(cluster, session, internalMetadata), + ProtocolVersion = (byte)internalMetadata.ProtocolVersion, + ExecutionProfiles = _infoProviders.ExecutionProfileInfoProvider.GetInformation(cluster, session, internalMetadata), + PoolSizeByHostDistance = _infoProviders.PoolSizeByHostDistanceInfoProvider.GetInformation(cluster, session, internalMetadata), + HeartbeatInterval = cluster .Configuration - .GetOrCreatePoolingOptions(cluster.Metadata.ControlConnection.ProtocolVersion) + .GetOrCreatePoolingOptions(internalMetadata.ProtocolVersion) .GetHeartBeatInterval() ?? 0, Compression = cluster.Configuration.ProtocolOptions.Compression, - ReconnectionPolicy = _infoProviders.ReconnectionPolicyInfoProvider.GetInformation(cluster, session), + ReconnectionPolicy = _infoProviders.ReconnectionPolicyInfoProvider.GetInformation(cluster, session, internalMetadata), Ssl = new SslInfo { Enabled = cluster.Configuration.ProtocolOptions.SslOptions != null }, - AuthProvider = _infoProviders.AuthProviderInfoProvider.GetInformation(cluster, session), - OtherOptions = _infoProviders.OtherOptionsInfoProvider.GetInformation(cluster, session), - PlatformInfo = _infoProviders.PlatformInfoProvider.GetInformation(cluster, session), - ConfigAntiPatterns = _infoProviders.ConfigAntiPatternsInfoProvider.GetInformation(cluster, session), + AuthProvider = _infoProviders.AuthProviderInfoProvider.GetInformation(cluster, session, internalMetadata), + OtherOptions = _infoProviders.OtherOptionsInfoProvider.GetInformation(cluster, session, internalMetadata), + PlatformInfo = _infoProviders.PlatformInfoProvider.GetInformation(cluster, session, internalMetadata), + ConfigAntiPatterns = _infoProviders.ConfigAntiPatternsInfoProvider.GetInformation(cluster, session, internalMetadata), PeriodicStatusInterval = cluster.Configuration.MonitorReportingOptions.StatusEventDelayMilliseconds / 1000 }; return new Insight { - Metadata = metadata, + Metadata = insightsMetadata, Data = startupData }; } diff --git a/src/Cassandra/DataStax/Insights/MessageFactories/InsightsStatusMessageFactory.cs b/src/Cassandra/DataStax/Insights/MessageFactories/InsightsStatusMessageFactory.cs index 0bfa519b5..98482774b 100644 --- a/src/Cassandra/DataStax/Insights/MessageFactories/InsightsStatusMessageFactory.cs +++ b/src/Cassandra/DataStax/Insights/MessageFactories/InsightsStatusMessageFactory.cs @@ -15,6 +15,8 @@ // using System.Collections.Generic; + +using Cassandra.Connections.Control; using Cassandra.DataStax.Insights.InfoProviders; using Cassandra.DataStax.Insights.Schema; using Cassandra.DataStax.Insights.Schema.StatusMessage; @@ -38,22 +40,22 @@ public InsightsStatusMessageFactory( _connectedNodesInfoProvider = connectedNodesInfoProvider; } - public Insight CreateMessage(IInternalCluster cluster, IInternalSession session) + public Insight CreateMessage(IInternalCluster cluster, IInternalSession session, IInternalMetadata internalMetadata) { - var metadata = _insightsMetadataFactory.CreateInsightsMetadata( + var insightsMetadata = _insightsMetadataFactory.CreateInsightsMetadata( InsightsStatusMessageFactory.StatusMessageName, InsightsStatusMessageFactory.StatusV1MappingId, InsightType.Event); var data = new InsightsStatusData { ClientId = cluster.Configuration.ClusterId.ToString(), SessionId = session.InternalSessionId.ToString(), - ControlConnection = cluster.Metadata.ControlConnection.EndPoint?.GetHostIpEndPointWithFallback().ToString(), - ConnectedNodes = _connectedNodesInfoProvider.GetInformation(cluster, session) + ControlConnection = internalMetadata.ControlConnection.EndPoint?.GetHostIpEndPointWithFallback().ToString(), + ConnectedNodes = _connectedNodesInfoProvider.GetInformation(cluster, session, internalMetadata) }; return new Insight { - Metadata = metadata, + Metadata = insightsMetadata, Data = data }; } diff --git a/src/Cassandra/Extensions.cs b/src/Cassandra/Extensions.cs index 377c45b39..970b5fc98 100644 --- a/src/Cassandra/Extensions.cs +++ b/src/Cassandra/Extensions.cs @@ -14,7 +14,10 @@ // limitations under the License. // +using System.Threading.Tasks; +using Cassandra.Connections.Control; using Cassandra.SessionManagement; +using Cassandra.Tasks; namespace Cassandra { @@ -27,7 +30,13 @@ namespace Cassandra public static class Extensions { /// + /// + /// This method will block if no connection has been opened yet. Please use if + /// your application uses the Task Parallel Library (e.g. async/await). + /// + /// /// Gets a snapshot containing information on the connections pools held by this Client at the current time. + /// /// /// The information provided in the returned object only represents the state at the moment this method was /// called and it's not maintained in sync with the driver metadata. @@ -38,12 +47,41 @@ public static class Extensions public static ISessionState GetState(this ISession instance) { var session = instance as IInternalSession; - return session == null ? SessionState.Empty() : SessionState.From(session); + + if (session == null) + { + return SessionState.Empty(); + } + + var metadata = session.TryInitAndGetMetadata(); + return SessionState.From(session, metadata); + } + + /// + /// Gets a snapshot containing information on the connections pools held by this Client at the current time. + /// + /// The information provided in the returned object only represents the state at the moment this method was + /// called and it's not maintained in sync with the driver metadata. + /// + /// + /// + /// + public static async Task GetStateAsync(this ISession instance) + { + var session = instance as IInternalSession; + + if (session == null) + { + return SessionState.Empty(); + } + + var metadata = await session.TryInitAndGetMetadataAsync().ConfigureAwait(false); + return SessionState.From(session, metadata); } - internal static ISessionState GetState(this IInternalSession instance) + internal static ISessionState GetState(this IInternalSession instance, IInternalMetadata internalMetadata) { - return SessionState.From(instance); + return SessionState.From(instance, internalMetadata); } } } \ No newline at end of file diff --git a/src/Cassandra/Helpers/ClusterInitializer.cs b/src/Cassandra/Helpers/ClusterInitializer.cs new file mode 100644 index 000000000..450021ae9 --- /dev/null +++ b/src/Cassandra/Helpers/ClusterInitializer.cs @@ -0,0 +1,382 @@ +// +// Copyright (C) DataStax Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Cassandra.Connections.Control; +using Cassandra.Requests; +using Cassandra.SessionManagement; +using Cassandra.Tasks; + +namespace Cassandra.Helpers +{ + internal class ClusterInitializer : IClusterInitializer + { + private const int Disposed = 10; + private const int Initialized = 5; + private const int Initializing = 1; + + private static readonly Logger Logger = new Logger(typeof(ClusterInitializer)); + + private readonly IInternalCluster _cluster; + private readonly IInternalMetadata _internalMetadata; + private readonly CancellationTokenSource _initCancellationTokenSource = new CancellationTokenSource(); + private readonly ISet _initCallbacks = + new HashSet(new ReferenceEqualityComparer()); + private readonly object _initCallbackLock = new object(); + + private volatile bool _initialized = false; + private volatile Task _initTask; + private volatile TaskCompletionSource _initTaskCompletionSource + = new TaskCompletionSource(); + + private volatile InitFatalErrorException _initException; + + private long _state = ClusterInitializer.Initializing; + + public ClusterInitializer( + IInternalCluster cluster, IInternalMetadata internalMetadata) + { + _cluster = cluster; + _internalMetadata = internalMetadata; + } + + public void Initialize() + { + _initTask = Task.Run(InitInternalAsync); + } + + /// + /// Called by the control connection after initializing + /// + /// + public async Task PostInitializeAsync() + { + await _cluster.PostInitializeAsync().ConfigureAwait(false); + } + + private static string GetAssemblyInfo() + { + var assembly = typeof(ISession).GetTypeInfo().Assembly; + var info = FileVersionInfo.GetVersionInfo(assembly.Location); + return $"{info.ProductName} v{info.FileVersion}"; + } + + public void RemoveCallback(ISessionInitializer callback) + { + if (_initialized) + { + return; + } + + if (IsDisposed) + { + return; + } + + lock (_initCallbackLock) + { + if (_initialized) + { + return; + } + + if (IsDisposed) + { + return; + } + + _initCallbacks.Remove(callback); + } + } + + public bool TryAttachInitCallback(ISessionInitializer callback) + { + if (_initialized) + { + return false; + } + + if (IsDisposed) + { + return false; + } + + lock (_initCallbackLock) + { + if (_initialized) + { + return false; + } + + if (IsDisposed) + { + return false; + } + + _initCallbacks.Add(callback); + return true; + } + } + + public bool IsDisposed => Interlocked.Read(ref _state) == ClusterInitializer.Disposed; + + private async Task InitInternalAsync() + { + ClusterInitializer.Logger.Info("Connecting to cluster using {0}", GetAssemblyInfo()); + try + { + var reconnectionSchedule = _cluster.Configuration.Policies.ReconnectionPolicy.NewSchedule(); + + do + { + try + { + await _internalMetadata.ControlConnection.InitAsync(this).ConfigureAwait(false); + break; + } + catch (Exception ex) + { + if (IsDisposed) + { + break; + } + + var currentTcs = _initTaskCompletionSource; + + var delay = reconnectionSchedule.NextDelayMs(); + ClusterInitializer.Logger.Error(ex, "Cluster initialization failed. Retrying in {0} ms.", delay); + Task.Run(() => currentTcs.TrySetException(ex)).Forget(); + + try + { + await Task.Delay( + TimeSpan.FromMilliseconds(delay), + _initCancellationTokenSource.Token).ConfigureAwait(false); + } + finally + { + _initTaskCompletionSource = new TaskCompletionSource(); + } + + } + } while (!IsDisposed); + + var previousState = Interlocked.CompareExchange(ref _state, ClusterInitializer.Initialized, ClusterInitializer.Initializing); + if (previousState == ClusterInitializer.Disposed) + { + await ShutdownInternalAsync().ConfigureAwait(false); + throw new ObjectDisposedException("Cluster instance was disposed before initialization finished."); + } + + _initialized = true; + + ClusterInitializer.Logger.Info("Cluster Connected using binary protocol version: [" + + _cluster.Configuration.SerializerManager.CurrentProtocolVersion + + "]"); + ClusterInitializer.Logger.Info("Cluster [" + _internalMetadata.ClusterName + "] has been initialized."); + + Task.Run(() => _initTaskCompletionSource.TrySetResult(_internalMetadata)).Forget(); + + Task.Run(async () => + { + IList callbacks; + lock (_initCallbackLock) + { + callbacks = new List(_initCallbacks); + _initCallbacks.Clear(); + } + + await Task.WhenAll(callbacks.Select(c => c.ClusterInitCallbackAsync())).ConfigureAwait(false); + }).Forget(); + return; + } + catch (Exception ex) + { + if (IsDisposed) + { + var newEx = new ObjectDisposedException("Cluster instance was disposed before initialization finished."); + Task.Run(() => _initTaskCompletionSource.TrySetException(newEx)).Forget(); + throw newEx; + } + + //There was an error that the driver is not able to recover from + //Store the exception for the following times + _initException = new InitFatalErrorException(ex); + //Throw the actual exception for the first time + Task.Run(() => _initTaskCompletionSource.TrySetException(ex)).Forget(); + throw; + } + } + + public void WaitInit() + { + if (_initialized) + { + //It was already initialized + return; + } + + WaitInitInternal(); + } + + public IInternalMetadata WaitInitAndGetMetadata() + { + if (_initialized) + { + //It was already initialized + return _internalMetadata; + } + + return WaitInitInternal(); + } + + public Task WaitInitAndGetMetadataAsync() + { + if (_initialized) + { + //It was already initialized + return Task.FromResult(_internalMetadata); + } + + return WaitInitInternalAsync(); + } + + public Task WaitInitAsync() + { + if (_initialized) + { + //It was already initialized + return TaskHelper.Completed; + } + + return WaitInitInternalAsync(); + } + private IInternalMetadata WaitInitInternal() + { + ValidateState(); + + var waiter = new TaskTimeoutHelper(_initTaskCompletionSource.Task); + if (waiter.WaitWithTimeout(_cluster.GetInitTimeout())) + { + return waiter.TaskToWait.GetAwaiter().GetResult(); + } + + throw new InitializationTimeoutException(); + } + + private async Task WaitInitInternalAsync() + { + ValidateState(); + + var waiter = new TaskTimeoutHelper(_initTaskCompletionSource.Task); + if (await waiter.WaitWithTimeoutAsync(_cluster.GetInitTimeout()).ConfigureAwait(false)) + { + return await waiter.TaskToWait.ConfigureAwait(false); + } + + throw new InitializationTimeoutException(); + } + + private void ValidateState() + { + var currentState = Interlocked.Read(ref _state); + if (currentState == ClusterInitializer.Disposed) + { + throw new ObjectDisposedException("This cluster object has been disposed."); + } + + if (_initException != null) + { + //There was an exception that is not possible to recover from + throw _initException; + } + } + + public async Task ShutdownAsync(int timeoutMs = Timeout.Infinite) + { + var previousState = Interlocked.Exchange(ref _state, ClusterInitializer.Disposed); + _initialized = false; + + if (previousState != ClusterInitializer.Disposed) + { + await _cluster.PreShutdownAsync(timeoutMs).ConfigureAwait(false); + } + + if (previousState == ClusterInitializer.Initializing) + { + _initCancellationTokenSource.Cancel(); + } + + try + { + await _initTask.ConfigureAwait(false); + } + catch (Exception) + { + // ignored + } + + _initialized = false; + + if (previousState != ClusterInitializer.Disposed) + { + _initCancellationTokenSource.Dispose(); + } + + if (previousState == ClusterInitializer.Initialized) + { + await ShutdownInternalAsync().ConfigureAwait(false); + ClusterInitializer.Logger.Info("Cluster [" + _internalMetadata.ClusterName + "] has been shut down."); + } + } + + private async Task ShutdownInternalAsync() + { + _internalMetadata.ControlConnection.Dispose(); + await _cluster.Configuration.ProtocolEventDebouncer.ShutdownAsync().ConfigureAwait(false); + await _cluster.PostShutdownAsync().ConfigureAwait(false); + } + } + + internal interface IClusterInitializer + { + bool TryAttachInitCallback(ISessionInitializer callback); + + void RemoveCallback(ISessionInitializer callback); + + bool IsDisposed { get; } + + void Initialize(); + + Task PostInitializeAsync(); + + void WaitInit(); + + IInternalMetadata WaitInitAndGetMetadata(); + + Task WaitInitAndGetMetadataAsync(); + + Task WaitInitAsync(); + + Task ShutdownAsync(int timeoutMs = Timeout.Infinite); + } +} \ No newline at end of file diff --git a/src/Cassandra/Helpers/SessionInitializer.cs b/src/Cassandra/Helpers/SessionInitializer.cs new file mode 100644 index 000000000..1331cdcc0 --- /dev/null +++ b/src/Cassandra/Helpers/SessionInitializer.cs @@ -0,0 +1,284 @@ +// +// Copyright (C) DataStax Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Threading; +using System.Threading.Tasks; + +using Cassandra.Connections.Control; +using Cassandra.Requests; +using Cassandra.SessionManagement; +using Cassandra.Tasks; + +namespace Cassandra.Helpers +{ + internal class SessionInitializer : ISessionInitializer + { + private const int Disposed = 10; + private const int Initialized = 5; + private const int Initializing = 1; + + private static readonly Logger Logger = new Logger(typeof(SessionInitializer)); + + private readonly IInternalSession _session; + private readonly CancellationTokenSource _initCancellationTokenSource = new CancellationTokenSource(); + + private volatile bool _initialized = false; + private volatile Task _initTask; + + private volatile TaskCompletionSource _initTaskCompletionSource + = new TaskCompletionSource(); + + private volatile InitFatalErrorException _initException; + + private volatile bool _addedCallback = true; + + private long _state = SessionInitializer.Initializing; + + public SessionInitializer(IInternalSession session) + { + _session = session; + } + + public bool IsInitialized => _initialized; + + public void Initialize() + { + if (_session.InternalCluster.ClusterInitializer.TryAttachInitCallback(this)) + { + _addedCallback = true; + return; + } + + if (_session.InternalCluster.ClusterInitializer.IsDisposed) + { + return; + } + + _initTask = Task.Run(InitInternalAsync); + } + + public Task ClusterInitCallbackAsync() + { + return InitInternalAsync(); + } + + public bool IsDisposed => Interlocked.Read(ref _state) == SessionInitializer.Disposed; + + private async Task InitInternalAsync() + { + _addedCallback = false; + + SessionInitializer.Logger.Info("Connecting to session [{0}]", _session.SessionName); + try + { + if (IsDisposed) + { + throw new ObjectDisposedException("Session instance was disposed before initialization finished."); + } + + await _session.PostInitializeAsync().ConfigureAwait(false); + + var previousState = Interlocked.CompareExchange(ref _state, SessionInitializer.Initialized, SessionInitializer.Initializing); + if (previousState == SessionInitializer.Disposed) + { + await _session.OnShutdownAsync().ConfigureAwait(false); + throw new ObjectDisposedException("Session instance was disposed before initialization finished."); + } + + _initialized = true; + + SessionInitializer.Logger.Info("Session [{0}] has been initialized.", _session.SessionName); + + Task.Run(() => _initTaskCompletionSource.TrySetResult(_session.InternalCluster.InternalMetadata)).Forget(); + return; + } + catch (Exception ex) + { + if (IsDisposed) + { + var newEx = new ObjectDisposedException("Session instance was disposed before initialization finished."); + Task.Run(() => _initTaskCompletionSource.TrySetException(newEx)).Forget(); + throw newEx; + } + + //There was an error that the driver is not able to recover from + //Store the exception for the following times + _initException = new InitFatalErrorException(ex); + //Throw the actual exception for the first time + Task.Run(() => _initTaskCompletionSource.TrySetException(ex)).Forget(); + throw; + } + } + + public void WaitInit() + { + if (_initialized) + { + //It was already initialized + return; + } + + WaitInitInternal(); + } + + public IInternalMetadata WaitInitAndGetMetadata() + { + if (_initialized) + { + //It was already initialized + return _session.InternalCluster.InternalMetadata; + } + + return WaitInitInternal(); + } + + public Task WaitInitAndGetMetadataAsync() + { + if (_initialized) + { + //It was already initialized + return Task.FromResult(_session.InternalCluster.InternalMetadata); + } + + return WaitInitInternalAsync(); + } + + public Task WaitInitAsync() + { + if (_initialized) + { + //It was already initialized + return TaskHelper.Completed; + } + + return WaitInitInternalAsync(); + } + + private IInternalMetadata WaitInitInternal() + { + ValidateState(); + var waiter = new TaskTimeoutHelper( + new[] + { + _session.InternalCluster.ClusterInitializer.WaitInitAndGetMetadataAsync(), + _initTaskCompletionSource.Task + }); + + if (waiter.WaitWithTimeout(_session.InternalCluster.GetInitTimeout())) + { + return waiter.TaskToWait.GetAwaiter().GetResult(); + } + + throw new InitializationTimeoutException(); + } + + private async Task WaitInitInternalAsync() + { + ValidateState(); + + var waiter = new TaskTimeoutHelper( + new[] + { + _session.InternalCluster.ClusterInitializer.WaitInitAndGetMetadataAsync(), + _initTaskCompletionSource.Task + }); + + if (await waiter.WaitWithTimeoutAsync(_session.InternalCluster.GetInitTimeout()).ConfigureAwait(false)) + { + return await waiter.TaskToWait.ConfigureAwait(false); + } + + throw new InitializationTimeoutException(); + } + + private void ValidateState() + { + var currentState = Interlocked.Read(ref _state); + if (currentState == SessionInitializer.Disposed) + { + throw new ObjectDisposedException("This session object has been disposed."); + } + + if (_initException != null) + { + //There was an exception that is not possible to recover from + throw _initException; + } + } + + public async Task ShutdownAsync(int timeoutMs = Timeout.Infinite) + { + var previousState = Interlocked.Exchange(ref _state, SessionInitializer.Disposed); + _initialized = false; + + if (previousState == SessionInitializer.Initializing) + { + _initCancellationTokenSource.Cancel(); + } + + try + { + if (_initTask != null) + { + await _initTask.ConfigureAwait(false); + } + } + catch (Exception) + { + // ignored + } + + _initialized = false; + + if (_addedCallback) + { + _session.InternalCluster.ClusterInitializer.RemoveCallback(this); + } + + if (previousState != SessionInitializer.Disposed) + { + _initCancellationTokenSource.Dispose(); + await _session.InternalCluster.OnSessionShutdownAsync(_session).ConfigureAwait(false); + } + + if (previousState == SessionInitializer.Initialized) + { + await _session.OnShutdownAsync().ConfigureAwait(false); + SessionInitializer.Logger.Info("Session [{0}] has been shut down.", _session.SessionName); + } + } + } + + internal interface ISessionInitializer + { + bool IsDisposed { get; } + bool IsInitialized { get; } + + void Initialize(); + + void WaitInit(); + + IInternalMetadata WaitInitAndGetMetadata(); + + Task WaitInitAndGetMetadataAsync(); + + Task WaitInitAsync(); + + Task ShutdownAsync(int timeoutMs = Timeout.Infinite); + + Task ClusterInitCallbackAsync(); + } +} \ No newline at end of file diff --git a/src/Cassandra/ICluster.cs b/src/Cassandra/ICluster.cs index 84a74a922..8014c359c 100644 --- a/src/Cassandra/ICluster.cs +++ b/src/Cassandra/ICluster.cs @@ -15,8 +15,6 @@ // using System; -using System.Collections.Generic; -using System.Net; using System.Threading; using System.Threading.Tasks; @@ -31,9 +29,9 @@ namespace Cassandra /// Cassandra cluster would be: /// /// - /// Cluster cluster = Cluster.Builder().AddContactPoint("192.168.0.1").Build(); - /// Session session = cluster.Connect("db1"); - /// foreach (var row in session.Execute("SELECT * FROM table1")) + /// Cluster cluster = Cluster.Builder().AddContactPoint("192.168.0.1").Build(); + /// Session session = cluster.Connect("db1"); + /// foreach (var row in session.Execute("SELECT * FROM table1")) /// // do something ... /// /// @@ -46,36 +44,21 @@ namespace Cassandra public interface ICluster : IDisposable { /// - /// Gets read-only metadata on the connected cluster. - /// This includes the - /// know nodes (with their status as seen by the driver) as well as the schema - /// definitions. + /// + /// Gets an utility class that allows you to fetch metadata about the connected cluster. + /// This includes the known nodes (with their status as seen by the driver) as well as the schema definitions. /// - /// This method may trigger the creation of a connection if none has been established yet. + /// + /// It also allows you to subscribe to certain events (e.g. ). /// /// - Metadata Metadata { get; } + IMetadata Metadata { get; } /// /// Cluster client configuration /// Configuration Configuration { get; } - - /// - /// Returns all known hosts of this cluster. - /// - ICollection AllHosts(); - - /// - /// Event that gets triggered when a new host is added to the cluster - /// - event Action HostAdded; - - /// - /// Event that gets triggered when a host has been removed from the cluster - /// - event Action HostRemoved; - + /// /// Creates a new session on this cluster. /// @@ -100,52 +83,18 @@ public interface ICluster : IDisposable /// Case-sensitive keyspace name to use Task ConnectAsync(string keyspace); - /// - /// Get the host instance for a given Ip address. - /// - /// Ip address of the host - /// The host or null if not found - Host GetHost(IPEndPoint address); - - /// - /// Gets a collection of replicas for a given partitionKey. Backward-compatibility only, use GetReplicas(keyspace, partitionKey) instead. - /// - /// Byte array representing the partition key - /// - ICollection GetReplicas(byte[] partitionKey); - - /// - /// Gets a collection of replicas for a given partitionKey on a given keyspace - /// - /// Byte array representing the partition key - /// Byte array representing the partition key - /// - ICollection GetReplicas(string keyspace, byte[] partitionKey); - /// /// Shutdown this cluster instance. This closes all connections from all the /// sessions of this * Cluster instance and reclaim all resources /// used by it.

This method has no effect if the cluster was already shutdown.

///
void Shutdown(int timeoutMs = Timeout.Infinite); - + /// /// Shutdown this cluster instance asynchronously. This closes all connections from all the /// sessions of this * Cluster instance and reclaim all resources /// used by it.

This method has no effect if the cluster was already shutdown.

///
Task ShutdownAsync(int timeoutMs = Timeout.Infinite); - - /// - /// Updates keyspace metadata (including token metadata for token aware routing) for a given keyspace or a specific keyspace table. - /// If no keyspace is provided then this method will update the metadata and token map for all the keyspaces of the cluster. - /// - Task RefreshSchemaAsync(string keyspace = null, string table = null); - - /// - /// Updates keyspace metadata (including token metadata for token aware routing) for a given keyspace or a specific keyspace table. - /// If no keyspace is provided then this method will update the metadata and token map for all the keyspaces of the cluster. - /// - bool RefreshSchema(string keyspace = null, string table = null); } -} +} \ No newline at end of file diff --git a/src/Cassandra/IMetadata.cs b/src/Cassandra/IMetadata.cs new file mode 100644 index 000000000..dabcd0cc1 --- /dev/null +++ b/src/Cassandra/IMetadata.cs @@ -0,0 +1,210 @@ +// +// Copyright (C) DataStax Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; + +namespace Cassandra +{ + /// + /// Allows for fetching of metadata about the connected cluster, including known nodes and schema definitions. + /// + public interface IMetadata : IMetadataSnapshotProvider + { + Task GetClusterDescriptionAsync(); + + ClusterDescription GetClusterDescription(); + + Host GetHost(IPEndPoint address); + + Task GetHostAsync(IPEndPoint address); + + /// + /// Returns all known hosts of this cluster. + /// + /// collection of all known hosts of this cluster. + ICollection AllHosts(); + + /// + /// Returns all known hosts of this cluster. + /// + /// collection of all known hosts of this cluster. + Task> AllHostsAsync(); + + IEnumerable AllReplicas(); + + Task> AllReplicasAsync(); + + /// + /// Get the replicas for a given partition key and keyspace + /// + ICollection GetReplicas(string keyspaceName, byte[] partitionKey); + + /// + /// Get the replicas for a given partition key + /// + ICollection GetReplicas(byte[] partitionKey); + + /// + /// Get the replicas for a given partition key and keyspace + /// + Task> GetReplicasAsync(string keyspaceName, byte[] partitionKey); + + /// + /// Get the replicas for a given partition key + /// + Task> GetReplicasAsync(byte[] partitionKey); + + /// + /// Returns metadata of specified keyspace. + /// + /// the name of the keyspace for which metadata should be + /// returned. + /// the metadata of the requested keyspace or null if + /// * keyspace is not a known keyspace. + KeyspaceMetadata GetKeyspace(string keyspace); + + /// + /// Returns metadata of specified keyspace. + /// + /// the name of the keyspace for which metadata should be + /// returned. + /// the metadata of the requested keyspace or null if + /// * keyspace is not a known keyspace. + Task GetKeyspaceAsync(string keyspace); + + /// + /// Returns a collection of all defined keyspaces names. + /// + /// a collection of all defined keyspaces names. + ICollection GetKeyspaces(); + + /// + /// Returns a collection of all defined keyspaces names. + /// + /// a collection of all defined keyspaces names. + Task> GetKeyspacesAsync(); + + /// + /// Returns names of all tables which are defined within specified keyspace. + /// + /// the name of the keyspace for which all tables metadata should be + /// returned. + /// an ICollection of the metadata for the tables defined in this + /// keyspace. + ICollection GetTables(string keyspace); + + /// + /// Returns names of all tables which are defined within specified keyspace. + /// + /// the name of the keyspace for which all tables metadata should be + /// returned. + /// an ICollection of the metadata for the tables defined in this + /// keyspace. + Task> GetTablesAsync(string keyspace); + + /// + /// Returns TableMetadata for specified table in specified keyspace. + /// + /// name of the keyspace within specified table is defined. + /// name of table for which metadata should be returned. + /// a TableMetadata for the specified table in the specified keyspace. + TableMetadata GetTable(string keyspace, string tableName); + + /// + /// Returns TableMetadata for specified table in specified keyspace. + /// + /// name of the keyspace within specified table is defined. + /// name of table for which metadata should be returned. + /// a TableMetadata for the specified table in the specified keyspace. + Task GetTableAsync(string keyspace, string tableName); + + /// + /// Returns the view metadata for the provided view name in the keyspace. + /// + /// name of the keyspace within specified view is defined. + /// name of view. + /// a MaterializedViewMetadata for the view in the specified keyspace. + MaterializedViewMetadata GetMaterializedView(string keyspace, string name); + + /// + /// Returns the view metadata for the provided view name in the keyspace. + /// + /// name of the keyspace within specified view is defined. + /// name of view. + /// a MaterializedViewMetadata for the view in the specified keyspace. + Task GetMaterializedViewAsync(string keyspace, string name); + + /// + /// Gets the definition associated with a User Defined Type from Cassandra + /// + UdtColumnInfo GetUdtDefinition(string keyspace, string typeName); + + /// + /// Gets the definition associated with a User Defined Type from Cassandra + /// + Task GetUdtDefinitionAsync(string keyspace, string typeName); + + /// + /// Gets the definition associated with a User Defined Function from Cassandra + /// + /// The function metadata or null if not found. + FunctionMetadata GetFunction(string keyspace, string name, string[] signature); + + /// + /// Gets the definition associated with a User Defined Function from Cassandra + /// + /// The function metadata or null if not found. + Task GetFunctionAsync(string keyspace, string name, string[] signature); + + /// + /// Gets the definition associated with a aggregate from Cassandra + /// + /// The aggregate metadata or null if not found. + AggregateMetadata GetAggregate(string keyspace, string name, string[] signature); + + /// + /// Gets the definition associated with a aggregate from Cassandra + /// + /// The aggregate metadata or null if not found. + Task GetAggregateAsync(string keyspace, string name, string[] signature); + + /// + /// Updates keyspace metadata (including token metadata for token aware routing) for a given keyspace or a specific keyspace table. + /// If no keyspace is provided then this method will update the metadata and token map for all the keyspaces of the cluster. + /// + bool RefreshSchema(string keyspace = null, string table = null); + + /// + /// Updates keyspace metadata (including token metadata for token aware routing) for a given keyspace or a specific keyspace table. + /// If no keyspace is provided then this method will update the metadata and token map for all the keyspaces of the cluster. + /// + Task RefreshSchemaAsync(string keyspace = null, string table = null); + + /// + /// Initiates a schema agreement check. + /// + /// Schema changes need to be propagated to all nodes in the cluster. + /// Once they have settled on a common version, we say that they are in agreement. + /// + /// This method does not perform retries so + /// does not apply. + /// + /// True if schema agreement was successful and false if it was not successful. + Task CheckSchemaAgreementAsync(); + } +} \ No newline at end of file diff --git a/src/Cassandra/IMetadataQueryProvider.cs b/src/Cassandra/IMetadataQueryProvider.cs index 597081b09..307d98b16 100644 --- a/src/Cassandra/IMetadataQueryProvider.cs +++ b/src/Cassandra/IMetadataQueryProvider.cs @@ -28,8 +28,6 @@ namespace Cassandra ///
internal interface IMetadataQueryProvider { - ProtocolVersion ProtocolVersion { get; } - /// /// The address of the endpoint used by the ControlConnection /// @@ -39,9 +37,7 @@ internal interface IMetadataQueryProvider /// The local address of the socket used by the ControlConnection ///
IPEndPoint LocalAddress { get; } - - ISerializerManager Serializer { get; } - + Task> QueryAsync(string cqlQuery, bool retry = false); Task SendQueryRequestAsync(string cqlQuery, bool retry, QueryProtocolOptions queryProtocolOptions); @@ -50,7 +46,5 @@ internal interface IMetadataQueryProvider /// Send request without any retry or reconnection logic. Also exceptions are not caught or logged. /// Task UnsafeSendQueryRequestAsync(string cqlQuery, QueryProtocolOptions queryProtocolOptions); - - IEnumerable Query(string cqlQuery, bool retry = false); } } \ No newline at end of file diff --git a/src/Cassandra/IMetadataSnapshotProvider.cs b/src/Cassandra/IMetadataSnapshotProvider.cs new file mode 100644 index 000000000..8575d11b1 --- /dev/null +++ b/src/Cassandra/IMetadataSnapshotProvider.cs @@ -0,0 +1,88 @@ +// +// Copyright (C) DataStax Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.Net; + +namespace Cassandra +{ + public interface IMetadataSnapshotProvider + { + event HostsEventHandler HostsEvent; + + event SchemaChangedEventHandler SchemaChangedEvent; + + /// + /// Event that gets triggered when a new host is added to the cluster + /// + event Action HostAdded; + + /// + /// Event that gets triggered when a host has been removed from the cluster + /// + event Action HostRemoved; + + Configuration Configuration { get; } + + /// + /// + /// Returns all known hosts of this cluster from the driver's cache. The driver's cache + /// is kept up to date using server protocol events so it will not be populated until the metadata initialization is done + /// and a connection is open. + /// + /// + /// This method might return an empty collection if the metadata initialization has not finished yet. + /// + /// + ICollection AllHostsSnapshot(); + + /// + /// + /// Get the replicas without performing any I/O (it will use the driver's cache). The driver's cache + /// is kept up to date using server protocol events so it will not be populated until the metadata initialization is done + /// and a connection is open. + /// + /// + /// This method might return an empty collection if the metadata initialization has not finished yet. + /// + /// + IEnumerable AllReplicasSnapshot(); + + /// + /// + /// Get the replicas for a given keyspace and partition key without performing any I/O (it will use the driver's cache). + /// The driver's cache is kept up to date using server protocol events so it will not be populated + /// until the metadata initialization is done and a connection is open. + /// + /// + /// This method might return an empty collection if the metadata initialization has not finished yet. + /// + /// + ICollection GetReplicasSnapshot(string keyspaceName, byte[] partitionKey); + + /// + /// + /// Get the replicas for a partition key without performing any I/O (it will use the driver's cache). + /// The driver's cache is kept up to date using server protocol events so it will not be populated + /// until the metadata initialization is done and a connection is open. + /// + /// + /// This method might return an empty collection if the metadata initialization has not finished yet. + /// + /// + ICollection GetReplicasSnapshot(byte[] partitionKey); + } +} \ No newline at end of file diff --git a/src/Cassandra/ISession.cs b/src/Cassandra/ISession.cs index 9f9e3f8f7..6e9053270 100644 --- a/src/Cassandra/ISession.cs +++ b/src/Cassandra/ISession.cs @@ -39,11 +39,6 @@ namespace Cassandra /// public interface ISession: IDisposable { - /// - /// Gets the Cassandra native binary protocol version - /// - int BinaryProtocolVersion { get; } - /// /// Gets the cluster information and state /// @@ -70,6 +65,39 @@ public interface ISession: IDisposable /// string SessionName { get; } + /// + /// + /// Waits until the initialization task is finished (this task is started when the Session is created). + /// If the session is already initialized, this method returns without blocking. + /// + /// + /// It is not necessary to call this method but you can use it if you want the initialization to happen + /// in a specific state of your application (e.g. during startup before your application listens for requests). + /// + /// + /// If your application uses the Task Parallel Library (e.g. async/await) please use instead. + /// + /// + /// If the initialization failed. + /// If the initialization timed out. + /// If further attempts to connect are made after initialization has failed. + void Connect(); + + /// + /// + /// Waits until the initialization task is finished (this task is started when the Session is created). + /// If the session is already initialized, this method returns a completed Task. + /// + /// + /// It is not necessary to call this method but you can use it if you want the initialization to happen + /// in a specific state of your application (e.g. during startup before your application listens for requests). + /// + /// + /// If the initialization failed. + /// If the initialization timed out. + /// If further attempts to connect are made after initialization has failed. + Task ConnectAsync(); + /// /// Begins asynchronous execute operation. /// @@ -318,10 +346,5 @@ public interface ISession: IDisposable /// Disposes the session asynchronously. /// Task ShutdownAsync(); - - [Obsolete("Method deprecated. The driver internally waits for schema agreement when there is an schema change. See ProtocolOptions.MaxSchemaAgreementWaitSeconds for more info.")] - void WaitForSchemaAgreement(RowSet rs); - [Obsolete("Method deprecated. The driver internally waits for schema agreement when there is an schema change. See ProtocolOptions.MaxSchemaAgreementWaitSeconds for more info.")] - bool WaitForSchemaAgreement(IPEndPoint forHost); } } diff --git a/src/Cassandra/InitializationErrorException.cs b/src/Cassandra/InitializationErrorException.cs new file mode 100644 index 000000000..aa47881b1 --- /dev/null +++ b/src/Cassandra/InitializationErrorException.cs @@ -0,0 +1,37 @@ +// +// Copyright (C) DataStax Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Runtime.Serialization; + +namespace Cassandra +{ + [Serializable] + public class InitializationErrorException : DriverException + { + internal InitializationErrorException(string message) : base(message) + { + } + + internal InitializationErrorException(string message, Exception innerException) : base(message, innerException) + { + } + + protected InitializationErrorException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/src/Cassandra/InitializationTimeoutException.cs b/src/Cassandra/InitializationTimeoutException.cs new file mode 100644 index 000000000..81a3f6b1f --- /dev/null +++ b/src/Cassandra/InitializationTimeoutException.cs @@ -0,0 +1,45 @@ +// +// Copyright (C) DataStax Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Runtime.Serialization; + +namespace Cassandra +{ + [Serializable] + public class InitializationTimeoutException : DriverException + { + internal InitializationTimeoutException() : base("Timed out while waiting for cluster initialization to finish. This mechanism is put in place to" + + " avoid blocking the calling thread forever. This usually caused by a networking issue" + + " between the client driver instance and the cluster. You can increase this timeout via " + + "the SocketOptions.ConnectTimeoutMillis config setting. This can also be related to deadlocks " + + "caused by mixing synchronous and asynchronous code.") + { + } + + internal InitializationTimeoutException(string message) : base(message) + { + } + + internal InitializationTimeoutException(string message, Exception innerException) : base(message, innerException) + { + } + + protected InitializationTimeoutException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/src/Cassandra/KeyspaceMetadata.cs b/src/Cassandra/KeyspaceMetadata.cs index 13f6ac536..ce9e84cb1 100644 --- a/src/Cassandra/KeyspaceMetadata.cs +++ b/src/Cassandra/KeyspaceMetadata.cs @@ -20,6 +20,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Cassandra.Connections.Control; using Cassandra.MetadataHelpers; using Cassandra.Tasks; @@ -31,7 +32,7 @@ public class KeyspaceMetadata private readonly ConcurrentDictionary _views = new ConcurrentDictionary(); private readonly ConcurrentDictionary, FunctionMetadata> _functions = new ConcurrentDictionary, FunctionMetadata>(); private readonly ConcurrentDictionary, AggregateMetadata> _aggregates = new ConcurrentDictionary, AggregateMetadata>(); - private readonly Metadata _parent; + private readonly IInternalMetadata _parent; /// /// Gets the name of this keyspace. @@ -73,14 +74,14 @@ public class KeyspaceMetadata internal IReplicationStrategy Strategy { get; } - internal KeyspaceMetadata(Metadata parent, string name, bool durableWrites, string strategyClass, + internal KeyspaceMetadata(IInternalMetadata parent, string name, bool durableWrites, string strategyClass, IDictionary replicationOptions, bool isVirtual = false) : this(parent, name, durableWrites, strategyClass, replicationOptions, new ReplicationStrategyFactory(), isVirtual) { } internal KeyspaceMetadata( - Metadata parent, + IInternalMetadata parent, string name, bool durableWrites, string strategyClass, @@ -127,8 +128,14 @@ public TableMetadata GetTableMetadata(string tableName) return TaskHelper.WaitToComplete( GetTableMetadataAsync(tableName), _parent.Configuration.DefaultRequestOptions.GetQueryAbortTimeout(2)); } - - internal async Task GetTableMetadataAsync(string tableName) + + /// + /// Returns metadata of specified table in this keyspace. + /// + /// the name of table to retrieve + /// the metadata for table tableName in this keyspace if it + /// exists, null otherwise. + public async Task GetTableMetadataAsync(string tableName) { if (_tables.TryGetValue(tableName, out var tableMetadata)) { @@ -159,8 +166,14 @@ public MaterializedViewMetadata GetMaterializedViewMetadata(string viewName) return TaskHelper.WaitToComplete( GetMaterializedViewMetadataAsync(viewName), _parent.Configuration.DefaultRequestOptions.GetQueryAbortTimeout(2)); } - - private async Task GetMaterializedViewMetadataAsync(string viewName) + + /// + /// Returns metadata of specified view in this keyspace. + /// + /// the name of view to retrieve + /// the metadata for view viewName in this keyspace if it + /// exists, null otherwise. + public async Task GetMaterializedViewMetadataAsync(string viewName) { if (_views.TryGetValue(viewName, out var v)) { @@ -218,11 +231,20 @@ internal void ClearAggregate(string name, string[] signature) /// keyspace. public IEnumerable GetTablesMetadata() { - var tableNames = GetTablesNames(); + return TaskHelper.WaitToComplete(GetTablesMetadataAsync()); + } + + /// + /// Returns metadata of all tables defined in this keyspace. + /// + /// an IEnumerable of TableMetadata for the tables defined in this + /// keyspace. + public async Task> GetTablesMetadataAsync() + { + var tableNames = await GetTablesNamesAsync().ConfigureAwait(false); return tableNames.Select(GetTableMetadata); } - - + /// /// Returns names of all tables defined in this keyspace. /// @@ -231,7 +253,18 @@ public IEnumerable GetTablesMetadata() /// keyspace tables names. public ICollection GetTablesNames() { - return TaskHelper.WaitToComplete(_parent.SchemaParser.GetTableNamesAsync(Name)); + return TaskHelper.WaitToComplete(GetTablesNamesAsync()); + } + + /// + /// Returns names of all tables defined in this keyspace. + /// + /// + /// a collection of all, defined in this + /// keyspace tables names. + public Task> GetTablesNamesAsync() + { + return _parent.SchemaParser.GetTableNamesAsync(Name); } /// @@ -305,8 +338,12 @@ public FunctionMetadata GetFunction(string functionName, string[] signature) return TaskHelper.WaitToComplete( GetFunctionAsync(functionName, signature), _parent.Configuration.DefaultRequestOptions.QueryAbortTimeout); } - - private async Task GetFunctionAsync(string functionName, string[] signature) + + /// + /// Gets a CQL function by name and signature + /// + /// The function metadata or null if not found. + public async Task GetFunctionAsync(string functionName, string[] signature) { if (signature == null) { @@ -339,8 +376,12 @@ public AggregateMetadata GetAggregate(string aggregateName, string[] signature) return TaskHelper.WaitToComplete( GetAggregateAsync(aggregateName, signature), _parent.Configuration.DefaultRequestOptions.QueryAbortTimeout); } - - private async Task GetAggregateAsync(string aggregateName, string[] signature) + + /// + /// Gets a CQL aggregate by name and signature + /// + /// The aggregate metadata or null if not found. + public async Task GetAggregateAsync(string aggregateName, string[] signature) { if (signature == null) { diff --git a/src/Cassandra/Logger.cs b/src/Cassandra/Logger.cs index 458f1d7c6..02dd354b1 100644 --- a/src/Cassandra/Logger.cs +++ b/src/Cassandra/Logger.cs @@ -64,6 +64,11 @@ public void Error(string message, params object[] args) _loggerHandler.Error(message, args); } + public void Error(Exception ex, string message, params object[] args) + { + _loggerHandler.Error(ex, message, args); + } + public void Warning(string message, params object[] args) { _loggerHandler.Warning(message, args); @@ -86,6 +91,7 @@ internal interface ILoggerHandler { void Error(Exception ex); void Error(string message, Exception ex = null); + void Error(Exception ex, string message, params object[] args); void Error(string message, params object[] args); void Verbose(string message, params object[] args); void Info(string message, params object[] args); @@ -116,6 +122,11 @@ public void Error(string message, params object[] args) _logger.LogError(message, args); } + public void Error(Exception ex, string message, params object[] args) + { + _logger.LogError(0, ex, message, args); + } + public void Verbose(string message, params object[] args) { _logger.LogDebug(message, args); @@ -211,6 +222,21 @@ public void Error(string message, params object[] args) Trace.WriteLine(string.Format("{0} #ERROR: {1}", DateTimeOffset.Now.DateTime.ToString(DateFormat), message), _category); } + public void Error(Exception ex, string message, params object[] args) + { + if (!Diagnostics.CassandraTraceSwitch.TraceError) + { + return; + } + if (args != null && args.Length > 0) + { + message = string.Format(message, args); + } + Trace.WriteLine( + string.Format("{0} #ERROR: {1}", DateTimeOffset.Now.DateTime.ToString(DateFormat), + message + (ex != null ? "\nEXCEPTION:\n " + GetExceptionAndAllInnerEx(ex) : string.Empty)), _category); + } + public void Warning(string message, params object[] args) { if (!Diagnostics.CassandraTraceSwitch.TraceWarning) diff --git a/src/Cassandra/Mapping/Mapper.cs b/src/Cassandra/Mapping/Mapper.cs index 8efe6d6b0..d154e0ab0 100644 --- a/src/Cassandra/Mapping/Mapper.cs +++ b/src/Cassandra/Mapping/Mapper.cs @@ -428,17 +428,18 @@ public ICqlBatch CreateBatch(BatchType batchType) /// public void Execute(ICqlBatch batch) { - //Wait async method to be completed or throw - TaskHelper.WaitToCompleteWithMetrics(_metricsManager, ExecuteAsync(batch), _queryAbortTimeout); + Execute(batch, Configuration.DefaultExecutionProfileName); } /// public void Execute(ICqlBatch batch, string executionProfile) { + _session.Connect(); + //Wait async method to be completed or throw TaskHelper.WaitToCompleteWithMetrics(_metricsManager, ExecuteAsync(batch, executionProfile), _queryAbortTimeout); } - + /// public Task ExecuteAsync(ICqlBatch batch) { @@ -479,6 +480,7 @@ public AppliedInfo DeleteIf(string cql, params object[] args) /// public AppliedInfo DeleteIf(Cql cql) { + _session.Connect(); return TaskHelper.WaitToCompleteWithMetrics(_metricsManager, DeleteIfAsync(cql), _queryAbortTimeout); } @@ -511,6 +513,8 @@ public IEnumerable Fetch(string cql, params object[] args) /// public IEnumerable Fetch(Cql cql) { + _session.Connect(); + //Use the async method var t = FetchAsync(cql); //Wait for it to be completed or throw @@ -533,6 +537,8 @@ public IPage FetchPage(int pageSize, byte[] pagingState, string cql, param /// public IPage FetchPage(Cql cql) { + _session.Connect(); + //Use the async method var t = FetchPageAsync(cql); //Wait for it to be completed or throw @@ -549,6 +555,8 @@ public T Single(string cql, params object[] args) /// public T Single(Cql cql) { + _session.Connect(); + //Use the async method var t = SingleAsync(cql); //Wait for it to be completed or throw @@ -565,6 +573,8 @@ public T SingleOrDefault(string cql, params object[] args) /// public T SingleOrDefault(Cql cql) { + _session.Connect(); + //Use async method var t = SingleOrDefaultAsync(cql); //Wait for it to be completed or throw @@ -581,6 +591,8 @@ public T First(string cql, params object[] args) /// public T First(Cql cql) { + _session.Connect(); + //Use async method var t = FirstAsync(cql); //Wait for it to be completed or throw @@ -597,6 +609,8 @@ public T FirstOrDefault(string cql, params object[] args) /// public T FirstOrDefault(Cql cql) { + _session.Connect(); + //Use async method var t = FirstOrDefaultAsync(cql); //Wait for it to be completed or throw @@ -642,6 +656,8 @@ public void Insert(T poco, string executionProfile, bool insertNulls, int? tt throw new ArgumentNullException(nameof(executionProfile)); } + _session.Connect(); + //Wait async method to be completed or throw TaskHelper.WaitToCompleteWithMetrics(_metricsManager, InsertAsync(poco, executionProfile, insertNulls, ttl, queryOptions), _queryAbortTimeout); } @@ -673,18 +689,21 @@ public AppliedInfo InsertIfNotExists(T poco, string executionProfile, bool /// public AppliedInfo InsertIfNotExists(T poco, bool insertNulls, int? ttl, CqlQueryOptions queryOptions = null) { + _session.Connect(); return TaskHelper.WaitToCompleteWithMetrics(_metricsManager, InsertIfNotExistsAsync(poco, insertNulls, ttl, queryOptions), _queryAbortTimeout); } /// public AppliedInfo InsertIfNotExists(T poco, string executionProfile, bool insertNulls, int? ttl, CqlQueryOptions queryOptions = null) { + _session.Connect(); return TaskHelper.WaitToCompleteWithMetrics(_metricsManager, InsertIfNotExistsAsync(poco, executionProfile, insertNulls, ttl, queryOptions), _queryAbortTimeout); } /// public void Update(T poco, CqlQueryOptions queryOptions = null) { + _session.Connect(); //Wait async method to be completed or throw TaskHelper.WaitToCompleteWithMetrics(_metricsManager, UpdateAsync(poco, queryOptions), _queryAbortTimeout); } @@ -692,6 +711,7 @@ public void Update(T poco, CqlQueryOptions queryOptions = null) /// public void Update(T poco, string executionProfile, CqlQueryOptions queryOptions = null) { + _session.Connect(); //Wait async method to be completed or throw TaskHelper.WaitToCompleteWithMetrics(_metricsManager, UpdateAsync(poco, executionProfile, queryOptions), _queryAbortTimeout); } @@ -705,6 +725,7 @@ public void Update(string cql, params object[] args) /// public void Update(Cql cql) { + _session.Connect(); //Wait async method to be completed or throw TaskHelper.WaitToCompleteWithMetrics(_metricsManager, UpdateAsync(cql), _queryAbortTimeout); } @@ -718,6 +739,7 @@ public AppliedInfo UpdateIf(string cql, params object[] args) /// public AppliedInfo UpdateIf(Cql cql) { + _session.Connect(); //Wait async method to be completed or throw return TaskHelper.WaitToCompleteWithMetrics(_metricsManager, UpdateIfAsync(cql), _queryAbortTimeout); } @@ -725,6 +747,7 @@ public AppliedInfo UpdateIf(Cql cql) /// public void Delete(T poco, CqlQueryOptions queryOptions = null) { + _session.Connect(); //Wait async method to be completed or throw TaskHelper.WaitToCompleteWithMetrics(_metricsManager, DeleteAsync(poco, queryOptions), _queryAbortTimeout); } @@ -732,6 +755,7 @@ public void Delete(T poco, CqlQueryOptions queryOptions = null) /// public void Delete(T poco, string executionProfile, CqlQueryOptions queryOptions = null) { + _session.Connect(); //Wait async method to be completed or throw TaskHelper.WaitToCompleteWithMetrics(_metricsManager, DeleteAsync(poco, executionProfile, queryOptions), _queryAbortTimeout); } @@ -745,6 +769,7 @@ public void Delete(string cql, params object[] args) /// public void Delete(Cql cql) { + _session.Connect(); //Wait async method to be completed or throw TaskHelper.WaitToCompleteWithMetrics(_metricsManager, DeleteAsync(cql), _queryAbortTimeout); } @@ -758,6 +783,7 @@ public void Execute(string cql, params object[] args) /// public void Execute(Cql cql) { + _session.Connect(); //Wait async method to be completed or throw TaskHelper.WaitToCompleteWithMetrics(_metricsManager, ExecuteAsync(cql), _queryAbortTimeout); } @@ -794,12 +820,14 @@ public async Task> ExecuteConditionalAsync(ICqlBatch batch, st /// public AppliedInfo ExecuteConditional(ICqlBatch batch) { + _session.Connect(); return TaskHelper.WaitToCompleteWithMetrics(_metricsManager, ExecuteConditionalAsync(batch), _queryAbortTimeout); } /// public AppliedInfo ExecuteConditional(ICqlBatch batch, string executionProfile) { + _session.Connect(); return TaskHelper.WaitToCompleteWithMetrics(_metricsManager, ExecuteConditionalAsync(batch, executionProfile), _queryAbortTimeout); } diff --git a/src/Cassandra/Metadata.cs b/src/Cassandra/Metadata.cs index e68bff204..b95bc9815 100644 --- a/src/Cassandra/Metadata.cs +++ b/src/Cassandra/Metadata.cs @@ -15,654 +15,329 @@ // using System; -using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; -using Cassandra.Collections; -using Cassandra.Connections; + using Cassandra.Connections.Control; -using Cassandra.MetadataHelpers; -using Cassandra.Requests; +using Cassandra.Helpers; using Cassandra.Tasks; namespace Cassandra { - /// - /// Keeps metadata on the connected cluster, including known nodes and schema - /// definitions. - /// - public class Metadata : IDisposable + /// + internal class Metadata : IMetadata { - private const string SelectSchemaVersionPeers = "SELECT schema_version FROM system.peers"; - private const string SelectSchemaVersionLocal = "SELECT schema_version FROM system.local"; - private static readonly Logger Logger = new Logger(typeof(ControlConnection)); - private volatile TokenMap _tokenMap; - private volatile ConcurrentDictionary _keyspaces = new ConcurrentDictionary(); - private volatile ISchemaParser _schemaParser; + private readonly IClusterInitializer _clusterInitializer; private readonly int _queryAbortTimeout; - private volatile CopyOnWriteDictionary> _resolvedContactPoints = - new CopyOnWriteDictionary>(); - - public event HostsEventHandler HostsEvent; - - public event SchemaChangedEventHandler SchemaChangedEvent; - - /// - /// Returns the name of currently connected cluster. - /// - /// the Cassandra name of currently connected cluster. - public String ClusterName { get; internal set; } - /// - /// Determines whether the cluster is provided as a service (DataStax Astra). - /// - public bool IsDbaas { get; private set; } = false; + internal IInternalMetadata InternalMetadata { get; } - /// - /// Gets the configuration associated with this instance. - /// - internal Configuration Configuration { get; private set; } - - /// - /// Control connection to be used to execute the queries to retrieve the metadata - /// - internal IControlConnection ControlConnection { get; set; } + internal Metadata(IClusterInitializer clusterInitializer, IInternalMetadata internalMetadata) + { + _clusterInitializer = clusterInitializer; + Configuration = internalMetadata.Configuration; + _queryAbortTimeout = Configuration.DefaultRequestOptions.QueryAbortTimeout; + InternalMetadata = internalMetadata; + } - internal ISchemaParser SchemaParser { get { return _schemaParser; } } + public Configuration Configuration { get; } - internal string Partitioner { get; set; } + /// + public event HostsEventHandler HostsEvent; - internal Hosts Hosts { get; private set; } + /// + public event SchemaChangedEventHandler SchemaChangedEvent; - internal IReadOnlyDictionary> ResolvedContactPoints => _resolvedContactPoints; + /// + public event Action HostAdded; - internal IReadOnlyTokenMap TokenToReplicasMap => _tokenMap; + /// + public event Action HostRemoved; - internal Metadata(Configuration configuration) + /// + public async Task GetClusterDescriptionAsync() { - _queryAbortTimeout = configuration.DefaultRequestOptions.QueryAbortTimeout; - Configuration = configuration; - Hosts = new Hosts(); - Hosts.Down += OnHostDown; - Hosts.Up += OnHostUp; + await TryInitializeAsync().ConfigureAwait(false); + return GetClusterDescriptionInternal(); } - internal Metadata(Configuration configuration, SchemaParser schemaParser) : this(configuration) + /// + public ClusterDescription GetClusterDescription() { - _schemaParser = schemaParser; + TryInitialize(); + return GetClusterDescriptionInternal(); } - public void Dispose() + /// + public ICollection AllHostsSnapshot() { - ShutDown(); + return InternalMetadata.AllHosts(); } - internal void SetResolvedContactPoints(IDictionary> resolvedContactPoints) + /// + public IEnumerable AllReplicasSnapshot() { - _resolvedContactPoints = new CopyOnWriteDictionary>(resolvedContactPoints); + return InternalMetadata.AllReplicas(); } - public Host GetHost(IPEndPoint address) + /// + public ICollection GetReplicasSnapshot(string keyspaceName, byte[] partitionKey) { - if (Hosts.TryGet(address, out var host)) - return host; - return null; + return InternalMetadata.GetReplicas(keyspaceName, partitionKey); } - internal Host AddHost(IPEndPoint address) + /// + public ICollection GetReplicasSnapshot(byte[] partitionKey) { - return Hosts.Add(address); + return GetReplicasSnapshot(null, partitionKey); } - internal Host AddHost(IPEndPoint address, IContactPoint contactPoint) + private ClusterDescription GetClusterDescriptionInternal() { - return Hosts.Add(address, contactPoint); + return new ClusterDescription(InternalMetadata); } - internal void RemoveHost(IPEndPoint address) + /// + public Host GetHost(IPEndPoint address) { - Hosts.RemoveIfExists(address); + TryInitialize(); + return InternalMetadata.GetHost(address); } - internal void FireSchemaChangedEvent(SchemaChangedEventArgs.Kind what, string keyspace, string table, object sender = null) + /// + public async Task GetHostAsync(IPEndPoint address) { - SchemaChangedEvent?.Invoke(sender ?? this, new SchemaChangedEventArgs { Keyspace = keyspace, What = what, Table = table }); + await TryInitializeAsync().ConfigureAwait(false); + return InternalMetadata.GetHost(address); } - private void OnHostDown(Host h) + /// + public ICollection AllHosts() { - HostsEvent?.Invoke(this, new HostsEventArgs { Address = h.Address, What = HostsEventArgs.Kind.Down }); + TryInitialize(); + return InternalMetadata.AllHosts(); } - private void OnHostUp(Host h) + /// + public async Task> AllHostsAsync() { - HostsEvent?.Invoke(h, new HostsEventArgs { Address = h.Address, What = HostsEventArgs.Kind.Up }); + await TryInitializeAsync().ConfigureAwait(false); + return InternalMetadata.AllHosts(); } - /// - /// Returns all known hosts of this cluster. - /// - /// collection of all known hosts of this cluster. - public ICollection AllHosts() + /// + public IEnumerable AllReplicas() { - return Hosts.ToCollection(); + TryInitialize(); + return InternalMetadata.AllReplicas(); } - public IEnumerable AllReplicas() + /// + public async Task> AllReplicasAsync() { - return Hosts.AllEndPointsToCollection(); - } - - // for tests - internal KeyValuePair[] KeyspacesSnapshot => _keyspaces.ToArray(); - - internal async Task RebuildTokenMapAsync(bool retry, bool fetchKeyspaces) - { - IEnumerable ksList = null; - if (fetchKeyspaces) - { - Metadata.Logger.Info("Retrieving keyspaces metadata"); - ksList = await _schemaParser.GetKeyspacesAsync(retry).ConfigureAwait(false); - } - - ConcurrentDictionary keyspaces; - if (ksList != null) - { - Metadata.Logger.Info("Updating keyspaces metadata"); - var ksMap = ksList.Select(ks => new KeyValuePair(ks.Name, ks)); - keyspaces = new ConcurrentDictionary(ksMap); - } - else - { - keyspaces = _keyspaces; - } - - Metadata.Logger.Info("Rebuilding token map"); - if (Partitioner == null) - { - throw new DriverInternalError("Partitioner can not be null"); - } - - var tokenMap = TokenMap.Build(Partitioner, Hosts.ToCollection(), keyspaces.Values); - _keyspaces = keyspaces; - _tokenMap = tokenMap; - } - - /// - /// this method should be called by the event debouncer - /// - internal bool RemoveKeyspaceFromTokenMap(string name) - { - Metadata.Logger.Verbose("Removing keyspace metadata: " + name); - var dropped = _keyspaces.TryRemove(name, out _); - _tokenMap?.RemoveKeyspace(name); - return dropped; - } - - internal async Task UpdateTokenMapForKeyspace(string name) - { - var keyspaceMetadata = await _schemaParser.GetKeyspaceAsync(name).ConfigureAwait(false); - - var dropped = false; - var updated = false; - if (_tokenMap == null) - { - await RebuildTokenMapAsync(false, false).ConfigureAwait(false); - } - - if (keyspaceMetadata == null) - { - Metadata.Logger.Verbose("Removing keyspace metadata: " + name); - dropped = _keyspaces.TryRemove(name, out _); - _tokenMap?.RemoveKeyspace(name); - } - else - { - Metadata.Logger.Verbose("Updating keyspace metadata: " + name); - _keyspaces.AddOrUpdate(keyspaceMetadata.Name, keyspaceMetadata, (k, v) => - { - updated = true; - return keyspaceMetadata; - }); - Metadata.Logger.Info("Rebuilding token map for keyspace {0}", keyspaceMetadata.Name); - if (Partitioner == null) - { - throw new DriverInternalError("Partitioner can not be null"); - } - - _tokenMap.UpdateKeyspace(keyspaceMetadata); - } - - if (Configuration.MetadataSyncOptions.MetadataSyncEnabled) - { - if (dropped) - { - FireSchemaChangedEvent(SchemaChangedEventArgs.Kind.Dropped, name, null, this); - } - else if (updated) - { - FireSchemaChangedEvent(SchemaChangedEventArgs.Kind.Updated, name, null, this); - } - else - { - FireSchemaChangedEvent(SchemaChangedEventArgs.Kind.Created, name, null, this); - } - } - - return keyspaceMetadata; - } - - /// - /// Get the replicas for a given partition key and keyspace - /// + await TryInitializeAsync().ConfigureAwait(false); + return InternalMetadata.AllReplicas(); + } + + /// public ICollection GetReplicas(string keyspaceName, byte[] partitionKey) { - if (_tokenMap == null) - { - Metadata.Logger.Warning("Metadata.GetReplicas was called but there was no token map."); - return new Host[0]; - } - return _tokenMap.GetReplicas(keyspaceName, _tokenMap.Factory.Hash(partitionKey)); + TryInitialize(); + return InternalMetadata.GetReplicas(keyspaceName, partitionKey); } + /// public ICollection GetReplicas(byte[] partitionKey) { return GetReplicas(null, partitionKey); } - /// - /// Returns metadata of specified keyspace. - /// - /// the name of the keyspace for which metadata should be - /// returned. - /// the metadata of the requested keyspace or null if - /// * keyspace is not a known keyspace. + /// + public async Task> GetReplicasAsync(string keyspaceName, byte[] partitionKey) + { + await TryInitializeAsync().ConfigureAwait(false); + return InternalMetadata.GetReplicas(keyspaceName, partitionKey); + } + + /// + public Task> GetReplicasAsync(byte[] partitionKey) + { + return GetReplicasAsync(null, partitionKey); + } + + /// public KeyspaceMetadata GetKeyspace(string keyspace) { - if (Configuration.MetadataSyncOptions.MetadataSyncEnabled) - { - //Use local cache - _keyspaces.TryGetValue(keyspace, out var ksInfo); - return ksInfo; - } + TryInitialize(); + return TaskHelper.WaitToComplete(InternalMetadata.GetKeyspaceAsync(keyspace), _queryAbortTimeout); + } - return TaskHelper.WaitToComplete(SchemaParser.GetKeyspaceAsync(keyspace), _queryAbortTimeout); + /// + public async Task GetKeyspaceAsync(string keyspace) + { + await TryInitializeAsync().ConfigureAwait(false); + return await InternalMetadata.GetKeyspaceAsync(keyspace).ConfigureAwait(false); } - /// - /// Returns a collection of all defined keyspaces names. - /// - /// a collection of all defined keyspaces names. + /// public ICollection GetKeyspaces() { - if (Configuration.MetadataSyncOptions.MetadataSyncEnabled) - { - //Use local cache - return _keyspaces.Keys; - } + TryInitialize(); + return TaskHelper.WaitToComplete(InternalMetadata.GetKeyspacesAsync(), _queryAbortTimeout); + } - return TaskHelper.WaitToComplete(SchemaParser.GetKeyspacesNamesAsync(), _queryAbortTimeout); + /// + public async Task> GetKeyspacesAsync() + { + await TryInitializeAsync().ConfigureAwait(false); + return await InternalMetadata.GetKeyspacesAsync().ConfigureAwait(false); } - /// - /// Returns names of all tables which are defined within specified keyspace. - /// - /// the name of the keyspace for which all tables metadata should be - /// returned. - /// an ICollection of the metadata for the tables defined in this - /// keyspace. + /// public ICollection GetTables(string keyspace) { - if (Configuration.MetadataSyncOptions.MetadataSyncEnabled) - { - return !_keyspaces.TryGetValue(keyspace, out var ksMetadata) - ? new string[0] - : ksMetadata.GetTablesNames(); - } + TryInitialize(); + return TaskHelper.WaitToComplete(InternalMetadata.GetTablesAsync(keyspace), _queryAbortTimeout); + } - return TaskHelper.WaitToComplete(SchemaParser.GetTableNamesAsync(keyspace), _queryAbortTimeout); + /// + public async Task> GetTablesAsync(string keyspace) + { + await TryInitializeAsync().ConfigureAwait(false); + return await InternalMetadata.GetTablesAsync(keyspace).ConfigureAwait(false); } - /// - /// Returns TableMetadata for specified table in specified keyspace. - /// - /// name of the keyspace within specified table is defined. - /// name of table for which metadata should be returned. - /// a TableMetadata for the specified table in the specified keyspace. + /// public TableMetadata GetTable(string keyspace, string tableName) { - return TaskHelper.WaitToComplete(GetTableAsync(keyspace, tableName), _queryAbortTimeout * 2); + TryInitialize(); + return TaskHelper.WaitToComplete(InternalMetadata.GetTableAsync(keyspace, tableName), _queryAbortTimeout * 2); } - internal Task GetTableAsync(string keyspace, string tableName) + /// + public async Task GetTableAsync(string keyspace, string tableName) { - if (Configuration.MetadataSyncOptions.MetadataSyncEnabled) - { - return !_keyspaces.TryGetValue(keyspace, out var ksMetadata) - ? Task.FromResult(null) - : ksMetadata.GetTableMetadataAsync(tableName); - } - - return SchemaParser.GetTableAsync(keyspace, tableName); + await TryInitializeAsync().ConfigureAwait(false); + return await InternalMetadata.GetTableAsync(keyspace, tableName).ConfigureAwait(false); } - /// - /// Returns the view metadata for the provided view name in the keyspace. - /// - /// name of the keyspace within specified view is defined. - /// name of view. - /// a MaterializedViewMetadata for the view in the specified keyspace. + /// public MaterializedViewMetadata GetMaterializedView(string keyspace, string name) { - if (Configuration.MetadataSyncOptions.MetadataSyncEnabled) - { - return !_keyspaces.TryGetValue(keyspace, out var ksMetadata) - ? null - : ksMetadata.GetMaterializedViewMetadata(name); - } + TryInitialize(); + return TaskHelper.WaitToComplete(InternalMetadata.GetMaterializedViewAsync(keyspace, name), _queryAbortTimeout * 2); + } - return TaskHelper.WaitToComplete(SchemaParser.GetViewAsync(keyspace, name), _queryAbortTimeout * 2); + /// + public async Task GetMaterializedViewAsync(string keyspace, string name) + { + await TryInitializeAsync().ConfigureAwait(false); + return await InternalMetadata.GetMaterializedViewAsync(keyspace, name).ConfigureAwait(false); } - /// - /// Gets the definition associated with a User Defined Type from Cassandra - /// + /// public UdtColumnInfo GetUdtDefinition(string keyspace, string typeName) { - return TaskHelper.WaitToComplete(GetUdtDefinitionAsync(keyspace, typeName), _queryAbortTimeout); + TryInitialize(); + return TaskHelper.WaitToComplete(InternalMetadata.GetUdtDefinitionAsync(keyspace, typeName), _queryAbortTimeout); } - /// - /// Gets the definition associated with a User Defined Type from Cassandra - /// - public Task GetUdtDefinitionAsync(string keyspace, string typeName) + /// + public async Task GetUdtDefinitionAsync(string keyspace, string typeName) { - if (Configuration.MetadataSyncOptions.MetadataSyncEnabled) - { - return !_keyspaces.TryGetValue(keyspace, out var ksMetadata) - ? Task.FromResult(null) - : ksMetadata.GetUdtDefinitionAsync(typeName); - } - - return SchemaParser.GetUdtDefinitionAsync(keyspace, typeName); + await TryInitializeAsync().ConfigureAwait(false); + return await InternalMetadata.GetUdtDefinitionAsync(keyspace, typeName).ConfigureAwait(false); } - /// - /// Gets the definition associated with a User Defined Function from Cassandra - /// - /// The function metadata or null if not found. + /// public FunctionMetadata GetFunction(string keyspace, string name, string[] signature) { - if (Configuration.MetadataSyncOptions.MetadataSyncEnabled) - { - return !_keyspaces.TryGetValue(keyspace, out var ksMetadata) - ? null - : ksMetadata.GetFunction(name, signature); - } + TryInitialize(); + return TaskHelper.WaitToComplete(InternalMetadata.GetFunctionAsync(keyspace, name, signature), _queryAbortTimeout); + } - var signatureString = SchemaParser.ComputeFunctionSignatureString(signature); - return TaskHelper.WaitToComplete(SchemaParser.GetFunctionAsync(keyspace, name, signatureString), _queryAbortTimeout); + /// + public async Task GetFunctionAsync(string keyspace, string name, string[] signature) + { + await TryInitializeAsync().ConfigureAwait(false); + return await InternalMetadata.GetFunctionAsync(keyspace, name, signature).ConfigureAwait(false); } - /// - /// Gets the definition associated with a aggregate from Cassandra - /// - /// The aggregate metadata or null if not found. + /// public AggregateMetadata GetAggregate(string keyspace, string name, string[] signature) { - if (Configuration.MetadataSyncOptions.MetadataSyncEnabled) - { - return !_keyspaces.TryGetValue(keyspace, out var ksMetadata) - ? null - : ksMetadata.GetAggregate(name, signature); - } - - var signatureString = SchemaParser.ComputeFunctionSignatureString(signature); - return TaskHelper.WaitToComplete(SchemaParser.GetAggregateAsync(keyspace, name, signatureString), _queryAbortTimeout); + TryInitialize(); + return TaskHelper.WaitToComplete(InternalMetadata.GetAggregateAsync(keyspace, name, signature), _queryAbortTimeout); } - /// - /// Gets the query trace. - /// - /// The query trace that contains the id, which properties are going to be populated. - /// - internal Task GetQueryTraceAsync(QueryTrace trace) + /// + public async Task GetAggregateAsync(string keyspace, string name, string[] signature) { - return _schemaParser.GetQueryTraceAsync(trace, Configuration.Timer); + await TryInitializeAsync().ConfigureAwait(false); + return await InternalMetadata.GetAggregateAsync(keyspace, name, signature).ConfigureAwait(false); } - /// - /// Updates the keyspace and token information - /// + /// public bool RefreshSchema(string keyspace = null, string table = null) { - return TaskHelper.WaitToComplete(RefreshSchemaAsync(keyspace, table), Configuration.DefaultRequestOptions.QueryAbortTimeout * 2); + TryInitialize(); + return TaskHelper.WaitToComplete(InternalMetadata.RefreshSchemaAsync(keyspace, table), _queryAbortTimeout * 2); } - /// - /// Updates the keyspace and token information - /// + /// public async Task RefreshSchemaAsync(string keyspace = null, string table = null) { - if (keyspace == null) - { - await ControlConnection.ScheduleAllKeyspacesRefreshAsync(true).ConfigureAwait(false); - return true; - } - - await ControlConnection.ScheduleKeyspaceRefreshAsync(keyspace, true).ConfigureAwait(false); - _keyspaces.TryGetValue(keyspace, out var ks); - if (ks == null) - { - return false; - } - - if (table != null) - { - ks.ClearTableMetadata(table); - } - return true; + await TryInitializeAsync().ConfigureAwait(false); + return await InternalMetadata.RefreshSchemaAsync(keyspace, table).ConfigureAwait(false); } - public void ShutDown(int timeoutMs = Timeout.Infinite) + /// + public async Task CheckSchemaAgreementAsync() { - //it is really not required to be called, left as it is part of the public API - //dereference the control connection - ControlConnection = null; + await TryInitializeAsync().ConfigureAwait(false); + return await InternalMetadata.CheckSchemaAgreementAsync().ConfigureAwait(false); } - /// - /// this method should be called by the event debouncer - /// - internal bool RemoveKeyspace(string name) + internal Task TryInitializeAsync() { - var existed = RemoveKeyspaceFromTokenMap(name); - if (!existed) - { - return false; - } - - FireSchemaChangedEvent(SchemaChangedEventArgs.Kind.Dropped, name, null, this); - return true; + return _clusterInitializer.WaitInitAsync(); } - /// - /// this method should be called by the event debouncer - /// - internal Task RefreshSingleKeyspace(string name) + internal void TryInitialize() { - return UpdateTokenMapForKeyspace(name); + _clusterInitializer.WaitInit(); } - internal void ClearTable(string keyspaceName, string tableName) + private void OnInternalHostRemoved(Host h) { - if (_keyspaces.TryGetValue(keyspaceName, out var ksMetadata)) - { - ksMetadata.ClearTableMetadata(tableName); - } + HostRemoved?.Invoke(h); } - internal void ClearView(string keyspaceName, string name) + private void OnInternalHostAdded(Host h) { - if (_keyspaces.TryGetValue(keyspaceName, out var ksMetadata)) - { - ksMetadata.ClearViewMetadata(name); - } + HostAdded?.Invoke(h); } - internal void ClearFunction(string keyspaceName, string functionName, string[] signature) + private void OnInternalHostsEvent(object sender, HostsEventArgs args) { - if (_keyspaces.TryGetValue(keyspaceName, out var ksMetadata)) - { - ksMetadata.ClearFunction(functionName, signature); - } + HostsEvent?.Invoke(sender, args); } - internal void ClearAggregate(string keyspaceName, string aggregateName, string[] signature) + private void OnInternalSchemaChangedEvent(object sender, SchemaChangedEventArgs args) { - if (_keyspaces.TryGetValue(keyspaceName, out var ksMetadata)) - { - ksMetadata.ClearAggregate(aggregateName, signature); - } + SchemaChangedEvent?.Invoke(sender, args); } - /// - /// Initiates a schema agreement check. - /// - /// Schema changes need to be propagated to all nodes in the cluster. - /// Once they have settled on a common version, we say that they are in agreement. - /// - /// This method does not perform retries so - /// does not apply. - /// - /// True if schema agreement was successful and false if it was not successful. - public async Task CheckSchemaAgreementAsync() + internal void SetupEventForwarding() { - if (Hosts.Count == 1) - { - // If there is just one node, the schema is up to date in all nodes :) - return true; - } - - try - { - var queries = new[] - { - ControlConnection.QueryAsync(SelectSchemaVersionLocal), - ControlConnection.QueryAsync(SelectSchemaVersionPeers) - }; - - await Task.WhenAll(queries).ConfigureAwait(false); - - return CheckSchemaVersionResults(queries[0].Result, queries[1].Result); - } - catch (Exception ex) - { - Logger.Error("Error while checking schema agreement.", ex); - } - - return false; - } - - /// - /// Checks if there is only one schema version between the provided query results. - /// - /// - /// Results obtained from a query to system.local table. - /// Must contain the schema_version column. - /// - /// - /// Results obtained from a query to system.peers table. - /// Must contain the schema_version column. - /// - /// True if there is a schema agreement (only 1 schema version). False otherwise. - private static bool CheckSchemaVersionResults( - IEnumerable localVersionQuery, IEnumerable peerVersionsQuery) - { - return new HashSet( - peerVersionsQuery - .Concat(localVersionQuery) - .Select(r => r.GetValue("schema_version"))).Count == 1; - } - - /// - /// Waits until that the schema version in all nodes is the same or the waiting time passed. - /// This method blocks the calling thread. - /// - internal bool WaitForSchemaAgreement(IConnection connection) - { - if (Hosts.Count == 1) - { - //If there is just one node, the schema is up to date in all nodes :) - return true; - } - var start = DateTime.Now; - var waitSeconds = Configuration.ProtocolOptions.MaxSchemaAgreementWaitSeconds; - Metadata.Logger.Info("Waiting for schema agreement"); - try - { - var totalVersions = 0; - while (DateTime.Now.Subtract(start).TotalSeconds < waitSeconds) - { - var serializer = ControlConnection.Serializer.GetCurrentSerializer(); - var schemaVersionLocalQuery = - new QueryRequest( - serializer, - Metadata.SelectSchemaVersionLocal, - QueryProtocolOptions.Default, - false, - null); - var schemaVersionPeersQuery = - new QueryRequest( - serializer, - Metadata.SelectSchemaVersionPeers, - QueryProtocolOptions.Default, - false, - null); - var queries = new[] { connection.Send(schemaVersionLocalQuery), connection.Send(schemaVersionPeersQuery) }; - // ReSharper disable once CoVariantArrayConversion - Task.WaitAll(queries, Configuration.DefaultRequestOptions.QueryAbortTimeout); - - if (Metadata.CheckSchemaVersionResults( - Configuration.MetadataRequestHandler.GetRowSet(queries[0].Result), - Configuration.MetadataRequestHandler.GetRowSet(queries[1].Result))) - { - return true; - } - - Thread.Sleep(500); - } - Metadata.Logger.Info($"Waited for schema agreement, still {totalVersions} schema versions in the cluster."); - } - catch (Exception ex) - { - //Exceptions are not fatal - Metadata.Logger.Error("There was an exception while trying to retrieve schema versions", ex); - } - - return false; - } - - /// - /// Sets the Cassandra version in order to identify how to parse the metadata information - /// - /// - internal void SetCassandraVersion(Version version) - { - _schemaParser = Configuration.SchemaParserFactory.Create(version, this, GetUdtDefinitionAsync, _schemaParser); - } - - internal void SetProductTypeAsDbaas() - { - IsDbaas = true; - } - - internal IEnumerable UpdateResolvedContactPoint(IContactPoint contactPoint, IEnumerable endpoints) - { - return _resolvedContactPoints.AddOrUpdate(contactPoint, _ => endpoints, (_, __) => endpoints); + InternalMetadata.Hosts.Added += InternalMetadata.OnHostAdded; + InternalMetadata.Hosts.Removed += InternalMetadata.OnHostRemoved; + InternalMetadata.HostAdded += OnInternalHostAdded; + InternalMetadata.HostRemoved += OnInternalHostRemoved; + InternalMetadata.HostsEvent += OnInternalHostsEvent; + InternalMetadata.SchemaChangedEvent += OnInternalSchemaChangedEvent; } } } \ No newline at end of file diff --git a/src/Cassandra/MetadataHelpers/ISchemaParserFactory.cs b/src/Cassandra/MetadataHelpers/ISchemaParserFactory.cs index 5c4cb0089..20ff257b6 100644 --- a/src/Cassandra/MetadataHelpers/ISchemaParserFactory.cs +++ b/src/Cassandra/MetadataHelpers/ISchemaParserFactory.cs @@ -15,12 +15,13 @@ using System; using System.Threading.Tasks; +using Cassandra.Connections.Control; namespace Cassandra.MetadataHelpers { internal interface ISchemaParserFactory { - ISchemaParser Create(Version cassandraVersion, Metadata parent, + ISchemaParser Create(Version cassandraVersion, IInternalMetadata parent, Func> udtResolver, ISchemaParser currentInstance = null); } diff --git a/src/Cassandra/MetadataHelpers/SchemaParserFactory.cs b/src/Cassandra/MetadataHelpers/SchemaParserFactory.cs index bba51c33e..965293b2d 100644 --- a/src/Cassandra/MetadataHelpers/SchemaParserFactory.cs +++ b/src/Cassandra/MetadataHelpers/SchemaParserFactory.cs @@ -15,6 +15,7 @@ using System; using System.Threading.Tasks; +using Cassandra.Connections.Control; namespace Cassandra.MetadataHelpers { @@ -27,7 +28,7 @@ internal class SchemaParserFactory : ISchemaParserFactory /// /// Creates a new instance if the currentInstance is not valid for the given Cassandra version /// - public ISchemaParser Create(Version cassandraVersion, Metadata parent, + public ISchemaParser Create(Version cassandraVersion, IInternalMetadata parent, Func> udtResolver, ISchemaParser currentInstance = null) { diff --git a/src/Cassandra/Metrics/Internal/IMetricsManager.cs b/src/Cassandra/Metrics/Internal/IMetricsManager.cs index fd66d0b95..669bdbc0a 100644 --- a/src/Cassandra/Metrics/Internal/IMetricsManager.cs +++ b/src/Cassandra/Metrics/Internal/IMetricsManager.cs @@ -32,12 +32,7 @@ internal interface IMetricsManager : IDriverMetrics, IDisposable /// Get the existing node metrics for the provided host or creates them and returns them if they don't exist yet. /// INodeMetrics GetOrCreateNodeMetrics(Host host); - - /// - /// Initialize metrics with the provided session. - /// - void InitializeMetrics(IInternalSession session); - + /// /// /// diff --git a/src/Cassandra/Metrics/Internal/MetricsManager.cs b/src/Cassandra/Metrics/Internal/MetricsManager.cs index 704a669b2..3462a58b8 100644 --- a/src/Cassandra/Metrics/Internal/MetricsManager.cs +++ b/src/Cassandra/Metrics/Internal/MetricsManager.cs @@ -29,11 +29,8 @@ namespace Cassandra.Metrics.Internal /// internal class MetricsManager : IMetricsManager { - private static readonly Logger Logger = new Logger(typeof(MetricsManager)); - private readonly IDriverMetricsProvider _driverMetricsProvider; private readonly DriverMetricsOptions _metricsOptions; - private readonly bool _metricsEnabled; private readonly string _sessionBucket; private readonly ISessionMetrics _sessionMetrics; private readonly CopyOnWriteDictionary> _nodeMetricsRegistryCollection; @@ -41,13 +38,18 @@ internal class MetricsManager : IMetricsManager private readonly bool _disabledSessionTimerMetrics; private readonly bool _disabledNodeTimerMetrics; - public MetricsManager(IDriverMetricsProvider driverMetricsProvider, DriverMetricsOptions metricsOptions, bool metricsEnabled, string sessionName) + public MetricsManager( + IInternalSession session, + IDriverMetricsProvider driverMetricsProvider, + DriverMetricsOptions metricsOptions, + bool metricsEnabled, + string sessionName) { _driverMetricsProvider = driverMetricsProvider; _metricsOptions = metricsOptions; - _metricsEnabled = metricsEnabled; + AreMetricsEnabled = metricsEnabled; _sessionBucket = metricsOptions.BucketPrefix != null ? $"{metricsOptions.BucketPrefix}.{sessionName}" : sessionName; - _sessionMetrics = new SessionMetrics(_driverMetricsProvider, metricsOptions, metricsEnabled, _sessionBucket); + _sessionMetrics = new SessionMetrics(session, _driverMetricsProvider, metricsOptions, metricsEnabled, _sessionBucket); _nodeMetricsRegistryCollection = new CopyOnWriteDictionary>(); _nodeMetricsCollection = new CopyOnWriteDictionary(); _disabledSessionTimerMetrics = !metricsEnabled || !metricsOptions.EnabledSessionMetrics.Contains(SessionMetric.Timers.CqlRequests); @@ -59,9 +61,9 @@ public MetricsManager(IDriverMetricsProvider driverMetricsProvider, DriverMetric /// public IReadOnlyDictionary> NodeMetrics => _nodeMetricsRegistryCollection; - + /// - public bool AreMetricsEnabled => _metricsEnabled; + public bool AreMetricsEnabled { get; } /// public TMetricType GetNodeMetric(Host host, NodeMetric nodeMetric) where TMetricType : class, IDriverMetric @@ -103,13 +105,7 @@ public TMetricType GetSessionMetric(SessionMetric sessionMetric) wh return typedMetric; } - - /// - public void InitializeMetrics(IInternalSession session) - { - _sessionMetrics.InitializeMetrics(session); - } - + /// public void RemoveNodeMetrics(Host host) { @@ -141,7 +137,7 @@ public INodeMetrics GetOrCreateNodeMetrics(Host host) { var nodeBucket = $"{_sessionBucket}.nodes.{MetricsManager.BuildHostAddressMetricPath(host.Address)}"; - var newRegistry = new NodeMetrics(_driverMetricsProvider, _metricsOptions, _metricsEnabled, nodeBucket); + var newRegistry = new NodeMetrics(_driverMetricsProvider, _metricsOptions, AreMetricsEnabled, nodeBucket); _nodeMetricsRegistryCollection.Add(host, newRegistry.MetricsRegistry); return newRegistry; diff --git a/src/Cassandra/Metrics/Registries/IInternalMetricsRegistry.cs b/src/Cassandra/Metrics/Registries/IInternalMetricsRegistry.cs index 2bd295ba5..9b5a2c15b 100644 --- a/src/Cassandra/Metrics/Registries/IInternalMetricsRegistry.cs +++ b/src/Cassandra/Metrics/Registries/IInternalMetricsRegistry.cs @@ -35,10 +35,5 @@ internal interface IInternalMetricsRegistry : IMetricsRegistry IDriverGauge Gauge(string bucket, TMetric metric, Func valueProvider); IDriverMetric GetMetric(TMetric metric); - - /// - /// Used to notify the registry that no more metrics will be added. (Concurrency optimization). - /// - void OnMetricsAdded(); } } \ No newline at end of file diff --git a/src/Cassandra/Metrics/Registries/ISessionMetrics.cs b/src/Cassandra/Metrics/Registries/ISessionMetrics.cs index a14934d4f..5b033a858 100644 --- a/src/Cassandra/Metrics/Registries/ISessionMetrics.cs +++ b/src/Cassandra/Metrics/Registries/ISessionMetrics.cs @@ -40,7 +40,5 @@ internal interface ISessionMetrics : IDisposable /// Internal MetricsRegistry used to create metrics internally. /// IInternalMetricsRegistry MetricsRegistry { get; } - - void InitializeMetrics(IInternalSession session); } } \ No newline at end of file diff --git a/src/Cassandra/Metrics/Registries/InternalMetricsRegistry.cs b/src/Cassandra/Metrics/Registries/InternalMetricsRegistry.cs index 1d202e64c..d7bfa9b39 100644 --- a/src/Cassandra/Metrics/Registries/InternalMetricsRegistry.cs +++ b/src/Cassandra/Metrics/Registries/InternalMetricsRegistry.cs @@ -16,8 +16,8 @@ using System; using System.Collections.Generic; -using System.Threading; +using Cassandra.Collections; using Cassandra.Metrics.Abstractions; using Cassandra.Metrics.Providers.Null; @@ -30,15 +30,23 @@ internal class InternalMetricsRegistry : IInternalMetricsRegistry _disabledMetrics; - private readonly Dictionary _gauges = new Dictionary(); - private readonly Dictionary _counters = new Dictionary(); - private readonly Dictionary _meters = new Dictionary(); - private readonly Dictionary _timers = new Dictionary(); - private readonly Dictionary _metrics = new Dictionary(); + private readonly CopyOnWriteDictionary _gauges = + new CopyOnWriteDictionary(); - private bool _initialized = false; + private readonly CopyOnWriteDictionary _counters = + new CopyOnWriteDictionary(); - public InternalMetricsRegistry(IDriverMetricsProvider driverMetricsProvider, IEnumerable disabledMetrics, bool metricsEnabled) + private readonly CopyOnWriteDictionary _meters = + new CopyOnWriteDictionary(); + + private readonly CopyOnWriteDictionary _timers = + new CopyOnWriteDictionary(); + + private readonly CopyOnWriteDictionary _metrics = + new CopyOnWriteDictionary(); + + public InternalMetricsRegistry( + IDriverMetricsProvider driverMetricsProvider, IEnumerable disabledMetrics, bool metricsEnabled) { _disabledMetrics = new HashSet(disabledMetrics); _driverMetricsProvider = driverMetricsProvider; @@ -50,7 +58,7 @@ public InternalMetricsRegistry(IDriverMetricsProvider driverMetricsProvider, IEn /// public IReadOnlyDictionary Gauges => _gauges; - + /// public IReadOnlyDictionary Meters => _meters; @@ -62,7 +70,6 @@ public InternalMetricsRegistry(IDriverMetricsProvider driverMetricsProvider, IEn public IDriverTimer Timer(string bucket, TMetric metric) { - ThrowIfInitialized(); if (!IsMetricEnabled(metric)) { return NullDriverTimer.Instance; @@ -73,10 +80,9 @@ public IDriverTimer Timer(string bucket, TMetric metric) _metrics.Add(metric, timer); return timer; } - + public IDriverMeter Meter(string bucket, TMetric metric) { - ThrowIfInitialized(); if (!IsMetricEnabled(metric)) { return NullDriverMeter.Instance; @@ -90,7 +96,6 @@ public IDriverMeter Meter(string bucket, TMetric metric) public IDriverCounter Counter(string bucket, TMetric metric) { - ThrowIfInitialized(); if (!IsMetricEnabled(metric)) { return NullDriverCounter.Instance; @@ -104,7 +109,6 @@ public IDriverCounter Counter(string bucket, TMetric metric) public IDriverGauge Gauge(string bucket, TMetric metric, Func valueProvider) { - ThrowIfInitialized(); if (!IsMetricEnabled(metric)) { return NullDriverGauge.Instance; @@ -122,21 +126,6 @@ public IDriverMetric GetMetric(TMetric metric) return driverMetric; } - /// - public void OnMetricsAdded() - { - _initialized = true; - Interlocked.MemoryBarrier(); - } - - private void ThrowIfInitialized() - { - if (_initialized) - { - throw new DriverInternalError("Can not add metrics after initialization is complete."); - } - } - private bool IsMetricEnabled(TMetric metric) { return _metricsEnabled && !_disabledMetrics.Contains(metric); diff --git a/src/Cassandra/Metrics/Registries/NodeMetrics.cs b/src/Cassandra/Metrics/Registries/NodeMetrics.cs index 32446736f..6b651bfb7 100644 --- a/src/Cassandra/Metrics/Registries/NodeMetrics.cs +++ b/src/Cassandra/Metrics/Registries/NodeMetrics.cs @@ -80,8 +80,6 @@ private void InitializeMetrics() InFlight = MetricsRegistry.Gauge( _bucketName, NodeMetric.Gauges.InFlight, () => _hostConnectionPool?.InFlight); - - MetricsRegistry.OnMetricsAdded(); } catch (Exception) { diff --git a/src/Cassandra/Metrics/Registries/SessionMetrics.cs b/src/Cassandra/Metrics/Registries/SessionMetrics.cs index 980560edc..730a76624 100644 --- a/src/Cassandra/Metrics/Registries/SessionMetrics.cs +++ b/src/Cassandra/Metrics/Registries/SessionMetrics.cs @@ -16,6 +16,7 @@ using System; using System.Linq; + using Cassandra.Metrics.Abstractions; using Cassandra.SessionManagement; @@ -27,29 +28,18 @@ internal class SessionMetrics : ISessionMetrics private readonly IDriverMetricsProvider _driverMetricsProvider; private readonly string _context; - public SessionMetrics(IDriverMetricsProvider driverMetricsProvider, DriverMetricsOptions metricsOptions, bool metricsEnabled, string context) + public SessionMetrics( + IInternalSession session, + IDriverMetricsProvider driverMetricsProvider, + DriverMetricsOptions metricsOptions, + bool metricsEnabled, + string context) { _driverMetricsProvider = driverMetricsProvider; _context = context; MetricsRegistry = new InternalMetricsRegistry( driverMetricsProvider, SessionMetric.AllSessionMetrics.Except(metricsOptions.EnabledSessionMetrics), metricsEnabled); - } - - public IDriverTimer CqlRequests { get; private set; } - - public IDriverCounter CqlClientTimeouts { get; private set; } - - public IDriverMeter BytesSent { get; private set; } - - public IDriverMeter BytesReceived { get; private set; } - - public IDriverGauge ConnectedNodes { get; private set; } - - /// - public IInternalMetricsRegistry MetricsRegistry { get; } - public void InitializeMetrics(IInternalSession session) - { try { CqlRequests = MetricsRegistry.Timer(_context, SessionMetric.Timers.CqlRequests); @@ -58,8 +48,6 @@ public void InitializeMetrics(IInternalSession session) BytesReceived = MetricsRegistry.Meter(_context, SessionMetric.Meters.BytesReceived); ConnectedNodes = MetricsRegistry.Gauge( _context, SessionMetric.Gauges.ConnectedNodes, () => session.ConnectedNodes); - - MetricsRegistry.OnMetricsAdded(); } catch (Exception) { @@ -68,6 +56,19 @@ public void InitializeMetrics(IInternalSession session) } } + public IDriverTimer CqlRequests { get; } + + public IDriverCounter CqlClientTimeouts { get; } + + public IDriverMeter BytesSent { get; } + + public IDriverMeter BytesReceived { get; } + + public IDriverGauge ConnectedNodes { get; } + + /// + public IInternalMetricsRegistry MetricsRegistry { get; } + public void Dispose() { _driverMetricsProvider.ShutdownMetricsBucket(_context); diff --git a/src/Cassandra/Policies/ConstantSpeculativeExecutionPolicy.cs b/src/Cassandra/Policies/ConstantSpeculativeExecutionPolicy.cs index 3303a0d6f..6f189befd 100644 --- a/src/Cassandra/Policies/ConstantSpeculativeExecutionPolicy.cs +++ b/src/Cassandra/Policies/ConstantSpeculativeExecutionPolicy.cs @@ -15,6 +15,8 @@ // using System; +using System.Threading.Tasks; +using Cassandra.Tasks; // ReSharper disable once CheckNamespace namespace Cassandra @@ -48,20 +50,20 @@ public ConstantSpeculativeExecutionPolicy(long delay, int maxSpeculativeExecutio public long Delay { get; } public int MaxSpeculativeExecutions { get; } - - public void Dispose() + + public Task InitializeAsync(IMetadataSnapshotProvider metadata) { - + return TaskHelper.Completed; } - public void Initialize(ICluster cluster) + public ISpeculativeExecutionPlan NewPlan(ICluster cluster, string keyspace, IStatement statement) { - + return new ConstantSpeculativeExecutionPlan(Delay, MaxSpeculativeExecutions); } - public ISpeculativeExecutionPlan NewPlan(string keyspace, IStatement statement) + public Task ShutdownAsync() { - return new ConstantSpeculativeExecutionPlan(Delay, MaxSpeculativeExecutions); + return TaskHelper.Completed; } private class ConstantSpeculativeExecutionPlan : ISpeculativeExecutionPlan diff --git a/src/Cassandra/Policies/DCAwareRoundRobinPolicy.cs b/src/Cassandra/Policies/DCAwareRoundRobinPolicy.cs index c967f7063..5c2c1dc25 100644 --- a/src/Cassandra/Policies/DCAwareRoundRobinPolicy.cs +++ b/src/Cassandra/Policies/DCAwareRoundRobinPolicy.cs @@ -16,9 +16,10 @@ using System; using System.Collections.Generic; -using System.Threading; using System.Linq; -using Cassandra.SessionManagement; +using System.Threading; +using System.Threading.Tasks; +using Cassandra.Tasks; namespace Cassandra { @@ -36,7 +37,6 @@ public class DCAwareRoundRobinPolicy : ILoadBalancingPolicy private readonly int _maxIndex = int.MaxValue - 10000; private volatile List _hosts; private readonly object _hostCreationLock = new object(); - private ICluster _cluster; private int _index; /// @@ -72,22 +72,22 @@ public DCAwareRoundRobinPolicy(string localDc) : this(inferLocalDc: false) LocalDc = localDc; } - + /// /// Gets the Local Datacenter. This value is provided in the constructor. /// public string LocalDc { get; private set; } - public void Initialize(ICluster cluster) + public Task InitializeAsync(IMetadataSnapshotProvider metadata) { - _cluster = cluster; - //When the pool changes, it should clear the local cache - _cluster.HostAdded += _ => ClearHosts(); - _cluster.HostRemoved += _ => ClearHosts(); + metadata.HostAdded += _ => ClearHosts(); + metadata.HostRemoved += _ => ClearHosts(); - LocalDc = cluster.Configuration.LocalDatacenterProvider.DiscoverLocalDatacenter( + LocalDc = metadata.Configuration.LocalDatacenterProvider.DiscoverLocalDatacenter( _inferLocalDc, LocalDc); + + return TaskHelper.Completed; } /// @@ -97,9 +97,10 @@ public void Initialize(ICluster cluster) /// is Ignored.

To configure how many host in each remote /// datacenter is considered Remote.

///
+ /// The metadata instance associated with the cluster for which the policy is created. /// the host of which to return the distance of. /// the HostDistance to host. - public HostDistance Distance(Host host) + public HostDistance Distance(IMetadataSnapshotProvider metadata, Host host) { var dc = GetDatacenter(host); return dc == LocalDc ? HostDistance.Local : HostDistance.Remote; @@ -112,11 +113,12 @@ public HostDistance Distance(Host host) /// per remote datacenter. The order of the local node in the returned query plan /// will follow a Round-robin algorithm.

///
+ /// The information about the session instance for which the policy is created. /// Keyspace on which the query is going to be executed /// the query for which to build the plan. /// a new query plan, i.e. an iterator indicating which host to try /// first for querying, which one to use as failover, etc... - public IEnumerable NewQueryPlan(string keyspace, IStatement query) + public IEnumerable NewQueryPlan(ICluster cluster, string keyspace, IStatement query) { var startIndex = Interlocked.Increment(ref _index); @@ -126,7 +128,7 @@ public IEnumerable NewQueryPlan(string keyspace, IStatement query) Interlocked.Exchange(ref _index, 0); } - var hosts = GetHosts(); + var hosts = GetHosts(cluster.Metadata); //Round-robin through local nodes for (var i = 0; i < hosts.Count; i++) { @@ -148,7 +150,7 @@ private string GetDatacenter(Host host) /// /// Gets a tuple containing the list of local and remote nodes /// - internal List GetHosts() + internal List GetHosts(IMetadataSnapshotProvider metadata) { var hosts = _hosts; if (hosts != null) @@ -166,7 +168,7 @@ internal List GetHosts() } //shallow copy the nodes - var allNodes = _cluster.AllHosts().ToArray(); + var allNodes = metadata.AllHostsSnapshot(); hosts = allNodes.Where(h => GetDatacenter(h) == LocalDc).ToList(); _hosts = hosts; @@ -174,4 +176,4 @@ internal List GetHosts() return hosts; } } -} +} \ No newline at end of file diff --git a/src/Cassandra/Policies/DefaultLoadBalancingPolicy.cs b/src/Cassandra/Policies/DefaultLoadBalancingPolicy.cs index 48394e9a3..8093612a1 100644 --- a/src/Cassandra/Policies/DefaultLoadBalancingPolicy.cs +++ b/src/Cassandra/Policies/DefaultLoadBalancingPolicy.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace Cassandra { @@ -66,9 +67,10 @@ public DefaultLoadBalancingPolicy(string localDc) /// in the local datacenter as Local and the rest /// is Ignored. /// + /// The metadata instance associated with the cluster for which the policy is created. /// the host of which to return the distance of. /// the HostDistance to host. - public HostDistance Distance(Host host) + public HostDistance Distance(IMetadataSnapshotProvider metadata, Host host) { var lastPreferredHost = _lastPreferredHost; if (lastPreferredHost != null && host == lastPreferredHost) @@ -78,35 +80,35 @@ public HostDistance Distance(Host host) return HostDistance.Local; } - return ChildPolicy.Distance(host); + return ChildPolicy.Distance(metadata, host); } /// /// Initializes the policy. /// - public void Initialize(ICluster cluster) + public Task InitializeAsync(IMetadataSnapshotProvider metadata) { - ChildPolicy.Initialize(cluster); + return ChildPolicy.InitializeAsync(metadata); } /// /// Returns the hosts to used for a query. /// - public IEnumerable NewQueryPlan(string keyspace, IStatement statement) + public IEnumerable NewQueryPlan(ICluster cluster, string keyspace, IStatement statement) { if (statement is TargettedSimpleStatement targetedStatement && targetedStatement.PreferredHost != null) { _lastPreferredHost = targetedStatement.PreferredHost; - return YieldPreferred(keyspace, targetedStatement); + return YieldPreferred(cluster, keyspace, targetedStatement); } - return ChildPolicy.NewQueryPlan(keyspace, statement); + return ChildPolicy.NewQueryPlan(cluster, keyspace, statement); } - private IEnumerable YieldPreferred(string keyspace, TargettedSimpleStatement statement) + private IEnumerable YieldPreferred(ICluster cluster, string keyspace, TargettedSimpleStatement statement) { yield return statement.PreferredHost; - foreach (var h in ChildPolicy.NewQueryPlan(keyspace, statement)) + foreach (var h in ChildPolicy.NewQueryPlan(cluster, keyspace, statement)) { yield return h; } diff --git a/src/Cassandra/Policies/ILoadBalancingPolicy.cs b/src/Cassandra/Policies/ILoadBalancingPolicy.cs index 00a48f12f..8b467ae15 100644 --- a/src/Cassandra/Policies/ILoadBalancingPolicy.cs +++ b/src/Cassandra/Policies/ILoadBalancingPolicy.cs @@ -15,6 +15,7 @@ // using System.Collections.Generic; +using System.Threading.Tasks; namespace Cassandra { @@ -25,15 +26,20 @@ namespace Cassandra public interface ILoadBalancingPolicy { /// + /// /// Initialize this load balancing policy. + /// + /// + /// If the implementation is not async, we recommend returning Task.CompletedTask or Task.FromResult(0); + /// /// /// Note that the driver guarantees /// that it will call this method exactly once per policy object and will do so /// before any call to another of the methods of the policy. /// /// - /// The information about the session instance for which the policy is created. - void Initialize(ICluster cluster); + /// Metadata snapshot provider of the session for which the policy is created. + Task InitializeAsync(IMetadataSnapshotProvider metadata); /// /// Returns the distance assigned by this policy to the provided host.

The @@ -46,10 +52,11 @@ public interface ILoadBalancingPolicy /// host in remote datacenters when the policy itself always picks host in the /// local datacenter first.

///
+ /// Metadata snapshot provider of the session for which the policy is created. /// the host of which to return the distance of. /// /// the HostDistance to host. - HostDistance Distance(Host host); + HostDistance Distance(IMetadataSnapshotProvider metadata, Host host); /// /// Returns the hosts to use for a new query.

Each new query will call this @@ -58,11 +65,12 @@ public interface ILoadBalancingPolicy /// be so), the next host will be used. If all hosts of the returned /// Iterator are down, the query will fail.

///
+ /// Cluster instance for which the policy is created. /// The query for which to build a plan, it can be null. /// Keyspace on which the query is going to be executed, it can be null. /// An iterator of Host. The query is tried against the hosts returned /// by this iterator in order, until the query has been sent successfully to one /// of the host. - IEnumerable NewQueryPlan(string keyspace, IStatement query); + IEnumerable NewQueryPlan(ICluster cluster, string keyspace, IStatement query); } } diff --git a/src/Cassandra/Policies/ILocalDatacenterProvider.cs b/src/Cassandra/Policies/ILocalDatacenterProvider.cs index 481b383c0..7be66d2d0 100644 --- a/src/Cassandra/Policies/ILocalDatacenterProvider.cs +++ b/src/Cassandra/Policies/ILocalDatacenterProvider.cs @@ -13,6 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using Cassandra.Connections.Control; using Cassandra.SessionManagement; namespace Cassandra @@ -30,6 +31,6 @@ internal interface ILocalDatacenterProvider /// /// Should be called after we have an initialized cluster instance. /// - void Initialize(IInternalCluster cluster); + void Initialize(IInternalCluster cluster, IInternalMetadata internalMetadata); } } \ No newline at end of file diff --git a/src/Cassandra/Policies/ISpeculativeExecutionPolicy.cs b/src/Cassandra/Policies/ISpeculativeExecutionPolicy.cs index f470bb4ad..66734b9b7 100644 --- a/src/Cassandra/Policies/ISpeculativeExecutionPolicy.cs +++ b/src/Cassandra/Policies/ISpeculativeExecutionPolicy.cs @@ -14,7 +14,7 @@ // limitations under the License. // -using System; +using System.Threading.Tasks; // ReSharper disable once CheckNamespace namespace Cassandra @@ -23,19 +23,25 @@ namespace Cassandra /// The policy that decides if the driver will send speculative queries to the next hosts when the current host takes too long to respond. /// only idempotent statements will be speculatively retried, see for more information. /// - public interface ISpeculativeExecutionPolicy : IDisposable + public interface ISpeculativeExecutionPolicy { /// /// Initializes the policy at cluster startup. /// - void Initialize(ICluster cluster); + Task InitializeAsync(IMetadataSnapshotProvider metadata); /// /// Returns the plan to use for a new query. /// + /// the cluster instance. /// the currently logged keyspace /// the query for which to build a plan. /// - ISpeculativeExecutionPlan NewPlan(string keyspace, IStatement statement); + ISpeculativeExecutionPlan NewPlan(ICluster cluster, string keyspace, IStatement statement); + + /// + /// Disposes the policy at cluster shutdown. + /// + Task ShutdownAsync(); } -} +} \ No newline at end of file diff --git a/src/Cassandra/Policies/LocalDatacenterProvider.cs b/src/Cassandra/Policies/LocalDatacenterProvider.cs index 07eedeca6..a1e2b9a2d 100644 --- a/src/Cassandra/Policies/LocalDatacenterProvider.cs +++ b/src/Cassandra/Policies/LocalDatacenterProvider.cs @@ -1,12 +1,12 @@ -// +// // Copyright (C) DataStax Inc. -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,23 +16,26 @@ using System; using System.Collections.Generic; using System.Linq; +using Cassandra.Connections.Control; using Cassandra.SessionManagement; namespace Cassandra { internal class LocalDatacenterProvider : ILocalDatacenterProvider { - private bool _initialized = false; + private volatile bool _initialized = false; - private IInternalCluster _cluster; - private IEnumerable _availableDcs; - private string _availableDcsStr; - private string _cachedDatacenter; + private volatile IInternalCluster _cluster; + private volatile IInternalMetadata _internalMetadata; + private volatile IEnumerable _availableDcs; + private volatile string _availableDcsStr; + private volatile string _cachedDatacenter; - public void Initialize(IInternalCluster cluster) + public void Initialize(IInternalCluster cluster, IInternalMetadata internalMetadata) { _cluster = cluster; - _availableDcs = _cluster.AllHosts().Select(h => h.Datacenter).Where(dc => dc != null).Distinct().ToList(); + _internalMetadata = internalMetadata; + _availableDcs = internalMetadata.AllHosts().Select(h => h.Datacenter).Where(dc => dc != null).Distinct().ToList(); _availableDcsStr = string.Join(", ", _availableDcs); _initialized = true; } @@ -67,7 +70,7 @@ public string DiscoverLocalDatacenter(bool inferLocalDc, string policyDatacenter "It can be specified in the load balancing policy constructor or " + $"via the Builder.WithLocalDatacenter() method. Available datacenters: {_availableDcsStr}."); } - + // implicit contact point so infer the local datacenter from the control connection host return InferLocalDatacenter(); } @@ -86,20 +89,20 @@ private string ValidateAndReturnDatacenter(string datacenter) private string InferLocalDatacenter() { - var cc = _cluster.GetControlConnection(); + var cc = _internalMetadata.ControlConnection; if (cc == null) { throw new DriverInternalError("ControlConnection was not correctly set"); } // Use the host used by the control connection - _cachedDatacenter = - cc.Host?.Datacenter ?? + _cachedDatacenter = + cc.Host?.Datacenter ?? throw new InvalidOperationException( "The local datacenter could not be inferred from the implicit contact point, " + "please set it explicitly in the load balancing policy constructor or " + $"via the Builder.WithLocalDatacenter() method. Available datacenters: {_availableDcsStr}."); - + return _cachedDatacenter; } } diff --git a/src/Cassandra/Policies/NoSpeculativeExecutionPolicy.cs b/src/Cassandra/Policies/NoSpeculativeExecutionPolicy.cs index 500aa96e7..100824c62 100644 --- a/src/Cassandra/Policies/NoSpeculativeExecutionPolicy.cs +++ b/src/Cassandra/Policies/NoSpeculativeExecutionPolicy.cs @@ -14,6 +14,8 @@ // limitations under the License. // +using System.Threading.Tasks; +using Cassandra.Tasks; // ReSharper disable once CheckNamespace namespace Cassandra @@ -30,20 +32,20 @@ private NoSpeculativeExecutionPolicy() { } - - public void Dispose() + + public Task InitializeAsync(IMetadataSnapshotProvider metadata) { - + return TaskHelper.Completed; } - public void Initialize(ICluster cluster) + public ISpeculativeExecutionPlan NewPlan(ICluster cluster, string keyspace, IStatement statement) { - + return Plan; } - public ISpeculativeExecutionPlan NewPlan(string keyspace, IStatement statement) + public Task ShutdownAsync() { - return Plan; + return TaskHelper.Completed; } private class NoSpeculativeExecutionPlan : ISpeculativeExecutionPlan diff --git a/src/Cassandra/Policies/RetryLoadBalancingPolicy.cs b/src/Cassandra/Policies/RetryLoadBalancingPolicy.cs index 1cbd7c079..5fc4ad0dd 100644 --- a/src/Cassandra/Policies/RetryLoadBalancingPolicy.cs +++ b/src/Cassandra/Policies/RetryLoadBalancingPolicy.cs @@ -17,6 +17,7 @@ using System; using System.Collections.Generic; using System.Threading; +using System.Threading.Tasks; namespace Cassandra { @@ -34,22 +35,22 @@ public RetryLoadBalancingPolicy(ILoadBalancingPolicy loadBalancingPolicy, IRecon public ILoadBalancingPolicy LoadBalancingPolicy { get; } - public void Initialize(ICluster cluster) + public Task InitializeAsync(IMetadataSnapshotProvider metadata) { - LoadBalancingPolicy.Initialize(cluster); + return LoadBalancingPolicy.InitializeAsync(metadata); } - public HostDistance Distance(Host host) + public HostDistance Distance(IMetadataSnapshotProvider metadata, Host host) { - return LoadBalancingPolicy.Distance(host); + return LoadBalancingPolicy.Distance(metadata, host); } - public IEnumerable NewQueryPlan(string keyspace, IStatement query) + public IEnumerable NewQueryPlan(ICluster cluster, string keyspace, IStatement query) { IReconnectionSchedule schedule = ReconnectionPolicy.NewSchedule(); while (true) { - IEnumerable childQueryPlan = LoadBalancingPolicy.NewQueryPlan(keyspace, query); + IEnumerable childQueryPlan = LoadBalancingPolicy.NewQueryPlan(cluster, keyspace, query); foreach (Host host in childQueryPlan) yield return host; diff --git a/src/Cassandra/Policies/RoundRobinPolicy.cs b/src/Cassandra/Policies/RoundRobinPolicy.cs index 990839ead..909f13012 100644 --- a/src/Cassandra/Policies/RoundRobinPolicy.cs +++ b/src/Cassandra/Policies/RoundRobinPolicy.cs @@ -14,15 +14,16 @@ // limitations under the License. // -using System.Collections.Generic; -using System.Threading; +using System.Collections.Generic; using System.Linq; - +using System.Threading; +using System.Threading.Tasks; +using Cassandra.Tasks; namespace Cassandra { /// - /// A Round-robin load balancing policy. + /// A Round-robin load balancing policy. /// This policy queries nodes in a /// round-robin fashion. For a given query, if an host fail, the next one /// (following the round-robin order) is tried, until all hosts have been tried. @@ -35,12 +36,11 @@ namespace Cassandra /// public class RoundRobinPolicy : ILoadBalancingPolicy { - ICluster _cluster; - int _index; + private int _index; - public void Initialize(ICluster cluster) + public Task InitializeAsync(IMetadataSnapshotProvider metadata) { - this._cluster = cluster; + return TaskHelper.Completed; } /// @@ -49,9 +49,10 @@ public void Initialize(ICluster cluster) /// datacenter deployment. If you use multiple datacenter, see /// DCAwareRoundRobinPolicy instead.

///
+ /// The metadata instance associated with the cluster for which the policy is created. /// the host of which to return the distance of. /// the HostDistance to host. - public HostDistance Distance(Host host) + public HostDistance Distance(IMetadataSnapshotProvider metadata, Host host) { return HostDistance.Local; } @@ -62,14 +63,15 @@ public HostDistance Distance(Host host) /// plans returned will cycle over all the host of the cluster in a round-robin /// fashion.

/// + /// The cluster instance for which the policy is created. /// Keyspace on which the query is going to be executed /// the query for which to build the plan. /// a new query plan, i.e. an iterator indicating which host to try /// first for querying, which one to use as failover, etc... - public IEnumerable NewQueryPlan(string keyspace, IStatement query) + public IEnumerable NewQueryPlan(ICluster cluster, string keyspace, IStatement query) { //shallow copy the all hosts - var hosts = (from h in _cluster.AllHosts() select h).ToArray(); + var hosts = (from h in cluster.Metadata.AllHostsSnapshot() select h).ToArray(); var startIndex = Interlocked.Increment(ref _index); //Simplified overflow protection @@ -84,4 +86,4 @@ public IEnumerable NewQueryPlan(string keyspace, IStatement query) } } } -} +} \ No newline at end of file diff --git a/src/Cassandra/Policies/TokenAwarePolicy.cs b/src/Cassandra/Policies/TokenAwarePolicy.cs index ddd1ccfe5..a756fadb3 100644 --- a/src/Cassandra/Policies/TokenAwarePolicy.cs +++ b/src/Cassandra/Policies/TokenAwarePolicy.cs @@ -18,6 +18,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; +using System.Threading.Tasks; namespace Cassandra { @@ -26,15 +27,14 @@ namespace Cassandra /// This policy encapsulates another policy. The resulting policy works in the following way: /// /// - /// The method is inherited from the child policy. - /// The host yielded by the method will first return the + /// The method is inherited from the child policy. + /// The host yielded by the method will first return the /// replicas for the statement, based on the . /// /// /// public class TokenAwarePolicy : ILoadBalancingPolicy { - private ICluster _cluster; private readonly ThreadLocal _prng = new ThreadLocal(() => new Random( // Predictable random numbers are OK Environment.TickCount * Environment.CurrentManagedThreadId)); @@ -52,22 +52,22 @@ public TokenAwarePolicy(ILoadBalancingPolicy childPolicy) public ILoadBalancingPolicy ChildPolicy { get; } - public void Initialize(ICluster cluster) + public Task InitializeAsync(IMetadataSnapshotProvider metadata) { - _cluster = cluster; - ChildPolicy.Initialize(cluster); + return ChildPolicy.InitializeAsync(metadata); } /// /// Return the HostDistance for the provided host. /// + /// The metadata instance associated with the cluster for which the policy is created. /// the host of which to return the distance of. /// /// the HostDistance to host as returned by the wrapped /// policy. - public HostDistance Distance(Host host) + public HostDistance Distance(IMetadataSnapshotProvider metadata, Host host) { - return ChildPolicy.Distance(host); + return ChildPolicy.Distance(metadata, host); } /// @@ -77,16 +77,17 @@ public HostDistance Distance(Host host) /// IStatement.RoutingKey is not null). Following what /// it will return the plan of the child policy.

///
+ /// The cluster instance for which the policy is created. /// Keyspace on which the query is going to be executed /// the query for which to build the plan. /// the new query plan. - public IEnumerable NewQueryPlan(string loggedKeyspace, IStatement query) + public IEnumerable NewQueryPlan(ICluster cluster, string loggedKeyspace, IStatement query) { var routingKey = query?.RoutingKey; IEnumerable childIterator; if (routingKey == null) { - childIterator = ChildPolicy.NewQueryPlan(loggedKeyspace, query); + childIterator = ChildPolicy.NewQueryPlan(cluster, loggedKeyspace, query); foreach (var h in childIterator) { yield return h; @@ -95,12 +96,12 @@ public IEnumerable NewQueryPlan(string loggedKeyspace, IStatement query) } var keyspace = query.Keyspace ?? loggedKeyspace; - var replicas = _cluster.GetReplicas(keyspace, routingKey.RawRoutingKey); + var replicas = cluster.Metadata.GetReplicasSnapshot(keyspace, routingKey.RawRoutingKey); var localReplicaSet = new HashSet(); var localReplicaList = new List(replicas.Count); // We can't do it lazily as we need to balance the load between local replicas - foreach (var localReplica in replicas.Where(h => ChildPolicy.Distance(h) == HostDistance.Local)) + foreach (var localReplica in replicas.Where(h => ChildPolicy.Distance(cluster.Metadata, h) == HostDistance.Local)) { localReplicaSet.Add(localReplica); localReplicaList.Add(localReplica); @@ -117,7 +118,7 @@ public IEnumerable NewQueryPlan(string loggedKeyspace, IStatement query) } // Then, return the rest of child policy hosts - childIterator = ChildPolicy.NewQueryPlan(loggedKeyspace, query); + childIterator = ChildPolicy.NewQueryPlan(cluster, loggedKeyspace, query); foreach (var h in childIterator) { if (localReplicaSet.Contains(h)) diff --git a/src/Cassandra/ProtocolEvents/ProtocolEventDebouncer.cs b/src/Cassandra/ProtocolEvents/ProtocolEventDebouncer.cs index 92bcc7ec2..c46037427 100644 --- a/src/Cassandra/ProtocolEvents/ProtocolEventDebouncer.cs +++ b/src/Cassandra/ProtocolEvents/ProtocolEventDebouncer.cs @@ -16,6 +16,7 @@ using System; using System.Linq; +using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Dataflow; @@ -32,7 +33,8 @@ internal class ProtocolEventDebouncer : IProtocolEventDebouncer private readonly ActionBlock, ProtocolEvent, bool>> _enqueueBlock; private readonly ActionBlock _processQueueBlock; private readonly SlidingWindowExclusiveTimer _timer; - + + private int _disposed = 0; private volatile EventQueue _queue = null; public ProtocolEventDebouncer(ITimerFactory timerFactory, TimeSpan delay, TimeSpan maxDelay) @@ -114,6 +116,12 @@ public async Task HandleEventAsync(ProtocolEvent ev, bool processNow) /// public async Task ShutdownAsync() { + // Dispose once + if (Interlocked.Increment(ref _disposed) != 1) + { + return; + } + _enqueueBlock.Complete(); await _enqueueBlock.Completion.ConfigureAwait(false); diff --git a/src/Cassandra/ProtocolVersion.cs b/src/Cassandra/ProtocolVersion.cs index a0054210b..560230d09 100644 --- a/src/Cassandra/ProtocolVersion.cs +++ b/src/Cassandra/ProtocolVersion.cs @@ -79,11 +79,11 @@ internal static class ProtocolVersionExtensions /// /// Determines if the protocol version is supported by this driver. /// - public static bool IsSupported(this ProtocolVersion version, Configuration config) + public static bool IsSupported(this ProtocolVersion version, bool allowBetaProtocolVersions) { if (version.IsBeta()) { - return config.AllowBetaProtocolVersions; + return allowBetaProtocolVersions; } return version >= ProtocolVersion.MinSupported && version <= ProtocolVersion.MaxSupported; @@ -94,7 +94,7 @@ public static bool IsSupported(this ProtocolVersion version, Configuration confi /// Returns zero when there isn't a lower supported version. /// /// - public static ProtocolVersion GetLowerSupported(this ProtocolVersion version, Configuration config) + public static ProtocolVersion GetLowerSupported(this ProtocolVersion version, bool allowBetaProtocolVersions) { var lowerVersion = version; do @@ -111,7 +111,7 @@ public static ProtocolVersion GetLowerSupported(this ProtocolVersion version, Co { lowerVersion = lowerVersion - 1; } - } while (lowerVersion > 0 && !lowerVersion.IsSupported(config)); + } while (lowerVersion > 0 && !lowerVersion.IsSupported(allowBetaProtocolVersions)); return lowerVersion; } @@ -119,7 +119,8 @@ public static ProtocolVersion GetLowerSupported(this ProtocolVersion version, Co /// /// Gets the highest supported protocol version collectively by the given hosts. /// - public static ProtocolVersion GetHighestCommon(this ProtocolVersion version, Configuration config, IEnumerable hosts) + public static ProtocolVersion GetHighestCommon( + this ProtocolVersion version, bool allowBetaProtocolVersions, IEnumerable hosts) { var maxVersion = (byte)version; var v3Requirement = false; @@ -141,7 +142,7 @@ public static ProtocolVersion GetHighestCommon(this ProtocolVersion version, Con { // Anything 4.0.0+ has a max protocol version of V5 and requires at least V3. v3Requirement = true; - maxVersion = config.AllowBetaProtocolVersions + maxVersion = allowBetaProtocolVersions ? Math.Min((byte)ProtocolVersion.V5, maxVersion) : Math.Min((byte)ProtocolVersion.V4, maxVersion); maxVersionWith3OrMore = maxVersion; @@ -313,6 +314,14 @@ public static bool CanStartupResponseErrorBeWrapped(this ProtocolVersion version return version >= ProtocolVersion.V4; } + /// + /// Determines whether UDTs are supported in the provided protocol version. + /// + public static bool SupportsUserDefinedTypes(this ProtocolVersion version) + { + return version >= ProtocolVersion.V3; + } + public static int GetHeaderSize(this ProtocolVersion version) { if (version.Uses2BytesStreamIds()) diff --git a/src/Cassandra/QueryTrace.cs b/src/Cassandra/QueryTrace.cs index 1c082f8de..4ec7d7358 100644 --- a/src/Cassandra/QueryTrace.cs +++ b/src/Cassandra/QueryTrace.cs @@ -18,6 +18,7 @@ using System.Collections.Generic; using System.Net; using System.Threading.Tasks; +using Cassandra.SessionManagement; using Cassandra.Tasks; namespace Cassandra @@ -33,8 +34,8 @@ namespace Cassandra public class QueryTrace { private readonly object _fetchLock = new object(); - private readonly Metadata _metadata; private readonly Guid _traceId; + private readonly IInternalSession _session; private IPAddress _coordinator; private int _duration = int.MinValue; private List _events; @@ -164,7 +165,7 @@ public IPAddress ClientAddress internal set { _clientAddress = value; } } - public QueryTrace(Guid traceId, ISession session) + internal QueryTrace(Guid traceId, IInternalSession session) { if (session == null) { @@ -177,7 +178,7 @@ public QueryTrace(Guid traceId, ISession session) //The instance is created before fetching the actual trace metadata //The properties will be populated later. _traceId = traceId; - _metadata = session.Cluster.Metadata; + _session = session; _metadataFetchSyncTimeout = session.Cluster.Configuration.DefaultRequestOptions.QueryAbortTimeout; } @@ -215,7 +216,7 @@ private void DoFetchTrace() { try { - TaskHelper.WaitToComplete(LoadAsync(), _metadataFetchSyncTimeout); + Load(); } catch (Exception ex) { @@ -226,13 +227,22 @@ private void DoFetchTrace() _isDisconnected = false; } } + internal QueryTrace Load() + { + // mark as disconnected, guaranteeing that it wont make metadata fetches triggered by a property get + // ReSharper disable once InconsistentlySynchronizedField : Can be both async and sync, don't mind + _isDisconnected = false; + var metadata = _session.TryInitAndGetMetadata(); + return TaskHelper.WaitToComplete(metadata.GetQueryTraceAsync(this), _metadataFetchSyncTimeout); + } - internal Task LoadAsync() + internal async Task LoadAsync() { // mark as disconnected, guaranteeing that it wont make metadata fetches triggered by a property get // ReSharper disable once InconsistentlySynchronizedField : Can be both async and sync, don't mind _isDisconnected = false; - return _metadata.GetQueryTraceAsync(this); + var metadata = await _session.TryInitAndGetMetadataAsync().ConfigureAwait(false); + return await metadata.GetQueryTraceAsync(this).ConfigureAwait(false); } /// diff --git a/src/Cassandra/Requests/IPrepareHandler.cs b/src/Cassandra/Requests/IPrepareHandler.cs index 9b0f63657..de15aa1fc 100644 --- a/src/Cassandra/Requests/IPrepareHandler.cs +++ b/src/Cassandra/Requests/IPrepareHandler.cs @@ -23,7 +23,7 @@ namespace Cassandra.Requests { internal interface IPrepareHandler { - Task Prepare( + Task PrepareAsync( PrepareRequest request, IInternalSession session, IEnumerator queryPlan); } } \ No newline at end of file diff --git a/src/Cassandra/Requests/IRequestHandler.cs b/src/Cassandra/Requests/IRequestHandler.cs index da12db4b2..eb4c8c524 100644 --- a/src/Cassandra/Requests/IRequestHandler.cs +++ b/src/Cassandra/Requests/IRequestHandler.cs @@ -19,6 +19,7 @@ using System.Net; using System.Threading.Tasks; using Cassandra.Connections; +using Cassandra.Connections.Control; using Cassandra.ExecutionProfiles; using Cassandra.Serialization; @@ -29,6 +30,8 @@ namespace Cassandra.Requests /// internal interface IRequestHandler { + IInternalMetadata InternalMetadata { get; } + IRequestOptions RequestOptions { get; } IExtendedRetryPolicy RetryPolicy { get; } @@ -46,6 +49,11 @@ internal interface IRequestHandler /// Marks this instance as completed (if not already) and in a new Task using the default scheduler, it invokes the action and sets the result /// bool SetCompleted(RowSet result, Action action); + + /// + /// Marks this instance as completed (if not already) and in a new Task using the default scheduler, it awaits the task and sets the result + /// + bool SetCompletedWithTask(RowSet result, Func task); void SetNoMoreHosts(NoHostAvailableException ex, IRequestExecution execution); diff --git a/src/Cassandra/Requests/IRequestHandlerFactory.cs b/src/Cassandra/Requests/IRequestHandlerFactory.cs index 1235c8dd7..b7d08f608 100644 --- a/src/Cassandra/Requests/IRequestHandlerFactory.cs +++ b/src/Cassandra/Requests/IRequestHandlerFactory.cs @@ -14,6 +14,7 @@ // limitations under the License. // +using Cassandra.Connections.Control; using Cassandra.ExecutionProfiles; using Cassandra.Serialization; using Cassandra.SessionManagement; @@ -22,10 +23,17 @@ namespace Cassandra.Requests { internal interface IRequestHandlerFactory { - IRequestHandler Create(IInternalSession session, ISerializer serializer, IRequest request, IStatement statement, IRequestOptions options); + IRequestHandler Create( + IInternalSession session, + IInternalMetadata internalMetadata, + ISerializer serializer, + IRequest request, + IStatement statement, + IRequestOptions options); - IRequestHandler Create(IInternalSession session, ISerializer serializer, IStatement statement, IRequestOptions options); + IRequestHandler Create( + IInternalSession session, IInternalMetadata internalMetadata, ISerializer serializer, IStatement statement, IRequestOptions options); - IRequestHandler Create(IInternalSession session, ISerializer serializer); + IRequestHandler Create(IInternalSession session, IInternalMetadata internalMetadata, ISerializer serializer); } } \ No newline at end of file diff --git a/src/Cassandra/Requests/PrepareHandler.cs b/src/Cassandra/Requests/PrepareHandler.cs index c8d22bb0a..52794483a 100644 --- a/src/Cassandra/Requests/PrepareHandler.cs +++ b/src/Cassandra/Requests/PrepareHandler.cs @@ -20,7 +20,9 @@ using System.Net; using System.Net.Sockets; using System.Threading.Tasks; + using Cassandra.Connections; +using Cassandra.Connections.Control; using Cassandra.Responses; using Cassandra.Serialization; using Cassandra.SessionManagement; @@ -42,7 +44,7 @@ public PrepareHandler(ISerializerManager serializerManager, IInternalCluster clu _reprepareHandler = reprepareHandler; } - public async Task Prepare( + public async Task PrepareAsync( PrepareRequest request, IInternalSession session, IEnumerator queryPlan) { var prepareResult = await SendRequestToOneNode(session, queryPlan, request).ConfigureAwait(false); @@ -55,7 +57,8 @@ public async Task Prepare( return prepareResult.PreparedStatement; } - private async Task SendRequestToOneNode(IInternalSession session, IEnumerator queryPlan, PrepareRequest request) + private async Task SendRequestToOneNode( + IInternalSession session, IEnumerator queryPlan, PrepareRequest request) { var triedHosts = new Dictionary(); @@ -70,7 +73,9 @@ private async Task SendRequestToOneNode(IInternalSession session, var result = await connection.Send(request).ConfigureAwait(false); return new PrepareResult { - PreparedStatement = await GetPreparedStatement(result, request, request.Keyspace ?? connection.Keyspace, session.Cluster).ConfigureAwait(false), + PreparedStatement = + await GetPreparedStatement( + result, request, request.Keyspace ?? connection.Keyspace).ConfigureAwait(false), TriedHosts = triedHosts, HostAddress = host.Address }; @@ -127,7 +132,7 @@ private Host GetNextHost(IEnumerator queryPlan, out HostDistance distance) } private async Task GetPreparedStatement( - Response response, PrepareRequest request, string keyspace, ICluster cluster) + Response response, PrepareRequest request, string keyspace) { if (response == null) { @@ -145,20 +150,20 @@ private async Task GetPreparedStatement( } var prepared = (OutputPrepared)output; var ps = new PreparedStatement( - prepared.VariablesRowsMetadata, - prepared.QueryId, + prepared.VariablesRowsMetadata, + prepared.QueryId, new ResultMetadata(prepared.ResultMetadataId, prepared.ResultRowsMetadata), - request.Query, - keyspace, + request.Query, + keyspace, _serializerManager) { IncomingPayload = resultResponse.CustomPayload }; - await FillRoutingInfo(ps, cluster).ConfigureAwait(false); + await FillRoutingInfo(ps, _cluster.InternalMetadata).ConfigureAwait(false); return ps; } - private static async Task FillRoutingInfo(PreparedStatement ps, ICluster cluster) + private static async Task FillRoutingInfo(PreparedStatement ps, IInternalMetadata internalMetadata) { var column = ps.Variables.Columns.FirstOrDefault(); if (column?.Keyspace == null) @@ -181,7 +186,7 @@ private static async Task FillRoutingInfo(PreparedStatement ps, ICluster cluster try { const string msgRoutingNotSet = "Routing information could not be set for query \"{0}\""; - var table = await cluster.Metadata.GetTableAsync(column.Keyspace, column.Table).ConfigureAwait(false); + var table = await internalMetadata.GetTableAsync(column.Keyspace, column.Table).ConfigureAwait(false); if (table == null) { Logger.Info(msgRoutingNotSet, ps.Cql); diff --git a/src/Cassandra/Requests/ReprepareHandler.cs b/src/Cassandra/Requests/ReprepareHandler.cs index 685ccc0ac..3eb7357ab 100644 --- a/src/Cassandra/Requests/ReprepareHandler.cs +++ b/src/Cassandra/Requests/ReprepareHandler.cs @@ -31,7 +31,8 @@ public async Task ReprepareOnAllNodesWithExistingConnections( IInternalSession session, PrepareRequest request, PrepareResult prepareResult) { var pools = session.GetPools(); - var hosts = session.InternalCluster.AllHosts(); + var metadata = await session.TryInitAndGetMetadataAsync().ConfigureAwait(false); + var hosts = metadata.AllHosts(); var poolsByHosts = pools.Join( hosts, po => po.Key, h => h.Address, diff --git a/src/Cassandra/Requests/RequestExecution.cs b/src/Cassandra/Requests/RequestExecution.cs index bde33115d..0476a9617 100644 --- a/src/Cassandra/Requests/RequestExecution.cs +++ b/src/Cassandra/Requests/RequestExecution.cs @@ -47,7 +47,11 @@ internal class RequestExecution : IRequestExecution /// private volatile Host _host; - public RequestExecution(IRequestHandler parent, IInternalSession session, IRequest request, IRequestObserver requestObserver) + public RequestExecution( + IRequestHandler parent, + IInternalSession session, + IRequest request, + IRequestObserver requestObserver) { _parent = parent; _session = session; @@ -210,7 +214,7 @@ private void HandleRowSetResult(Response response) if (resultResponse.Output is OutputSchemaChange schemaChange) { //Schema changes need to do blocking operations - HandleSchemaChange(resultResponse, schemaChange); + HandleSchemaChangeAsync(resultResponse, schemaChange); return; } RowSet rs; @@ -229,7 +233,7 @@ private void HandleRowSetResult(Response response) _parent.SetCompleted(null, FillRowSet(rs, resultResponse)); } - private void HandleSchemaChange(ResultResponse response, OutputSchemaChange schemaChange) + private void HandleSchemaChangeAsync(ResultResponse response, OutputSchemaChange schemaChange) { var result = FillRowSet(new RowSet(), response); @@ -237,17 +241,19 @@ private void HandleSchemaChange(ResultResponse response, OutputSchemaChange sche result.Info.SetSchemaInAgreement(false); // Wait for the schema change before returning the result - _parent.SetCompleted( + _parent.SetCompletedWithTask( result, - () => + async () => { - var schemaAgreed = _session.Cluster.Metadata.WaitForSchemaAgreement(_connection); + var schemaAgreed = await _parent.InternalMetadata.WaitForSchemaAgreementAsync(_connection).ConfigureAwait(false); result.Info.SetSchemaInAgreement(schemaAgreed); try { - TaskHelper.WaitToComplete( - _session.InternalCluster.GetControlConnection().HandleSchemaChangeEvent(schemaChange.SchemaChangeEventArgs, true), - _session.Cluster.Configuration.ProtocolOptions.MaxSchemaAgreementWaitSeconds * 1000); + await _parent.InternalMetadata.ControlConnection + .HandleSchemaChangeEvent(schemaChange.SchemaChangeEventArgs, true) + .WaitToCompleteAsync( + _session.Cluster.Configuration.ProtocolOptions.MaxSchemaAgreementWaitSeconds * 1000) + .ConfigureAwait(false); } catch (TimeoutException) { @@ -322,7 +328,8 @@ private void SetAutoPage(RowSet rs, IInternalSession session) var request = (IQueryRequest)_parent.BuildRequest(); request.PagingState = pagingState; - return _session.Cluster.Configuration.RequestHandlerFactory.Create(session, _parent.Serializer, request, statement, _parent.RequestOptions).SendAsync(); + return _session.Cluster.Configuration.RequestHandlerFactory.Create( + session, _parent.InternalMetadata, _parent.Serializer, request, statement, _parent.RequestOptions).SendAsync(); }, _parent.RequestOptions.QueryAbortTimeout, _session.MetricsManager); } } diff --git a/src/Cassandra/Requests/RequestHandler.cs b/src/Cassandra/Requests/RequestHandler.cs index cca23b0b7..327807f8f 100644 --- a/src/Cassandra/Requests/RequestHandler.cs +++ b/src/Cassandra/Requests/RequestHandler.cs @@ -24,6 +24,7 @@ using Cassandra.Collections; using Cassandra.Connections; +using Cassandra.Connections.Control; using Cassandra.ExecutionProfiles; using Cassandra.Observers.Abstractions; using Cassandra.Serialization; @@ -49,25 +50,37 @@ internal class RequestHandler : IRequestHandler private ISpeculativeExecutionPlan _executionPlan; private volatile HashedWheelTimer.ITimeout _nextExecutionTimeout; private readonly IRequestObserver _requestObserver; + public IExtendedRetryPolicy RetryPolicy { get; } + public ISerializer Serializer { get; } + public IStatement Statement { get; } + public IRequestOptions RequestOptions { get; } + public IInternalMetadata InternalMetadata { get; } + /// /// Creates a new instance using a request, the statement and the execution profile. /// public RequestHandler( - IInternalSession session, ISerializer serializer, IRequest request, IStatement statement, IRequestOptions requestOptions) + IInternalSession session, + IInternalMetadata internalMetadata, + ISerializer serializer, + IRequest request, + IStatement statement, + IRequestOptions requestOptions) { _session = session ?? throw new ArgumentNullException(nameof(session)); + InternalMetadata = internalMetadata ?? throw new ArgumentNullException(nameof(internalMetadata)); _requestObserver = session.ObserverFactory.CreateRequestObserver(); _requestResultHandler = new TcsMetricsRequestResultHandler(_requestObserver); _request = request; Serializer = serializer ?? throw new ArgumentNullException(nameof(session)); Statement = statement; RequestOptions = requestOptions ?? throw new ArgumentNullException(nameof(requestOptions)); - + RetryPolicy = RequestOptions.RetryPolicy; if (statement?.RetryPolicy != null) @@ -75,23 +88,35 @@ public RequestHandler( RetryPolicy = statement.RetryPolicy.Wrap(RetryPolicy); } - _queryPlan = RequestHandler.GetQueryPlan(session, statement, RequestOptions.LoadBalancingPolicy).GetEnumerator(); + _queryPlan = RequestHandler.GetQueryPlan( + session, _session.Cluster.Metadata, statement, RequestOptions.LoadBalancingPolicy).GetEnumerator(); } /// /// Creates a new instance using the statement to build the request. /// Statement can not be null. /// - public RequestHandler(IInternalSession session, ISerializer serializer, IStatement statement, IRequestOptions requestOptions) - : this(session, serializer, RequestHandler.GetRequest(statement, serializer, requestOptions), statement, requestOptions) + public RequestHandler( + IInternalSession session, + IInternalMetadata internalMetadata, + ISerializer serializer, + IStatement statement, + IRequestOptions requestOptions) + : this( + session, + internalMetadata, + serializer, + RequestHandler.GetRequest(statement, serializer, requestOptions), + statement, + requestOptions) { } /// /// Creates a new instance with no request, suitable for getting a connection. /// - public RequestHandler(IInternalSession session, ISerializer serializer) - : this(session, serializer, null, null, session.Cluster.Configuration.DefaultRequestOptions) + public RequestHandler(IInternalSession session, IInternalMetadata internalMetadata, ISerializer serializer) + : this(session, internalMetadata, serializer, null, null, session.Cluster.Configuration.DefaultRequestOptions) { } @@ -100,13 +125,13 @@ public RequestHandler(IInternalSession session, ISerializer serializer) /// In the special case when a Host is provided at Statement level, it will return a query plan with a single /// host. /// - private static IEnumerable GetQueryPlan(ISession session, IStatement statement, ILoadBalancingPolicy lbp) + private static IEnumerable GetQueryPlan(ISession session, IMetadata metadata, IStatement statement, ILoadBalancingPolicy lbp) { // Single host iteration var host = (statement as Statement)?.Host; return host == null - ? lbp.NewQueryPlan(session.Keyspace, statement) + ? lbp.NewQueryPlan(session.Cluster, session.Keyspace, statement) : Enumerable.Repeat(host, 1); } @@ -182,19 +207,22 @@ public bool SetCompleted(RowSet result, Action action) { return SetCompleted(null, result, action); } + + /// + public bool SetCompletedWithTask(RowSet result, Func task) + { + return SetCompletedWithTask(null, result, task); + } - /// - /// Marks this instance as completed. - /// If ex is not null, sets the exception. - /// If action is not null, it invokes it using the default task scheduler. - /// - private bool SetCompleted(Exception ex, RowSet result, Action action) + private bool SetCompletedState() + { + var finishedNow = Interlocked.CompareExchange(ref _state, RequestHandler.StateCompleted, RequestHandler.StateInit) + == RequestHandler.StateInit; + return finishedNow; + } + + private void CancelRunningExecutions() { - var finishedNow = Interlocked.CompareExchange(ref _state, RequestHandler.StateCompleted, RequestHandler.StateInit) == RequestHandler.StateInit; - if (!finishedNow) - { - return false; - } //Cancel the current timer //When the next execution timer is being scheduled at the *same time* //the timer is not going to be cancelled, in that case, this instance is going to stay alive a little longer @@ -203,11 +231,21 @@ private bool SetCompleted(Exception ex, RowSet result, Action action) { execution.Cancel(); } + } + + private bool SetResultException(Exception ex) + { if (ex != null) { _requestResultHandler.TrySetException(ex); return true; } + + return false; + } + + private void InvokeCallbackAndSetResult(RowSet result, Action action) + { if (action != null) { //Create a new Task using the default scheduler, invoke the action and set the result @@ -223,9 +261,82 @@ private bool SetCompleted(Exception ex, RowSet result, Action action) _requestResultHandler.TrySetException(actionEx); } }); - return true; + return; } + _requestResultHandler.TrySetResult(result); + return; + } + + private void AwaitTaskAndSetResult(RowSet result, Func task) + { + if (task != null) + { + //Create a new Task using the default scheduler, invoke the action and set the result + Task.Run(async () => + { + try + { + await task().ConfigureAwait(false); + _requestResultHandler.TrySetResult(result); + } + catch (Exception actionEx) + { + _requestResultHandler.TrySetException(actionEx); + } + }); + return; + } + + _requestResultHandler.TrySetResult(result); + return; + } + + /// + /// Marks this instance as completed. + /// If ex is not null, sets the exception. + /// If action is not null, it invokes it using the default task scheduler. + /// + private bool SetCompleted(Exception ex, RowSet result, Action action) + { + var finishedNow = SetCompletedState(); + if (!finishedNow) + { + return false; + } + + CancelRunningExecutions(); + + if (SetResultException(ex)) + { + return true; + } + + InvokeCallbackAndSetResult(result, action); + return true; + } + + /// + /// Marks this instance as completed. + /// If ex is not null, sets the exception. + /// If action is not null, it invokes it using the default task scheduler. + /// + private bool SetCompletedWithTask(Exception ex, RowSet result, Func task) + { + var finishedNow = SetCompletedState(); + if (!finishedNow) + { + return false; + } + + CancelRunningExecutions(); + + if (SetResultException(ex)) + { + return true; + } + + AwaitTaskAndSetResult(result, task); return true; } @@ -414,7 +525,8 @@ private void ScheduleNext(Host currentHost) } if (_executionPlan == null) { - _executionPlan = RequestOptions.SpeculativeExecutionPolicy.NewPlan(_session.Keyspace, Statement); + _executionPlan = RequestOptions.SpeculativeExecutionPolicy.NewPlan( + _session.Cluster, _session.Keyspace, Statement); } var delay = _executionPlan.NextExecution(currentHost); if (delay <= 0) diff --git a/src/Cassandra/Requests/RequestHandlerFactory.cs b/src/Cassandra/Requests/RequestHandlerFactory.cs index 6763c0fd4..c6473aecb 100644 --- a/src/Cassandra/Requests/RequestHandlerFactory.cs +++ b/src/Cassandra/Requests/RequestHandlerFactory.cs @@ -14,6 +14,7 @@ // limitations under the License. // +using Cassandra.Connections.Control; using Cassandra.ExecutionProfiles; using Cassandra.Serialization; using Cassandra.SessionManagement; @@ -22,20 +23,26 @@ namespace Cassandra.Requests { internal class RequestHandlerFactory : IRequestHandlerFactory { - public IRequestHandler Create(IInternalSession session, ISerializer serializer, IRequest request, IStatement statement, IRequestOptions options) + public IRequestHandler Create( + IInternalSession session, + IInternalMetadata metadata, + ISerializer serializer, + IRequest request, + IStatement statement, + IRequestOptions options) { - return new RequestHandler(session, serializer, request, statement, options); + return new RequestHandler(session, metadata, serializer, request, statement, options); } public IRequestHandler Create( - IInternalSession session, ISerializer serializer, IStatement statement, IRequestOptions options) + IInternalSession session, IInternalMetadata metadata, ISerializer serializer, IStatement statement, IRequestOptions options) { - return new RequestHandler(session, serializer, statement, options); + return new RequestHandler(session, metadata, serializer, statement, options); } - public IRequestHandler Create(IInternalSession session, ISerializer serializer) + public IRequestHandler Create(IInternalSession session, IInternalMetadata metadata, ISerializer serializer) { - return new RequestHandler(session, serializer); + return new RequestHandler(session, metadata, serializer); } } } \ No newline at end of file diff --git a/src/Cassandra/Requests/TaskTimeoutHelper.cs b/src/Cassandra/Requests/TaskTimeoutHelper.cs new file mode 100644 index 000000000..cc21b1349 --- /dev/null +++ b/src/Cassandra/Requests/TaskTimeoutHelper.cs @@ -0,0 +1,87 @@ +// +// Copyright (C) DataStax Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Cassandra.Requests +{ + internal class TaskTimeoutHelper + { + public TaskTimeoutHelper(Task taskToWait) + { + TaskToWait = taskToWait; + } + + public TaskTimeoutHelper(IEnumerable> tasksToWait) + { + foreach (var task in tasksToWait) + { + TaskToWait = TaskToWait == null + ? task + : TaskToWait.ContinueWith( + prevTask => prevTask.IsFaulted + ? prevTask + : task, TaskContinuationOptions.ExecuteSynchronously).Unwrap(); + } + } + + public Task TaskToWait { get; } + + public bool WaitWithTimeout(TimeSpan timeout) + { + using (var tcs = new CancellationTokenSource()) + { + var timeoutTask = Task.Delay(timeout, tcs.Token); + + var finishedTask = Task.WaitAny(TaskToWait, timeoutTask); + + tcs.Cancel(); + try + { + timeoutTask.GetAwaiter().GetResult(); + } + catch + { + } + + return finishedTask == 0; + } + } + + public async Task WaitWithTimeoutAsync(TimeSpan timeout) + { + using (var tcs = new CancellationTokenSource()) + { + var timeoutTask = Task.Delay(timeout, tcs.Token); + + var finishedTask = await Task.WhenAny(TaskToWait, timeoutTask).ConfigureAwait(false); + + tcs.Cancel(); + try + { + await timeoutTask.ConfigureAwait(false); + } + catch + { + } + + return finishedTask == TaskToWait; + } + } + } +} \ No newline at end of file diff --git a/src/Cassandra/SchemaParser.cs b/src/Cassandra/SchemaParser.cs index f6e45cb8f..d4c7aa347 100644 --- a/src/Cassandra/SchemaParser.cs +++ b/src/Cassandra/SchemaParser.cs @@ -19,6 +19,7 @@ using System.Linq; using System.Net; using System.Threading.Tasks; +using Cassandra.Connections.Control; using Cassandra.Serialization; using Cassandra.Tasks; using SortOrder = Cassandra.DataCollectionMetadata.SortOrder; @@ -34,13 +35,13 @@ internal abstract class SchemaParser : ISchemaParser private const string SelectTraceEvents = "SELECT * FROM system_traces.events WHERE session_id = {0}"; protected readonly IMetadataQueryProvider Cc; - protected readonly Metadata Parent; + protected readonly IInternalMetadata Parent; protected abstract string SelectAggregates { get; } protected abstract string SelectFunctions { get; } protected abstract string SelectTables { get; } protected abstract string SelectUdts { get; } - protected SchemaParser(Metadata parent) + protected SchemaParser(IInternalMetadata parent) { Cc = parent.ControlConnection; Parent = parent; @@ -165,7 +166,7 @@ internal class SchemaParserV1 : SchemaParser protected override string SelectUdts => "SELECT * FROM system.schema_usertypes WHERE keyspace_name='{0}' AND type_name = '{1}'"; - internal SchemaParserV1(Metadata parent) : base(parent) + internal SchemaParserV1(IInternalMetadata parent) : base(parent) { } @@ -494,7 +495,7 @@ public override Task GetAggregateAsync(string keyspaceName, s ReturnType = DataTypeParser.ParseFqTypeName(row.GetValue("return_type")), ArgumentTypes = (row.GetValue("argument_types") ?? emptyArray).Select(s => DataTypeParser.ParseFqTypeName(s)).ToArray(), }; - var initConditionRaw = Deserialize(Cc, row.GetValue("initcond"), aggregate.StateType.TypeCode, aggregate.StateType.TypeInfo); + var initConditionRaw = Deserialize(Parent.SerializerManager, row.GetValue("initcond"), aggregate.StateType.TypeCode, aggregate.StateType.TypeInfo); if (initConditionRaw != null) { aggregate.InitialCondition = initConditionRaw.ToString(); @@ -503,13 +504,13 @@ public override Task GetAggregateAsync(string keyspaceName, s }); } - private static object Deserialize(IMetadataQueryProvider cc, byte[] buffer, ColumnTypeCode typeCode, IColumnInfo typeInfo) + private static object Deserialize(ISerializerManager serializerManager, byte[] buffer, ColumnTypeCode typeCode, IColumnInfo typeInfo) { if (buffer == null) { return null; } - return cc.Serializer.GetCurrentSerializer().Deserialize(buffer, 0, buffer.Length, typeCode, typeInfo); + return serializerManager.GetCurrentSerializer().Deserialize(buffer, 0, buffer.Length, typeCode, typeInfo); } } @@ -536,7 +537,7 @@ internal class SchemaParserV2 : SchemaParser protected override string SelectUdts => "SELECT * FROM system_schema.types WHERE keyspace_name='{0}' AND type_name = '{1}'"; - internal SchemaParserV2(Metadata parent, Func> udtResolver) + internal SchemaParserV2(IInternalMetadata parent, Func> udtResolver) : base(parent) { _udtResolver = udtResolver; @@ -914,7 +915,7 @@ internal class SchemaParserV3 : SchemaParserV2 "SELECT * FROM system_virtual_schema.columns WHERE keyspace_name = '{0}' AND table_name='{1}'"; private const string SelectVirtualKeyspaceNames = "SELECT keyspace_name FROM system_virtual_schema.keyspaces"; - internal SchemaParserV3(Metadata parent, Func> udtResolver) + internal SchemaParserV3(IInternalMetadata parent, Func> udtResolver) : base(parent, udtResolver) { diff --git a/src/Cassandra/Session.cs b/src/Cassandra/Session.cs index 423616068..1ee5e013e 100644 --- a/src/Cassandra/Session.cs +++ b/src/Cassandra/Session.cs @@ -23,14 +23,15 @@ using Cassandra.Collections; using Cassandra.Connections; +using Cassandra.Connections.Control; using Cassandra.DataStax.Graph; using Cassandra.DataStax.Insights; using Cassandra.ExecutionProfiles; +using Cassandra.Helpers; using Cassandra.Metrics; using Cassandra.Metrics.Internal; using Cassandra.Observers.Abstractions; using Cassandra.Requests; -using Cassandra.Serialization; using Cassandra.SessionManagement; using Cassandra.Tasks; @@ -39,19 +40,23 @@ namespace Cassandra /// public class Session : IInternalSession { - private readonly ISerializerManager _serializerManager; private static readonly Logger Logger = new Logger(typeof(Session)); private readonly IThreadSafeDictionary _connectionPool; private readonly IInternalCluster _cluster; - private int _disposed; - private volatile string _keyspace; private readonly IMetricsManager _metricsManager; private readonly IObserverFactory _observerFactory; + private readonly IInsightsClient _insightsClient; + private readonly UdtMappingDefinitions _udtMappingDefinitions; - internal IInternalSession InternalRef => this; + private readonly SessionInitializer _sessionInitializer; + + private volatile string _keyspace; + + private volatile TaskCompletionSource _initTaskCompletionSource + = new TaskCompletionSource(); - public int BinaryProtocolVersion => (int)_serializerManager.GetCurrentSerializer().ProtocolVersion; + internal IInternalSession InternalRef => this; /// public ICluster Cluster => _cluster; @@ -70,7 +75,7 @@ public class Session : IInternalSession /// /// Determines if the session is already disposed /// - public bool IsDisposed => Volatile.Read(ref _disposed) > 0; + public bool IsDisposed => _sessionInitializer.IsDisposed; /// /// Gets or sets the keyspace @@ -91,10 +96,33 @@ string IInternalSession.Keyspace } /// - public UdtMappingDefinitions UserDefinedTypes { get; private set; } + public UdtMappingDefinitions UserDefinedTypes + { + get + { + if (IsDisposed) + { + throw new ObjectDisposedException("Session is disposed."); + } + + return _udtMappingDefinitions; + } + } public string SessionName { get; } + /// + public void Connect() + { + _sessionInitializer.WaitInit(); + } + + /// + public Task ConnectAsync() + { + return InternalRef.TryInitAndGetMetadataAsync(); + } + public Policies Policies => Configuration.Policies; /// @@ -104,20 +132,24 @@ internal Session( IInternalCluster cluster, Configuration configuration, string keyspace, - ISerializerManager serializerManager, string sessionName) { - _serializerManager = serializerManager; _cluster = cluster; Configuration = configuration; Keyspace = keyspace; SessionName = sessionName; - UserDefinedTypes = new UdtMappingDefinitions(this, serializerManager); _connectionPool = new CopyOnWriteDictionary(); - _cluster.HostRemoved += OnHostRemoved; - _metricsManager = new MetricsManager(configuration.MetricsProvider, Configuration.MetricsOptions, Configuration.MetricsEnabled, SessionName); + _metricsManager = new MetricsManager( + this, + configuration.MetricsProvider, + Configuration.MetricsOptions, + Configuration.MetricsEnabled, + SessionName); _observerFactory = configuration.ObserverFactoryBuilder.Build(_metricsManager); _insightsClient = configuration.InsightsClientFactory.Create(cluster, this); + _udtMappingDefinitions = new UdtMappingDefinitions(this); + _sessionInitializer = new SessionInitializer(this); + _sessionInitializer.Initialize(); } /// @@ -151,7 +183,7 @@ public void ChangeKeyspace(string keyspace) /// public void CreateKeyspace(string keyspace, Dictionary replication = null, bool durableWrites = true) { - WaitForSchemaAgreement(Execute(CqlQueryTools.GetCreateKeyspaceCql(keyspace, replication, durableWrites, false))); + Execute(CqlQueryTools.GetCreateKeyspaceCql(keyspace, replication, durableWrites, false)); Session.Logger.Info("Keyspace [" + keyspace + "] has been successfully CREATED."); } @@ -192,16 +224,15 @@ public void Dispose() { ShutdownAsync().GetAwaiter().GetResult(); } - + /// - public async Task ShutdownAsync() + public Task ShutdownAsync() { - //Only dispose once - if (Interlocked.Increment(ref _disposed) != 1) - { - return; - } + return _sessionInitializer.ShutdownAsync(); + } + async Task IInternalSession.OnShutdownAsync() + { if (_insightsClient != null) { await _insightsClient.ShutdownAsync().ConfigureAwait(false); @@ -209,7 +240,7 @@ public async Task ShutdownAsync() _metricsManager?.Dispose(); - _cluster.HostRemoved -= OnHostRemoved; + _cluster.Metadata.HostRemoved -= OnHostRemoved; var pools = _connectionPool.ToArray(); foreach (var pool in pools) @@ -217,35 +248,45 @@ public async Task ShutdownAsync() pool.Value.Dispose(); } } + + Task IInternalSession.TryInitAndGetMetadataAsync() + { + return _sessionInitializer.WaitInitAndGetMetadataAsync(); + } + + IInternalMetadata IInternalSession.TryInitAndGetMetadata() + { + return _sessionInitializer.WaitInitAndGetMetadata(); + } - /// - async Task IInternalSession.Init() + async Task IInternalSession.PostInitializeAsync() { - _metricsManager.InitializeMetrics(this); + InternalRef.InternalCluster.Metadata.HostRemoved += OnHostRemoved; - if (Configuration.GetOrCreatePoolingOptions(_serializerManager.CurrentProtocolVersion).GetWarmup()) + var serializerManager = _cluster.Configuration.SerializerManager; + if (Configuration.GetOrCreatePoolingOptions(serializerManager.CurrentProtocolVersion).GetWarmup()) { - await Warmup().ConfigureAwait(false); + await WarmupAsync(_cluster.InternalMetadata).ConfigureAwait(false); } if (Keyspace != null) { - // Borrow a connection, trying to fail fast - var handler = Configuration.RequestHandlerFactory.Create(this, _serializerManager.GetCurrentSerializer()); + // Borrow a connection, trying to fail fast if keyspace is invalid + var handler = Configuration.RequestHandlerFactory.Create(this, _cluster.InternalMetadata, serializerManager.GetCurrentSerializer()); await handler.GetNextConnectionAsync(new Dictionary()).ConfigureAwait(false); } - - _insightsClient.Init(); + + _insightsClient.Initialize(_cluster.InternalMetadata); } - + /// /// Creates the required connections on all hosts in the local DC. /// Returns a Task that is marked as completed after all pools were warmed up. /// In case, all the host pool warmup fail, it logs an error. /// - private async Task Warmup() + private async Task WarmupAsync(IInternalMetadata metadata) { - var hosts = _cluster.AllHosts().Where(h => _cluster.RetrieveAndSetDistance(h) == HostDistance.Local).ToArray(); + var hosts = metadata.AllHosts().Where(h => _cluster.RetrieveAndSetDistance(h) == HostDistance.Local).ToArray(); var tasks = new Task[hosts.Length]; for (var i = 0; i < hosts.Length; i++) { @@ -275,7 +316,11 @@ private async Task Warmup() public RowSet EndExecute(IAsyncResult ar) { var task = (Task)ar; - TaskHelper.WaitToCompleteWithMetrics(_metricsManager, task, Configuration.DefaultRequestOptions.QueryAbortTimeout); + var initTimeout = _cluster.GetInitTimeout(); + var timeout = _sessionInitializer.IsInitialized + ? Configuration.DefaultRequestOptions.QueryAbortTimeout + initTimeout.TotalMilliseconds + : initTimeout.TotalMilliseconds; + TaskHelper.WaitToCompleteWithMetrics(_metricsManager, task, (int)timeout); return task.Result; } @@ -283,14 +328,19 @@ public RowSet EndExecute(IAsyncResult ar) public PreparedStatement EndPrepare(IAsyncResult ar) { var task = (Task)ar; - TaskHelper.WaitToCompleteWithMetrics(_metricsManager, task, Configuration.DefaultRequestOptions.QueryAbortTimeout); + var initTimeout = _cluster.GetInitTimeout(); + var timeout = _sessionInitializer.IsInitialized + ? Configuration.DefaultRequestOptions.QueryAbortTimeout + initTimeout.TotalMilliseconds + : initTimeout.TotalMilliseconds; + TaskHelper.WaitToCompleteWithMetrics(_metricsManager, task, (int)timeout); return task.Result; } /// public RowSet Execute(IStatement statement, string executionProfileName) { - var task = ExecuteAsync(statement, executionProfileName); + var metadata = InternalRef.TryInitAndGetMetadata(); + var task = ExecuteInternalAsync(metadata, statement, InternalRef.GetRequestOptions(executionProfileName)); TaskHelper.WaitToCompleteWithMetrics(_metricsManager, task, Configuration.DefaultRequestOptions.QueryAbortTimeout); return task.Result; } @@ -332,15 +382,18 @@ public Task ExecuteAsync(IStatement statement) } /// - public Task ExecuteAsync(IStatement statement, string executionProfileName) + public async Task ExecuteAsync(IStatement statement, string executionProfileName) { - return ExecuteAsync(statement, InternalRef.GetRequestOptions(executionProfileName)); + var metadata = await InternalRef.TryInitAndGetMetadataAsync().ConfigureAwait(false); + return await ExecuteInternalAsync( + metadata, statement, InternalRef.GetRequestOptions(executionProfileName)).ConfigureAwait(false); } - - private Task ExecuteAsync(IStatement statement, IRequestOptions requestOptions) + + private Task ExecuteInternalAsync( + IInternalMetadata metadata, IStatement statement, IRequestOptions requestOptions) { return Configuration.RequestHandlerFactory - .Create(this, _serializerManager.GetCurrentSerializer(), statement, requestOptions) + .Create(this, metadata, metadata.SerializerManager.GetCurrentSerializer(), statement, requestOptions) .SendAsync(); } @@ -350,7 +403,7 @@ IHostConnectionPool IInternalSession.GetOrCreateConnectionPool(Host host, HostDi var hostPool = _connectionPool.GetOrAdd(host.Address, address => { var newPool = Configuration.HostConnectionPoolFactory.Create( - host, Configuration, _serializerManager, _observerFactory); + host, Configuration, _cluster.Configuration.SerializerManager, _observerFactory); newPool.AllConnectionClosed += InternalRef.OnAllConnectionClosed; newPool.SetDistance(distance); _metricsManager.GetOrCreateNodeMetrics(host).InitializePoolGauges(newPool); @@ -432,7 +485,8 @@ public PreparedStatement Prepare(string cqlQuery, string keyspace) /// public PreparedStatement Prepare(string cqlQuery, string keyspace, IDictionary customPayload) { - var task = PrepareAsync(cqlQuery, keyspace, customPayload); + _sessionInitializer.WaitInit(); + var task = _cluster.PrepareAsync(this, cqlQuery, keyspace, customPayload); TaskHelper.WaitToCompleteWithMetrics(_metricsManager, task, Configuration.ClientOptions.QueryAbortTimeout); return task.Result; } @@ -459,26 +513,8 @@ public Task PrepareAsync(string cqlQuery, string keyspace) public async Task PrepareAsync( string cqlQuery, string keyspace, IDictionary customPayload) { - var serializer = _serializerManager.GetCurrentSerializer(); - var currentVersion = serializer.ProtocolVersion; - if (!currentVersion.SupportsKeyspaceInRequest() && keyspace != null) - { - // Validate protocol version here and not at PrepareRequest level, as PrepareRequest can be issued - // in the background (prepare and retry, prepare on up, ...) - throw new NotSupportedException($"Protocol version {currentVersion} does not support" + - " setting the keyspace as part of the PREPARE request"); - } - var request = new PrepareRequest(serializer, cqlQuery, keyspace, customPayload); - return await _cluster.Prepare(this, _serializerManager, request).ConfigureAwait(false); - } - - public void WaitForSchemaAgreement(RowSet rs) - { - } - - public bool WaitForSchemaAgreement(IPEndPoint hostAddress) - { - return false; + await _sessionInitializer.WaitInitAsync().ConfigureAwait(false); + return await _cluster.PrepareAsync(this, cqlQuery, keyspace, customPayload).ConfigureAwait(false); } private IStatement GetDefaultStatement(string cqlQuery) @@ -506,7 +542,7 @@ private void OnHostRemoved(Host host) pool.Dispose(); } } - + /// public GraphResultSet ExecuteGraph(IGraphStatement statement) { @@ -522,21 +558,30 @@ public Task ExecuteGraphAsync(IGraphStatement graphStatement) /// public GraphResultSet ExecuteGraph(IGraphStatement statement, string executionProfileName) { - return TaskHelper.WaitToCompleteWithMetrics(_metricsManager, ExecuteGraphAsync(statement, executionProfileName)); + var metadata = InternalRef.TryInitAndGetMetadata(); + return TaskHelper.WaitToCompleteWithMetrics( + _metricsManager, ExecuteGraphInternalAsync(metadata, statement, executionProfileName)); } /// public async Task ExecuteGraphAsync(IGraphStatement graphStatement, string executionProfileName) + { + var metadata = await InternalRef.TryInitAndGetMetadataAsync().ConfigureAwait(false); + return await ExecuteGraphInternalAsync(metadata, graphStatement, executionProfileName).ConfigureAwait(false); + } + + private async Task ExecuteGraphInternalAsync( + IInternalMetadata metadata, IGraphStatement graphStatement, string executionProfileName) { var requestOptions = InternalRef.GetRequestOptions(executionProfileName); var stmt = graphStatement.ToIStatement(requestOptions.GraphOptions); - await GetAnalyticsPrimary(stmt, graphStatement, requestOptions).ConfigureAwait(false); - var rs = await ExecuteAsync(stmt, requestOptions).ConfigureAwait(false); + await GetAnalyticsPrimary(metadata, stmt, graphStatement, requestOptions).ConfigureAwait(false); + var rs = await ExecuteInternalAsync(metadata, stmt, requestOptions).ConfigureAwait(false); return GraphResultSet.CreateNew(rs, graphStatement, requestOptions.GraphOptions); } private async Task GetAnalyticsPrimary( - IStatement statement, IGraphStatement graphStatement, IRequestOptions requestOptions) + IInternalMetadata internalMetadata, IStatement statement, IGraphStatement graphStatement, IRequestOptions requestOptions) { if (!(statement is TargettedSimpleStatement) || !requestOptions.GraphOptions.IsAnalyticsQuery(graphStatement)) { @@ -548,8 +593,8 @@ private async Task GetAnalyticsPrimary( RowSet rs; try { - rs = await ExecuteAsync( - new SimpleStatement("CALL DseClientTool.getAnalyticsGraphServer()"), requestOptions).ConfigureAwait(false); + rs = await ExecuteInternalAsync( + internalMetadata, new SimpleStatement("CALL DseClientTool.getAnalyticsGraphServer()"), requestOptions).ConfigureAwait(false); } catch (Exception ex) { @@ -557,10 +602,10 @@ private async Task GetAnalyticsPrimary( return statement; } - return AdaptRpcPrimaryResult(rs, targetedSimpleStatement); + return AdaptRpcPrimaryResult(internalMetadata, rs, targetedSimpleStatement); } - private IStatement AdaptRpcPrimaryResult(RowSet rowSet, TargettedSimpleStatement statement) + private IStatement AdaptRpcPrimaryResult(IInternalMetadata internalMetadata, RowSet rowSet, TargettedSimpleStatement statement) { var row = rowSet.FirstOrDefault(); if (row == null) @@ -578,7 +623,7 @@ private IStatement AdaptRpcPrimaryResult(RowSet rowSet, TargettedSimpleStatement var hostName = location.Substring(0, location.LastIndexOf(':')); var address = Configuration.AddressTranslator.Translate( new IPEndPoint(IPAddress.Parse(hostName), Configuration.ProtocolOptions.Port)); - var host = Cluster.GetHost(address); + var host = internalMetadata.GetHost(address); statement.PreferredHost = host; return statement; } diff --git a/src/Cassandra/SessionManagement/IInternalCluster.cs b/src/Cassandra/SessionManagement/IInternalCluster.cs index d90f02dd5..6b46a7f25 100644 --- a/src/Cassandra/SessionManagement/IInternalCluster.cs +++ b/src/Cassandra/SessionManagement/IInternalCluster.cs @@ -14,25 +14,25 @@ // limitations under the License. // +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; + using Cassandra.Connections; using Cassandra.Connections.Control; -using Cassandra.Requests; -using Cassandra.Serialization; +using Cassandra.Helpers; namespace Cassandra.SessionManagement { /// internal interface IInternalCluster : ICluster { - bool AnyOpenConnections(Host host); + IInternalMetadata InternalMetadata { get; } - /// - /// Gets the control connection used by the cluster - /// - IControlConnection GetControlConnection(); + IClusterInitializer ClusterInitializer { get; } + + bool AnyOpenConnections(Host host); /// /// If contact points are not provided in the builder, the driver will use localhost @@ -44,20 +44,30 @@ internal interface IInternalCluster : ICluster /// Gets the the prepared statements cache /// ConcurrentDictionary PreparedQueries { get; } - + /// /// Executes the prepare request on the first host selected by the load balancing policy. /// When is enabled, it prepares on the rest of the hosts in /// parallel. /// In case the statement was already in the prepared statements cache, logs an warning but prepares it anyway. /// - Task Prepare(IInternalSession session, ISerializerManager serializerManager, PrepareRequest request); - + Task PrepareAsync(IInternalSession session, string cqlQuery, string keyspace, IDictionary customPayload); + IReadOnlyDictionary> GetResolvedEndpoints(); /// /// Helper method to retrieve the aggregate distance from all configured LoadBalancingPolicies and set it at Host level. /// HostDistance RetrieveAndSetDistance(Host host); + + TimeSpan GetInitTimeout(); + + Task PostInitializeAsync(); + + Task PreShutdownAsync(int timeoutMs); + + Task PostShutdownAsync(); + + Task OnSessionShutdownAsync(IInternalSession session); } } \ No newline at end of file diff --git a/src/Cassandra/SessionManagement/IInternalSession.cs b/src/Cassandra/SessionManagement/IInternalSession.cs index c8b9e236f..cf1c3d935 100644 --- a/src/Cassandra/SessionManagement/IInternalSession.cs +++ b/src/Cassandra/SessionManagement/IInternalSession.cs @@ -20,6 +20,7 @@ using System.Threading.Tasks; using Cassandra.Connections; +using Cassandra.Connections.Control; using Cassandra.ExecutionProfiles; using Cassandra.Metrics.Internal; using Cassandra.Observers.Abstractions; @@ -36,10 +37,13 @@ internal interface IInternalSession : ISession /// Guid InternalSessionId { get; } - /// - /// Initialize the session - /// - Task Init(); + Task PostInitializeAsync(); + + Task OnShutdownAsync(); + + IInternalMetadata TryInitAndGetMetadata(); + + Task TryInitAndGetMetadataAsync(); /// /// Gets or creates the connection pool for a given host diff --git a/src/Cassandra/SessionManagement/ISessionFactory.cs b/src/Cassandra/SessionManagement/ISessionFactory.cs index 8bcab0372..ef918fd51 100644 --- a/src/Cassandra/SessionManagement/ISessionFactory.cs +++ b/src/Cassandra/SessionManagement/ISessionFactory.cs @@ -14,14 +14,11 @@ // limitations under the License. // -using System.Threading.Tasks; -using Cassandra.Serialization; - namespace Cassandra.SessionManagement { internal interface ISessionFactory { - Task CreateSessionAsync( - IInternalCluster cluster, string keyspace, ISerializerManager serializer, string sessionName); + IInternalSession CreateSession( + IInternalCluster cluster, string keyspace, string sessionName); } } \ No newline at end of file diff --git a/src/Cassandra/SessionManagement/SessionFactory.cs b/src/Cassandra/SessionManagement/SessionFactory.cs index f27526762..4f7878fbe 100644 --- a/src/Cassandra/SessionManagement/SessionFactory.cs +++ b/src/Cassandra/SessionManagement/SessionFactory.cs @@ -14,18 +14,14 @@ // limitations under the License. // -using System.Threading.Tasks; -using Cassandra.Serialization; - namespace Cassandra.SessionManagement { internal class SessionFactory : ISessionFactory { - public Task CreateSessionAsync( - IInternalCluster cluster, string keyspace, ISerializerManager serializer, string sessionName) + public IInternalSession CreateSession( + IInternalCluster cluster, string keyspace, string sessionName) { - return Task.FromResult( - new Session(cluster, cluster.Configuration, keyspace, serializer, sessionName).InternalRef); + return new Session(cluster, cluster.Configuration, keyspace, sessionName).InternalRef; } } } \ No newline at end of file diff --git a/src/Cassandra/SessionState.cs b/src/Cassandra/SessionState.cs index d3cb4faa3..6da030447 100644 --- a/src/Cassandra/SessionState.cs +++ b/src/Cassandra/SessionState.cs @@ -18,6 +18,7 @@ using System.Text; using Cassandra.Collections; using Cassandra.Connections; +using Cassandra.Connections.Control; using Cassandra.SessionManagement; namespace Cassandra @@ -62,13 +63,13 @@ public override string ToString() return builder.ToString(); } - internal static SessionState From(IInternalSession session) + internal static SessionState From(IInternalSession session, IInternalMetadata internalMetadata) { var pools = session.GetPools(); var result = new Dictionary(); foreach (var kv in pools) { - var host = session.Cluster.GetHost(kv.Key); + var host = internalMetadata.GetHost(kv.Key); if (host == null) { continue; diff --git a/src/Cassandra/SocketOptions.cs b/src/Cassandra/SocketOptions.cs index dbce0f7fb..a78f9076b 100644 --- a/src/Cassandra/SocketOptions.cs +++ b/src/Cassandra/SocketOptions.cs @@ -44,7 +44,7 @@ public class SocketOptions private bool _useStreamMode; private int _readTimeoutMillis = DefaultReadTimeoutMillis; private int _defunctReadTimeoutThreshold = DefaultDefunctReadTimeoutThreshold; - private int _metadataAbortTimeout = 5 * 60000; + private int _metadataAbortTimeoutMs = 5 * 60000; /// /// Gets the number of milliseconds to wait for the socket to connect @@ -122,7 +122,7 @@ public int ReadTimeoutMillis get { return _readTimeoutMillis; } } - internal int MetadataAbortTimeout => _metadataAbortTimeout; + internal int MetadataAbortTimeoutMs => _metadataAbortTimeoutMs; /// /// Gets the amount of requests that simultaneously have to timeout before closing the connection. @@ -227,7 +227,7 @@ public SocketOptions SetDefunctReadTimeoutThreshold(int amountOfTimeouts) internal SocketOptions SetMetadataAbortTimeout(int metadataAbortTimeout) { - _metadataAbortTimeout = metadataAbortTimeout; + _metadataAbortTimeoutMs = metadataAbortTimeout; return this; } } diff --git a/src/Cassandra/Tasks/TaskHelper.cs b/src/Cassandra/Tasks/TaskHelper.cs index 72b72b593..1188b9cc9 100644 --- a/src/Cassandra/Tasks/TaskHelper.cs +++ b/src/Cassandra/Tasks/TaskHelper.cs @@ -171,13 +171,13 @@ public static void WaitToComplete(Task task, int timeout = Timeout.Infinite) /// It throws a TimeoutException when the task didn't complete in the expected time. /// /// the task to wait upon - /// timeout in milliseconds + /// timeout in milliseconds /// /// - public static async Task WaitToCompleteAsync(this Task task, int timeout = Timeout.Infinite) + public static async Task WaitToCompleteAsync(this Task task, int timeoutMs = Timeout.Infinite) { //It should wait and throw any exception - if (timeout == Timeout.Infinite) + if (timeoutMs == Timeout.Infinite) { await task.ConfigureAwait(false); return; @@ -185,7 +185,7 @@ public static async Task WaitToCompleteAsync(this Task task, int timeout = Timeo try { - var timeoutTask = Task.Delay(TimeSpan.FromMilliseconds(timeout)); + var timeoutTask = Task.Delay(TimeSpan.FromMilliseconds(timeoutMs)); var finishedTask = await Task.WhenAny(task, timeoutTask).ConfigureAwait(false); if (finishedTask == timeoutTask) { @@ -467,20 +467,7 @@ public static async Task WithCancellation( return await task.ConfigureAwait(false); } - - /// - /// Simple helper to create a CancellationToken that is cancelled after - /// the provided . - /// - /// Timespan after which the returned - /// is canceled. - /// A newly created that will be canceled - /// after the specified . - public static CancellationToken CancelTokenAfterDelay(TimeSpan timespan) - { - return new CancellationTokenSource(timespan).Token; - } - + /// /// Calls Task.Delay with a cancellation token. /// diff --git a/src/Cassandra/UdtMappingDefinitions.cs b/src/Cassandra/UdtMappingDefinitions.cs index 96f04c9df..cc3a5469d 100644 --- a/src/Cassandra/UdtMappingDefinitions.cs +++ b/src/Cassandra/UdtMappingDefinitions.cs @@ -18,7 +18,7 @@ using System.Collections.Concurrent; using System.Linq; using System.Threading.Tasks; -using Cassandra.Serialization; +using Cassandra.Connections.Control; using Cassandra.SessionManagement; using Cassandra.Tasks; @@ -32,14 +32,12 @@ public class UdtMappingDefinitions private readonly ConcurrentDictionary _udtByNetType; private readonly IInternalCluster _cluster; private readonly IInternalSession _session; - private readonly ISerializerManager _serializer; - internal UdtMappingDefinitions(IInternalSession session, ISerializerManager serializer) + internal UdtMappingDefinitions(IInternalSession session) { _udtByNetType = new ConcurrentDictionary(); _cluster = session.InternalCluster; _session = session; - _serializer = serializer; } /// @@ -48,7 +46,14 @@ internal UdtMappingDefinitions(IInternalSession session, ISerializerManager seri /// public void Define(params UdtMap[] udtMaps) { - TaskHelper.WaitToComplete(DefineAsync(udtMaps), _cluster.Configuration.DefaultRequestOptions.QueryAbortTimeout); + if (udtMaps == null) + { + throw new ArgumentNullException("udtMaps"); + } + + var metadata = _session.TryInitAndGetMetadata(); + TaskHelper.WaitToComplete( + DefineInternalAsync(metadata, udtMaps), _cluster.Configuration.DefaultRequestOptions.QueryAbortTimeout); } /// @@ -61,6 +66,12 @@ public async Task DefineAsync(params UdtMap[] udtMaps) { throw new ArgumentNullException("udtMaps"); } + var metadata = await _session.TryInitAndGetMetadataAsync().ConfigureAwait(false); + await DefineInternalAsync(metadata, udtMaps).ConfigureAwait(false); + } + + private async Task DefineInternalAsync(IInternalMetadata metadata, UdtMap[] udtMaps) + { var sessionKeyspace = _session.Keyspace; if (string.IsNullOrEmpty(sessionKeyspace) && udtMaps.Any(map => map.Keyspace == null)) { @@ -68,17 +79,17 @@ public async Task DefineAsync(params UdtMap[] udtMaps) "You can specify it while creating the UdtMap, while creating the Session and" + " while creating the Cluster (default keyspace config setting)."); } - if (_session.BinaryProtocolVersion < 3) + if (!metadata.ProtocolVersion.SupportsUserDefinedTypes()) { throw new NotSupportedException("User defined type mapping is supported with C* 2.1+ and protocol version 3+"); } // Add types to both indexes foreach (var map in udtMaps) { - var udtDefition = await GetDefinitionAsync(map.Keyspace ?? sessionKeyspace, map).ConfigureAwait(false); - map.SetSerializer(_serializer.GetCurrentSerializer()); + var udtDefition = await GetDefinitionAsync(map.Keyspace ?? sessionKeyspace, map, metadata).ConfigureAwait(false); + map.SetSerializer(metadata.SerializerManager.GetCurrentSerializer()); map.Build(udtDefition); - _serializer.SetUdtMap(udtDefition.Name, map); + metadata.SerializerManager.SetUdtMap(udtDefition.Name, map); _udtByNetType.AddOrUpdate(map.NetType, map, (k, oldValue) => map); } } @@ -87,7 +98,7 @@ public async Task DefineAsync(params UdtMap[] udtMaps) /// Gets the definition and validates the fields /// /// - private async Task GetDefinitionAsync(string keyspace, UdtMap map) + private async Task GetDefinitionAsync(string keyspace, UdtMap map, IInternalMetadata metadata) { var caseSensitiveUdtName = map.UdtName; if (map.IgnoreCase) @@ -95,7 +106,8 @@ private async Task GetDefinitionAsync(string keyspace, UdtMap map //identifiers are lower cased in Cassandra caseSensitiveUdtName = caseSensitiveUdtName.ToLowerInvariant(); } - var udtDefinition = await _cluster.Metadata.GetUdtDefinitionAsync(keyspace, caseSensitiveUdtName).ConfigureAwait(false); + + var udtDefinition = await metadata.GetUdtDefinitionAsync(keyspace, caseSensitiveUdtName).ConfigureAwait(false); if (udtDefinition == null) { throw new InvalidTypeException($"{caseSensitiveUdtName} UDT not found on keyspace {keyspace}"); @@ -113,4 +125,4 @@ internal UdtMap GetUdtMap(string keyspace, Type netType) return _udtByNetType.TryGetValue(netType, out UdtMap map) ? map : null; } } -} +} \ No newline at end of file