Integration Tests in Service Fabric – Client side deployment

In the most recent post, I showed how the R base class fetches all the test definitions from the cluster and executes the tests one by one. In this post, I will walk you through the client side deployment. One of the nice benefits of Service Fabric is that it provides out of the box a relatively complete deployment story. It is possible to deploy application packages entirely in code by using Powershell or C#. All you need for that to work is the latest SDK.

To deploy an application to the cluster we need to know a few things upfront. Mainly these are:

  • The location of the cluster
  • The ImageStoreConnectionString
  • The ApplicationTypeName
  • The ApplicationTypeVersion
  • The ServiceUri where the tests will be exposed
  • The application package path

The R base class derives the above settings by convention.

TypeName = typeof(TSelf).Name;
ImageStorePath = DeterministicGuid(TypeName);
ApplicationTypeName = $"{TypeName}Type";
ApplicationTypeVersion = new Version(1, 0, 0);
ApplicationName = new Uri($"fabric:/{TypeName}");
ServiceUri = new Uri($"fabric:/{TypeName}/Tests");
var oneLevelUp = Path.Combine(DetermineCallerFilePath(), @"..\");
var applicationName = $"{TypeName}Application";
var directory = Directory.EnumerateDirectories(oneLevelUp, $"{applicationName}", SearchOption.TopDirectoryOnly).Single();
#if DEBUG
var directoryName = "Debug";
#else
var directoryName = "Release";
#endif

TestAppPkgPath = $@"{directory}\pkg\{directoryName}";

static string DetermineCallerFilePath([CallerFilePath] string path = null)
{
    return Path.GetDirectoryName(path);
}

The generic parameter of the R base class is used to determine the type name. Then a deterministic GUID is created that derives from the type name. Most of the conventions follow the type name. With the CallerFilePath attribute, the code determines the location that is a level higher up in the current project hierarchy. In most scenarios, this should be the solution folder. Based on that the directory is searched the matches the application name. With a simple, if compiler directive the test application package path is computed. So for the OrderShippingTests these properties would look like the following:

  • TypeName: OrderShippingTests
  • ImageStorePath: 9804fe18-175e-848b-b596-b408d6948141
  • ApplicationName: OrderShippingTestsType
  • ApplicationTypeVersion: 1.0.0
  • ApplicationName: fabric:/OrderShippingTests
  • ServiceUri: fabric:/OrderShippingTests/Tests
  • TestAppPkgPath: ..\OrderShippingTestsApplication\pkg\[Release|Debug]

As soon as these properties are defined the deployment process can start. Here is the Setup method in all it’s glory:

static async Task SetUp() {
    using (var fabric = new FabricClient()) {
        var clusterManifest = await GetClusterManifest(fabric).ConfigureAwait(false);
        imageStoreConnectionString = clusterManifest["Management"]["ImageStoreConnectionString"];
        await TearDown().ConfigureAwait(false);
        var app = fabric.ApplicationManager;
        app.CopyApplicationPackage(imageStoreConnectionString, TestAppPkgPath, ImageStorePath.ToString());
        await app.ProvisionApplicationAsync(ImageStorePath.ToString()).ConfigureAwait(false);
        var nameValueCollection = new NameValueCollection();
        await app.CreateApplicationAsync(new ApplicationDescription(ApplicationName, ApplicationTypeName, ApplicationTypeVersion.ToString(), nameValueCollection)).ConfigureAwait(false);
    }

    testRunner = ServiceProxy.Create<ITestRunner>(ServiceUri);
}

The GetClusterManifest method fetches the cluster manifest with the fabric client and uses XDocument magic to turn the manifest into a nested dictionary. The cluster manifest contains the ImageStoreConnectionString in the management section. Before the application is redeployment any potential leftover on the cluster of previous deployments of the application are first torn down in the TearDown method (more on that later). The fabric client exposes an ApplicationManager which allows copying the test application package into the image store, then the application is provisioned and created. As soon as this process is done the ServiceProxy class is used to create a remoting client of type ITestRunner that is bound to the ServiceUri. The above code shows the true power of the SDK. With only a few lines of CSharp code I’m able to automatically deploy, provision and create an application in the cluster. Now let’s have a look into the tear down.

static async Task TearDown() {
    using (var fabric = new FabricClient()) {
        var app = fabric.ApplicationManager;
        var applications = await fabric.QueryManager.GetApplicationListAsync(ApplicationName).ConfigureAwait(false);
        if (applications.Any()) {
            await app.DeleteApplicationAsync(new DeleteApplicationDescription(ApplicationName)).ConfigureAwait(false);
            await app.UnprovisionApplicationAsync(ApplicationTypeName, ApplicationTypeVersion.ToString()).ConfigureAwait(false);
            app.RemoveApplicationPackage(imageStoreConnectionString, ImageStorePath.ToString());
        }

        var applicationTypes = await fabric.QueryManager.GetApplicationTypeListAsync(ApplicationTypeName);
        if (applicationTypes.Any()) {
            await app.UnprovisionApplicationAsync(ApplicationTypeName, ApplicationTypeVersion.ToString()).ConfigureAwait(false);
        }
    }
}

It is almost as simple as the setup. The application manager is used again in combination with the query manager to query for the applications with the name that was derived by conventions. If any applications are found they are deleted. The order is crucial here. First, the application has to be deleted, then the application needs to be unprovisioned and the pkg is removed from the image store. Sometimes it is possible, for example when someone aborts the teardown in the middle that the application type name remains. In that the application list query will not return anything. But the deployment in the setup would fail since the application type is already defined. Therefore as a final step the teardown method queries for the leftover application type names and removes them as well.

Why am I showing you all this code you might wonder? My belief is that with these posts you might learn a few things with Service Fabric, like deploying, provisioning and more with the SDK that you haven’t used before. You never know when such knowledge might come handy. I hope you find these posts valuable. If you do so or not feel free to tell me in the comments so that I can improve.

About the author

Daniel Marbach

6 comments

  • Again Thank you for these posts!

    Do you see any possibility to deploy other Statefull/stateless services in the test setup, along with the service that hosts the tests? Then we could integration test a full service -> post some input -> monitor the output in a DB/bus etc.

  • It should work out of the box. It is just a matter of packaging things together into the same application. Since the whole application gets deployed anything that is packaged with it will be deployed.

  • The R class is only deploying the TestPkg. As I see it – the class needs to be extended so it deploys others services specified in the applicationManifest.xml?

By Daniel Marbach

Recent Posts