Skip to content
162 changes: 160 additions & 2 deletions src/Extensions/Abstractions/ExecutionContext/BaseExecutionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@
// Licensed under the MIT license.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using Microsoft.Extensions.Hosting;

namespace Microsoft.Omex.Extensions.Abstractions.ExecutionContext
Expand All @@ -28,6 +33,7 @@ public class BaseExecutionContext : IExecutionContext
internal const string ApplicationNameVariableName = "Fabric_ApplicationName";
internal const string NodeNameVariableName = "Fabric_NodeName";
internal const string NodeIPOrFQDNVariableName = "Fabric_NodeIPOrFQDN";
internal const string FarbicFolderApplication = "Fabric_Folder_Application";

/// <summary>
/// Create instance of execution context
Expand All @@ -36,10 +42,10 @@ public class BaseExecutionContext : IExecutionContext
public BaseExecutionContext(IHostEnvironment? hostEnvironment = null)
{
MachineName = GetMachineName();
BuildVersion = GetBuildVersion();
BuildVersion = GetBuildVersionFromServiceManifest() ?? DefaultEmptyValue;

ClusterIpAddress = GetIpAddress(MachineName);

RegionName = GetVariable(RegionNameVariableName) ?? DefaultEmptyValue;
DeploymentSlice = GetVariable(SliceNameVariableName) ?? DefaultEmptyValue;

Expand Down Expand Up @@ -137,6 +143,158 @@ protected static string GetBuildVersion()
: DefaultEmptyValue;
}

/// <summary>
/// Get build version from the current running service's manifest file
/// </summary>
/// <param name="serviceManifestName"> Service manifest name defined in ApplicationManifest.xml </param>
/// <returns> Build version if found, otherwise null </returns>
protected static string? GetBuildVersionFromServiceManifest(string serviceManifestName)
{
string? serviceManifestPath = GetServiceManifestPath(serviceManifestName);
if (serviceManifestPath == null)
{
return null;
}

return XElement.Load(serviceManifestPath).Attribute("Version")?.Value;
}

/// <summary>
/// Get build version from the current running service's manifest file
/// </summary>
/// <returns> Build version if found, otherwise null </returns>
protected static string? GetBuildVersionFromServiceManifest()
{
string? applicationDir = GetVariable(FarbicFolderApplication);
string? serviceName = GetVariable(ServiceNameVariableName);
string? applicationManifest = GetApplicationManifestPath();

if (applicationDir == null || serviceName == null || applicationManifest == null)
{
return null;
}

// Firstly we load all entries from Application Manifest
IEnumerable<XElement> descendants = XDocument
.Load(applicationManifest)
.Descendants();

// Query all service specific manifest names
IEnumerable<string> manifestNames = descendants
.Where(entry => entry.Name.LocalName == "ServiceManifestRef")
.Select(entry => entry.Attribute("ServiceManifestName")?.Value)
.Where(entry => entry != null)
.Select(entry => entry!); // we know at this point that there are not nulls.

// Query all services and corresponding types from Application Manifest
IEnumerable<(string serviceName, string serviceTypeName)> serviceMetaInfo = QueryServicesNamesWithTypes(descendants);

// Query all service types and corresponding build versions from service manifest files
IEnumerable<(string serviceTypeName, string buildVersion)> manifestMetaInfo = QueryServicesTypesWithVersion(manifestNames);

// Match our service with corresponding build version
try
{
string targetServiceType = serviceMetaInfo.Single(entry => entry.serviceName == serviceName.Split('/').Last())
.serviceTypeName;
return manifestMetaInfo.Single(entry => entry.serviceTypeName == targetServiceType).buildVersion;
}
catch (Exception)
{
// Target service not found
return null;
}
}

internal static IEnumerable<(string serviceTypeName, string buildVersion)> QueryServicesTypesWithVersion(IEnumerable<string> manifestNames)
{
return manifestNames
.Select(manifestFilename =>
{
string? manifestFilePath = GetServiceManifestPath(manifestFilename);
if (manifestFilePath == null)
{
return (serviceTypeName: null, buildVersion: null);
}

IEnumerable<XElement> manifestDescendants = XDocument.Load(manifestFilePath).Descendants();
IEnumerable<XElement> serviceManifestEntries = manifestDescendants
.Where(entry => entry.Name.LocalName == "ServiceManifest");

if (serviceManifestEntries.Count() != 1)
{
return (serviceTypeName: null, buildVersion: null);
}

string? buildVersion = serviceManifestEntries.Single().Attribute("Version")?.Value;

IEnumerable<string?> serviceTypeNames = manifestDescendants
.Where(entry => entry.Name.LocalName is "StatelessServiceType" or "StatefulServiceType")
.Select(entry => entry.Attribute("ServiceTypeName")?.Value);

if (serviceTypeNames.Count() != 1)
{
return (serviceTypeName: null, buildVersion: null);
}

return (serviceTypeName: serviceTypeNames.Single(), buildVersion);
})
.Where(entry => entry.serviceTypeName != null && entry.buildVersion != null)
.Select(entry => (serviceTypeName: entry.serviceTypeName!, buildVersion: entry.buildVersion!));
}

internal static IEnumerable<(string serviceName, string serviceTypeName)> QueryServicesNamesWithTypes(IEnumerable<XElement> descendants)
{
return descendants
.Where(entry => entry.Name.LocalName == "Service")
.Select(entry =>
{
string? serviceName = entry.Attribute("Name")?.Value;
IEnumerable<XElement> serviceDescendants = entry
.Descendants().Where(entry => entry.Name.LocalName is "StatelessService" or "StatefulService");

if (serviceDescendants.Count() != 1)
{
return (null, null);
}
XElement head = serviceDescendants.Single();
return (serviceName, serviceTypeName: head.Attribute("ServiceTypeName")?.Value);
})
.Where(entry => entry.serviceName != null && entry.serviceTypeName != null)
.Select(entry => (serviceName: entry.serviceName!, serviceTypeName: entry.serviceTypeName!)); // we know at this point that there are not nulls.
}

internal static string? GetApplicationManifestPath()
{
Regex regex = new(@"(ApplicationManifest\..*\.xml)$");
string[] files = Directory
.EnumerateFiles(@"C:\\SfDevCluster\", "*.*", SearchOption.AllDirectories)
.Where(path => regex.IsMatch(path))
.ToArray();

return files.Length != 1 ? null : files.Single();
}

internal static string? GetServiceManifestPath(string serviceManifestName)
{
string? applicationDir = GetVariable(FarbicFolderApplication);

if (applicationDir == null)
{
return null;
}

string serviceProperName = serviceManifestName.Replace(@"\", @"\\").Replace(".", @"\.");
string regexExp = string.Format(@"(.*{0}.*\.Manifest\..*\.xml)$", serviceProperName);
Regex regex = new(regexExp);

string[] manifests = Directory.GetFiles(applicationDir).Where(
path => regex.IsMatch(path)
).ToArray();

return manifests.Length != 1 ? null : manifests.Single();
}

/// <summary>
/// Default empty value
/// </summary>
Expand Down