Skip to content

Commit

Permalink
Added Couchbase.Extensions.DnsDiscovery package for DNS SRV discovery (
Browse files Browse the repository at this point in the history
  • Loading branch information
brantburnett authored Jan 8, 2017
1 parent 9887e81 commit 65da886
Show file tree
Hide file tree
Showing 26 changed files with 901 additions and 26 deletions.
14 changes: 14 additions & 0 deletions Couchbase.Extensions.sln
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Couchbase.Extensions.Depend
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "TestApp", "tests\TestApp\TestApp.xproj", "{4C92B1B9-CE3E-458D-8CBA-359A473E1DBF}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Couchbase.Extensions.DnsDiscovery", "src\Couchbase.Extensions.DnsDiscovery\Couchbase.Extensions.DnsDiscovery.xproj", "{71A8FC7A-A848-4414-BFAE-86E8A08E59A0}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Couchbase.Extensions.DnsDiscovery.UnitTests", "tests\Couchbase.Extensions.DnsDiscovery.UnitTests\Couchbase.Extensions.DnsDiscovery.UnitTests.xproj", "{B743B8D9-1F91-4160-BFED-BFD23EA79CE6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -42,6 +46,14 @@ Global
{4C92B1B9-CE3E-458D-8CBA-359A473E1DBF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4C92B1B9-CE3E-458D-8CBA-359A473E1DBF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4C92B1B9-CE3E-458D-8CBA-359A473E1DBF}.Release|Any CPU.Build.0 = Release|Any CPU
{71A8FC7A-A848-4414-BFAE-86E8A08E59A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{71A8FC7A-A848-4414-BFAE-86E8A08E59A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{71A8FC7A-A848-4414-BFAE-86E8A08E59A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{71A8FC7A-A848-4414-BFAE-86E8A08E59A0}.Release|Any CPU.Build.0 = Release|Any CPU
{B743B8D9-1F91-4160-BFED-BFD23EA79CE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B743B8D9-1F91-4160-BFED-BFD23EA79CE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B743B8D9-1F91-4160-BFED-BFD23EA79CE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B743B8D9-1F91-4160-BFED-BFD23EA79CE6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -51,5 +63,7 @@ Global
{DD43EE36-EE8A-42E6-B837-AD5F32FE7F6F} = {727F95F9-8FD9-423C-ACC6-B23E0D6CCC4C}
{BACE8433-B111-44E7-A8BE-ECA7A48CAC6A} = {727F95F9-8FD9-423C-ACC6-B23E0D6CCC4C}
{4C92B1B9-CE3E-458D-8CBA-359A473E1DBF} = {727F95F9-8FD9-423C-ACC6-B23E0D6CCC4C}
{71A8FC7A-A848-4414-BFAE-86E8A08E59A0} = {EC47DFB9-AB39-41F8-BD7C-7B25BBCDEBD1}
{B743B8D9-1F91-4160-BFED-BFD23EA79CE6} = {727F95F9-8FD9-423C-ACC6-B23E0D6CCC4C}
EndGlobalSection
EndGlobal
2 changes: 1 addition & 1 deletion Couchbase.Extensions.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/FilterSettingsManager/CoverageFilterXml/@EntryValue">&lt;data&gt;&lt;IncludeFilters /&gt;&lt;ExcludeFilters&gt;&lt;Filter ModuleMask="Couchbase.Extensions.DependencyInjection.*Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /&gt;&lt;Filter ModuleMask="Couchbase.Extensions.DependencyInjection" ModuleVersionMask="*" ClassMask="Couchbase.Extensions.DependencyInjection.Internal.ClusterProvider" FunctionMask="CreateCluster" IsEnabled="True" /&gt;&lt;Filter ModuleMask="TestApp" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /&gt;&lt;/ExcludeFilters&gt;&lt;/data&gt;</s:String>
<s:String x:Key="/Default/FilterSettingsManager/CoverageFilterXml/@EntryValue">&lt;data&gt;&lt;IncludeFilters /&gt;&lt;ExcludeFilters&gt;&lt;Filter ModuleMask="*Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /&gt;&lt;Filter ModuleMask="Couchbase.Extensions.DependencyInjection" ModuleVersionMask="*" ClassMask="Couchbase.Extensions.DependencyInjection.Internal.ClusterProvider" FunctionMask="CreateCluster" IsEnabled="True" /&gt;&lt;Filter ModuleMask="TestApp" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /&gt;&lt;Filter ModuleMask="Couchbase.Extensions.DnsDiscovery" ModuleVersionMask="*" ClassMask="Couchbase.Extensions.DnsDiscovery.Internal.LookupClientAdapter" FunctionMask="*" IsEnabled="True" /&gt;&lt;/ExcludeFilters&gt;&lt;/data&gt;</s:String>
<s:String x:Key="/Default/FilterSettingsManager/AttributeFilterXml/@EntryValue">&lt;data&gt;&lt;AttributeFilter ClassMask="Couchbase.Extensions.DependencyInjection.Internal.ExcludeFromCodeCoverageAttribute" IsEnabled="True" /&gt;&lt;/data&gt;</s:String></wpf:ResourceDictionary>
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,51 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF
}
```

## Couchbase.Extensions.DnsDiscovery

A .Net Core compatible DNS SRV mechanism for discovering a Couchbase cluster dynamically.

### Configuration

To use, call `AddCouchbaseDnsDiscovery` during the service registration process, usually in your `Startup` class. You may choose whether or not to add this step based on the environment. Be sure to add the basic Couchbase configuration first.

```csharp
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();

Configuration = builder.Build();
Environment = env;
}

public IConfigurationRoot Configuration { get; }
public IHostingEnvironment Environment { get; }

public void ConfigureServices(IServiceCollection services)
{
// Register Couchbase with configuration section
services.AddCouchbase(Configuration.GetSection("Couchbase"));

if (Environment.IsProduction())
{
// Lookup DNS SRV records using the query _couchbase._tcp.services.local
services.AddCouchbaseDnsDiscovery("_couchbase._tcp.services.local");

// Note: the record name above could also be retreived from configuration
}

// Register other services, like .AddMvc()
}
```

The above configuration will perform a DNS SRV query (only in the Production environment). The results will be used to replace the Servers list in the Couchbase client configuration. Any servers listed in the configuration section will be thrown out.

Note that only the servers with the highest priority in the DNS SRV response will be used. For example, if the response returns 2 servers with a priority of 10 and 2 different servers with a priority of 20, the first set will be used to initialize the cluster. SRV record failover is not supported.

Additionally, due to the nature of Couchbase clusters, the weight fields in the DNS SRV records are ignored. However, the port is used and should normally be set to 8091.
17 changes: 15 additions & 2 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
version: '{build}'
branches:
only:
- master
configuration: Release
environment:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
Expand All @@ -12,16 +15,26 @@ build:
verbosity: minimal
after_build:
- cmd: dotnet pack --configuration %CONFIGURATION% "src/Couchbase.Extensions.DependencyInjection"
- cmd: dotnet pack --configuration %CONFIGURATION% "src/Couchbase.Extensions.DnsDiscovery"
test_script:
- cmd: dotnet test "tests\Couchbase.Extensions.DependencyInjection.UnitTests"
- cmd: dotnet test "tests\Couchbase.Extensions.DnsDiscovery.UnitTests"
artifacts:
- path: src/Couchbase.Extensions.DependencyInjection/bin/%CONFIGURATION%/*.nupkg
name: NuGet
name: DependencyInjection
- path: src/Couchbase.Extensions.DnsDiscovery/bin/%CONFIGURATION%/*.nupkg
name: DnsDiscovery
deploy:
- provider: NuGet
api_key:
secure: KzT1ESVyB5LB0Ovg+dPUmvZYQJ0XYoBEe9DW1pBDKzv0y/7y2RlJ1xt16ZI5dnVE
artifact: NuGet
artifact: DependencyInjection
on:
APPVEYOR_REPO_TAG: true
- provider: NuGet
api_key:
secure: KzT1ESVyB5LB0Ovg+dPUmvZYQJ0XYoBEe9DW1pBDKzv0y/7y2RlJ1xt16ZI5dnVE
artifact: DnsDiscovery
on:
APPVEYOR_REPO_TAG: true
notifications:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>

<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>71a8fc7a-a848-4414-bfae-86e8a08e59a0</ProjectGuid>
<RootNamespace>Couchbase.Extensions.DnsDiscovery</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
</PropertyGroup>

<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>
9 changes: 9 additions & 0 deletions src/Couchbase.Extensions.DnsDiscovery/ICouchbaseDnsLookup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Couchbase.Configuration.Client;

namespace Couchbase.Extensions.DnsDiscovery
{
public interface ICouchbaseDnsLookup
{
void Apply(CouchbaseClientDefinition clientDefinition, string recordName);
}
}
109 changes: 109 additions & 0 deletions src/Couchbase.Extensions.DnsDiscovery/Internal/CouchbaseDnsLookup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Couchbase.Configuration.Client;
using DnsClient;
using DnsClient.Protocol;
using Microsoft.Extensions.Logging;

namespace Couchbase.Extensions.DnsDiscovery.Internal
{
internal class CouchbaseDnsLookup : ICouchbaseDnsLookup
{
private readonly ILookupClientAdapter _lookupClient;
private readonly ILogger<CouchbaseDnsLookup> _logger;

public CouchbaseDnsLookup(ILookupClientAdapter lookupClient, ILogger<CouchbaseDnsLookup> logger)
{
if (lookupClient == null)
{
throw new ArgumentNullException(nameof(lookupClient));
}
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}

_lookupClient = lookupClient;
_logger = logger;
}

public void Apply(CouchbaseClientDefinition clientDefinition, string recordName)
{
if (clientDefinition == null)
{
throw new ArgumentNullException(nameof(clientDefinition));
}
if (recordName == null)
{
throw new ArgumentNullException(nameof(recordName));
}

try
{
// Ensure an empty collection of servers before resolving
if (clientDefinition.Servers == null)
{
clientDefinition.Servers = new List<Uri>();
}
else
{
clientDefinition.Servers.Clear();
}

_logger.LogInformation("Looking up Couchbase servers using record '{0}'", recordName);

List<SrvRecord> servers;
var syncContextCache = SynchronizationContext.Current;
try
{
// Ensure that we're outside any sync context before waiting on an async result to prevent deadlocks
SynchronizationContext.SetSynchronizationContext(null);

servers = _lookupClient
.QuerySrvAsync(recordName).Result
.OrderBy(p => p.Priority)
.ToList();
}
finally
{
if (syncContextCache != null)
{
SynchronizationContext.SetSynchronizationContext(syncContextCache);
}
}

if (!servers.Any())
{
_logger.LogError("No SRV records returned for query '{0}'", recordName);
return;
}

var firstPriority = servers.First().Priority;
foreach (var server in servers.Where(p => p.Priority == firstPriority))
{
var uri = new Uri($"http://{FormatTargetDns(server.Target)}:{server.Port}/pools");

_logger.LogInformation("Got Couchbase server '{0}'", uri);

clientDefinition.Servers.Add(uri);
}
}
catch (Exception ex)
{
_logger.LogError(0, ex, "Exception getting Couchbase servers for '{0}'", recordName);
}
}

private string FormatTargetDns(string target)
{
if (target.EndsWith("."))
{
return target.Substring(0, target.Length - 1);
}

return target;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DnsClient.Protocol;

namespace Couchbase.Extensions.DnsDiscovery.Internal
{
/// <summary>
/// Mockable adapter for DnsClient.LookupClient
/// </summary>
internal interface ILookupClientAdapter
{
Task<IEnumerable<SrvRecord>> QuerySrvAsync(string query);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DnsClient;
using DnsClient.Protocol;
using Microsoft.Extensions.Logging;

namespace Couchbase.Extensions.DnsDiscovery.Internal
{
/// <summary>
/// Mockable adapter for DnsClient.LookupClient
/// </summary>
internal class LookupClientAdapter : ILookupClientAdapter
{
private readonly ILogger<LookupClientAdapter> _logger;
private readonly LookupClient _lookupClient = new LookupClient();

public LookupClientAdapter(ILogger<LookupClientAdapter> logger)
{
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}

_logger = logger;
}

public async Task<IEnumerable<SrvRecord>> QuerySrvAsync(string query)
{
var response = await _lookupClient.QueryAsync(query, QueryType.SRV);

if (response.HasError)
{
_logger.LogError("DNS query error '{0}'", response.ErrorMessage);

return new SrvRecord[] {};
}

return response.Answers.SrvRecords();
}
}
}
22 changes: 22 additions & 0 deletions src/Couchbase.Extensions.DnsDiscovery/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Couchbase.Extensions.DnsDiscovery")]
[assembly: AssemblyTrademark("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("71a8fc7a-a848-4414-bfae-86e8a08e59a0")]

[assembly: InternalsVisibleTo("Couchbase.Extensions.DnsDiscovery.UnitTests")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
Loading

0 comments on commit 65da886

Please sign in to comment.