Whats the best / recommended Aproach for Couchbase .NET SDK

I am working on a .NET Webservice which will fetch some Config, do userAuthentication and log all requests and Responses to Couchbase. The question is what is the best Aproach to create the bucket ?
Should i create a Connection and Bucket for Each request or is it recommended to create Connection at Startup and Bucket and then reuse this one over and over. If so what would be the recommended way to check if the bucket is still open to accept new calls like upsert etc before issuing the command So i can create a new one if no longer valid.
If a Connection per Request is the way to go how do i clean up the Bucket and Connection to not end up with 1000 th of dead connection objects ?

@aponnath

The recommend approach is a single Cluster object shared throughout your application lifetime. It is self-repairing, but does have some diagnostic functions to check status.

The ClusterHelper object is a useful tool for managing this singleton.

Brant

You say Cluster object, how about the bucket ? Do i close and dispose after each transaction ?

Do you know if there is a good sample somewhere avail ?

The Cluster object manages the Bucket objects for you, just keep calling OpenBucket, and don’t dispose the buckets.

There are definitely some examples out there, are you on .NET Framework or .NET Core?

.NET Framework.
I would be a bit concerned about calling openBucket over and over as i did this some time ago in NodeJs and run into a hell of issues as i was hovering around 50K connections and it became slow and died. After cleaning up my connection and buckets i never got above 50 open ones

Here’s an example for .NET Framework: https://docs.couchbase.com/dotnet-sdk/current/sample-app-backend.html

This example uses ClusterHelper.GetBucket, which will only open the bucket once and returns it from a dictionary for every subsequent request. However, the same is true for Cluster.OpenBucket. It’s perfectly safe to call repeatedly, it’s somewhat poorly named. It doesn’t actually open a bucket unless it hasn’t been opened before, otherwise it returns a singleton.

Ok, i tried the code but have little to no luck getting past the authentication. I was able based on the sample code to move it to vb.net for my project and i am able to get the Cluster Initialized. But when i make the actual call to get a doc from couchbase
Here is how i call it

return CouchbaseStorageHelper.Instance.Get("uriEndPoint::C985544D-2A31-44A0-8228-3318A56DB8E9", "SOAPAPI");

Here is the Error. I know the User and password is correct

Couchbase.Configuration.Server.Serialization.BootstrapException
HResult=0x80131500
Message=Could not bootstrap - check inner exceptions for details.
Source=Couchbase.NetClient
StackTrace:
at Couchbase.Core.ClusterController.CreateBucketImpl(String bucketName, String password, IAuthenticator authenticator)
at Couchbase.Core.ClusterController.CreateBucket(String bucketName, String password, IAuthenticator authenticator)
at Couchbase.Cluster.OpenBucket(String bucketName, String password)
at Couchbase.ClusterHelper.GetBucket(String bucketName, String password)
at Couchbase.ClusterHelper.GetBucket(String bucketName)
at CouchTest.Storage.Couchbase.CouchbaseStorageHelper.Get(String id, String bucket) in C:\Users\Alex Ponnath\source\repos\CouchTest\CouchTest\Storage\Couchbase\CouchbaseStorageHelper.cs:line 83
at CouchTest.Controllers.TestController.Get() in C:\Users\Alex Ponnath\source\repos\CouchTest\CouchTest\Controllers\TestController.cs:line 17
at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass6_1.b__3(Object instance, Object methodParameters)
at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.Execute(Object instance, Object arguments)
at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken)

Inner Exception 1:
AuthenticationException: Authentication failed for bucket ‘SOAPAPI’

First i thought i did maybe have a problem with my C# to VB.net translation but i get the same when running the C# code. Could this be due to a mismatch or compatibility between code and Server ? I am using 6.5 Server and the 2.7.16 version of the .NET SDK.

@aponnath this doesn’t seem to be any issue with compatibility, according to the compatibility matrix looks like the server version you are using is compatible with the SDK.

Did you get a chance to look at the example code @btburnett3 was referring to ? the reason I am asking is that “CouchbaseStorageHelper” is not available in that project so I am wondering if you have any kind of special implementation of ClusterHelper ?

The documentation does advise and instructs to minimize the use of cluster connections and the sample code shows how to authenticate to a cluster using ClusterHelper class. This would hold a connection to the server (Singleton) and would manage it.

The App_Start and App_End in global.asax handles the opening and disposing of the cluster.

Can you share the snippet of your code that basically authenticates to the cluster ? Does the same UI and password work with CB UI ?

Not sure what you are referring to as far as the CouchbaseStorageHelper not part of project. Below is the class in question with is part of the GITHUB Repo

And yes i use the CouchbaseConfig to setup the Cluster Helper but this does not create the initial connection, that happens only when you make the first call to the cluster instance. And that’s where i get the rejection do to authentication failure. If i go and do this via my code without clusterhelper the credentials work just fine

Ok after some more playing around i think i found the source of the issue. In the current version Couchbase 6.x uses a username and password authentication. At one time there was only a basket password. When debugging the code i found that the code sets the Username = bucketname. When i go and create a user with the bucketname the authentication passes.
The code i am referring to is in the ClusterController.cs starting with line 210

private KeyValuePair<string, string> ResolveCredentials(string bucketName, string password, IAuthenticator authenticator = null)
    {
        var username = bucketName;
        if (authenticator == null)
        {
            //try to find a password in configuration
            BucketConfiguration bucketConfig;
            if (_clientConfig.BucketConfigs.TryGetValue(bucketName, out bucketConfig)
                && bucketConfig.Password != null)
            {
                bucketName = bucketConfig.BucketName;
                password = bucketConfig.Password;
            }
        }
        else
        {
            if (authenticator is CertAuthenticator)
            {
                return new KeyValuePair<string, string>(username ?? bucketName, null);
            }
            var bucketCredentials = authenticator.GetCredentials(AuthContext.BucketKv, bucketName);
            switch (authenticator.AuthenticatorType)
            {
                case AuthenticatorType.Classic:
                    if (bucketCredentials.ContainsKey(bucketName))
                    {
                        username = bucketName;
                        password = bucketCredentials.First().Value;
                    }
                    else
                    {
                        throw new BucketNotFoundException(string.Format("Could not find credentials for bucket: {0}", bucketName));
                    }
                    break;
                case AuthenticatorType.Password:
                    username = bucketCredentials.First().Key;
                    password = bucketCredentials.First().Value;
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        return new KeyValuePair<string, string>(username ?? bucketName, password);
    }

i observed that the username was never set 

So how does one overcome this issue other then going and creating a user with the same name as bucket ?

@aponnath I was about to reply back.

  1. The reason I was not seeing “CouchbaseStorageHelper” class is because cloning the repo is not bringing that down on my local. I do see this now in the remote repository under storage/couchbase folder. Not sure where its used in the project.

  2. I am running a CB 6.5 EE locally , I pulled down the repo and called the API directly and it works.
    You will have to authenticate to the Cluster passing in username and password. Just like the code does in the Register method in APP_START (yes that gets called the first time with the very first request). If you step through the register code, it has ensure index which basically attempts to get the bucket does that work for you ? (Sanity check).

I am not sure why username should be equal to the bucket name. I dont have any setup like that.
I have username = “Administrator”
Password = “password” (default that comes with any CB installation)
Bucket Name = travel-sample

@aponnath -

Couchbase Server has used Role Based Access Control (RBAC) since version 5.0; previously authentication was done with a “bucket name/password” combination. RBAC requires you to use a “role name/password” combination.

var cluster = new Cluster(new ClientConfiguration
{
     Servers = new List<Uri> { new Uri("http://10.112.170.101") }
});

var authenticator = new PasswordAuthenticator("username", "password");
cluster.Authenticate(authenticator);
var bucket = cluster.OpenBucket("bucketname");

More details here under the “Creating Cluster and Bucket” section: Start Using the .NET SDK | Couchbase Docs

-Jeff

1 Like

Thanks that’s how i use it currently without the ClusterHelper, but how you use this with the ClusterHelper as one specifys the username password like

        Dim config = New ClientConfiguration()
        config.BucketConfigs.Clear()
        config.Servers = New List(Of Uri)(New Uri() {New Uri(serverUri)})
        config.BucketConfigs.Add("SOAPAPI", New BucketConfiguration With {
            .BucketName = "SOAPAPI",
            .Username = user,
            .Password = password
                                                                                        })
        ClusterHelper.Initialize(config)

        Dim bucket = ClusterHelper.GetBucket("SOAPAPI")

        Dim myData = bucket.GetDocument(Of Object)("uriEndPoint::C985544D-2A31-44A0-8228-3318A56DB8E9")
        Debug.WriteLine(myData)

So where would i tell the ClusterHelper to use RBAC Authentication when i try to open the bucket ?

    Dim config = New ClientConfiguration()
    config.BucketConfigs.Clear() // optional
    config.Servers = New List(Of Uri)(New Uri() {New Uri(serverUri)})
    Dim credentials = new PasswordAuthenticator("Administrator", "password");
    //ClusterHelper.Initialize(config)
    ClusterHelper.Initialize(config, credentials);
     Dim bucket = ClusterHelper.GetBucket("SOAPAPI");

Please see if this helps!!!

1 Like

@aponnath -

Two ways:
1 - ClusterHelper has an overload that takes an IAuthenticator you can replace the PasswordAuthenticator in my example as the second parameter.

or

2 - Set the ClientConfiguration.Password and ClientConfiguration.Username fields with your RBAC role and password.

Additionally, remove the config.BucketConfigs.Add line, its for backwards compatibility with non-RBAC clusters and implies bucket authentication, which you cannot do with RBAC servers.

-Jeff

1 Like

But isn’t it the point of the BucketConfig to add possibly multiple bucket configs to the ClusterHelper so when you call the bucket you can call it by just passing the name of the bucket without credentials since they are stored in the ClusterHelper instance. So as there could be the case that there is multiple usernames and passwords for different buckets how else would you add them to the BucketConfigs ?

    Dim config = New ClientConfiguration()
        config.BucketConfigs.Clear()
        config.Servers = New List(Of Uri)(New Uri() {New Uri(serverUri)})
    Dim credentials = New PasswordAuthenticator(user, password)
        ClusterHelper.Initialize(config, credentials)

        Dim bucket = ClusterHelper.GetBucket("SOAPAPI")
        Dim bucket2 = ClusterHelper.GetBucket("default")

        Dim myData = bucket.GetDocument(Of Object)("uriEndPoint::C985544D-2A31-44A0-8228-3318A56DB8E9")
        Debug.WriteLine(myData)

        Dim myData2 = bucket2.GetDocument(Of Object)("cname::0151080f-8c0a-4f2f-9927-a818245cd681")
        Debug.WriteLine(myData2)

The above code works as it seems as you can pass the actual bucketname without having it configured in the bucketConfig but it does not allow for different Username and Passwords for Buckets anymore.

@aponnath -

That is the whole point of RBAC; you create a role and assign privileges to it which are associated with a resource. The method you are following is the older bucket/password approach, which has many limitations.

You can read more here.

-Jeff

Jeff, i was able to make it work. I think it would be a good idea to somewhere mention in this sample project that it will default to the old bucket password. That would safe someone some time not having to debug the source code like i did to find that out.

1 Like

@aponnath -

Good to hear you got its straightened out; yeah understood, its somewhat of a gotcha. We’ll work on improving things in the future.

-Jeff