Skip to content

Commit

Permalink
Add a new update session manager based on Azure Table optimistic locking
Browse files Browse the repository at this point in the history
  • Loading branch information
Nehme Bilal committed Nov 5, 2017
1 parent 3cf2be4 commit 29e0ba3
Show file tree
Hide file tree
Showing 20 changed files with 685 additions and 205 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,24 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UpdateSession\AzureTableUpdateSessionManager.cs" />
<Compile Include="UpdateSession\BlobBasedUpdateSessionManager.cs" />
<Compile Include="UpdateSession\AzureBlobStorageUpdateSessionDiModule.cs" />
<Compile Include="UpdateSession\AzureStorageUpdateSessionDiModule.cs" />
<Compile Include="UpdateSession\IUpdateBlob.cs" />
<Compile Include="UpdateSession\IUpdateBlobFactory.cs" />
<Compile Include="UpdateSession\IUpdateSessionTable.cs" />
<Compile Include="UpdateSession\Retry\LockUpdateBlobErrorDetectionStrategy.cs" />
<Compile Include="UpdateSession\Retry\StartUpdateSessionRetryDecorator.cs" />
<Compile Include="UpdateSession\Retry\StorageExceptionErrorDetectionStrategy.cs" />
<Compile Include="UpdateSession\UpdateBlob.cs" />
<Compile Include="UpdateSession\UpdateBlobFactory.cs" />
<Compile Include="UpdateSession\Retry\UpdateBlobFactoryRetryLockDecorator.cs" />
<Compile Include="UpdateSession\UpdateBlobUnavailableException.cs" />
<Compile Include="UpdateSession\Retry\UpdateSessionManagerRetryDecorator.cs" />
<Compile Include="UpdateSession\Retry\StorageExceptionUpdateSessionRetryDecorator.cs" />
<Compile Include="UpdateSession\UpdateDomainEntity.cs" />
<Compile Include="UpdateSession\UpdateSessionStatus.cs" />
<Compile Include="UpdateSession\UpdateSessionTable.cs" />
<Compile Include="UpdateSession\UpdateSessionTransaction.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AzureUtils\AzureUtils.csproj">
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using System;
using Autofac;
using Etg.Yams.Azure.Lease;
using Etg.Yams.Azure.UpdateSession.Retry;
using Etg.Yams.Update;
using Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling;

namespace Etg.Yams.Azure.UpdateSession
{
public class AzureStorageUpdateSessionDiModule
{
private readonly IContainer _container;
private const string UpdateSessionRetryStrategyModuleName = "updateSessionRetryStrategy";

public AzureStorageUpdateSessionDiModule(
string clusterId,
string instanceId,
string updateDomain,
string connectionString,
TimeSpan updateSessionTtl,
int storageExceptionRetryCount = 20,
int storageExceptionRetryIntervalInSeconds = 1,
int startUpdateSessionRetryCount = 5,
int startUpdateSessionRetryIntervalInSeconds = 1) : this(RegisterTypes(
clusterId, instanceId, updateDomain, connectionString, updateSessionTtl,
storageExceptionRetryCount, storageExceptionRetryIntervalInSeconds,
startUpdateSessionRetryCount, startUpdateSessionRetryIntervalInSeconds).Build())
{
}

public AzureStorageUpdateSessionDiModule(IContainer container)
{
_container = container;
}

public static ContainerBuilder RegisterTypes(string clusterId,
string instanceId,
string updateDomain,
string connectionString,
TimeSpan updateSessionTtl,
int storageExceptionRetryCount = 20,
int storageExceptionRetryIntervalInSeconds = 1,
int startUpdateSessionRetryCount = 5,
int startUpdateSessionRetryIntervalInSeconds = 1)
{
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterType<BlobLeaseFactory>().As<IBlobLeaseFactory>().SingleInstance();

containerBuilder.Register<RetryStrategy>(
c => new FixedInterval(storageExceptionRetryCount, TimeSpan.FromSeconds(storageExceptionRetryIntervalInSeconds)))
.Named<RetryStrategy>(UpdateSessionRetryStrategyModuleName).SingleInstance();

containerBuilder.Register(
c => new AzureTableUpdateSessionManager(c.Resolve<IUpdateSessionTable>(), clusterId, instanceId,
updateDomain));

containerBuilder.RegisterInstance(new UpdateSessionTable(connectionString, updateSessionTtl));
containerBuilder.Register<IUpdateSessionManager>(
c =>
new StartUpdateSessionRetryDecorator(
new StorageExceptionUpdateSessionRetryDecorator(
c.Resolve<AzureTableUpdateSessionManager>(),
c.ResolveNamed<RetryStrategy>(UpdateSessionRetryStrategyModuleName),
new StorageExceptionErrorDetectionStrategy()), startUpdateSessionRetryCount,
TimeSpan.FromSeconds(startUpdateSessionRetryIntervalInSeconds)));
return containerBuilder;
}

public IContainer Container => _container;

public IUpdateSessionManager UpdateSessionManager => _container.Resolve<IUpdateSessionManager>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Etg.Yams.Update;

namespace Etg.Yams.Azure.UpdateSession
{
public class AzureTableUpdateSessionManager : IUpdateSessionManager
{
public const string UpdateSessionTableName = "YamsUpdateSession";
private readonly IUpdateSessionTable _updateSessionTable;
private readonly string _clusterId;
private readonly string _instanceId;
private readonly string _instanceUpdateDomain;

public AzureTableUpdateSessionManager(IUpdateSessionTable updateSessionTable, string clusterId,
string instanceId, string instanceUpdateDomain)
{
_updateSessionTable = updateSessionTable;
_clusterId = clusterId;
_instanceId = instanceId;
_instanceUpdateDomain = instanceUpdateDomain;
}

public async Task<bool> TryStartUpdateSession(string appId)
{
Trace.TraceInformation(
$"Instance {_instanceId} will attempt to start update session for " +
$"ApplicationId = {appId}, UpdateDomain = {_instanceUpdateDomain}");

UpdateSessionTransaction transaction = new UpdateSessionTransaction(_clusterId, _instanceId, _instanceUpdateDomain, appId);
UpdateSessionStatus updateSessionStatus = await _updateSessionTable.FetchUpdateSessionStatus(_clusterId, appId);

if (updateSessionStatus.UpdateDomainEntity == null ||
updateSessionStatus.UpdateDomainEntity.UpdateDomain == _instanceUpdateDomain)
{
if (updateSessionStatus.UpdateDomainEntity == null)
{
transaction.InsertUpdateDomain();
}

transaction.MarkInstanceListAsModified();
}
else if(!updateSessionStatus.InstancesEntities.Any()) // no instance in the current update domain is updating
{
// set a new update domain (if no other instance beats us to it)
transaction.ReplaceUpdateDomain(updateSessionStatus); // will fail if current update domain changes
transaction.FailIfInstanceListModified(updateSessionStatus); // will fail if instance list changes
}
else
{
return false;
}

// enlist the current instance (this will succeed even if the active update domain is different but we
// won't start the update session, see below)
transaction.InsertOrReplaceInstance();

if (await _updateSessionTable.TryExecuteTransaction(transaction))
{
// handle the case where an instance enlisted itself after the update domain has changed,
string updateDomain = await _updateSessionTable.GetActiveUpdateDomain(_clusterId, appId);
if (updateDomain != _instanceUpdateDomain)
{
// Note that deleting this row is optional because it will filtered out anyway when list of instances
// of the active update domain is loaded (as a result, it's not an issue if this fails).
// We delete it anyway to keep the table clean.
await _updateSessionTable.DeleteInstanceEntity(_clusterId, _instanceId, appId);
return false;
}
Trace.TraceInformation(
$"Instance {_instanceId} successfully started the update session for " +
$"ApplicationId = {appId}, UpdateDomain = {_instanceUpdateDomain}");
return true;
}

return false;
}

public async Task EndUpdateSession(string appId)
{
Trace.TraceInformation(
$"Instance {_instanceId} Will attempt to end the update session for " +
$"ApplicationId = {appId}, " +
$"UpdateDomain = {_instanceUpdateDomain}");

await _updateSessionTable.DeleteInstanceEntity(_clusterId, _instanceId, appId);

Trace.TraceInformation(
$"Instance {_instanceId} successfully ended the update session for " +
$"ApplicationId = {appId}, " +
$"UpdateDomain = {_instanceUpdateDomain}");

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Threading.Tasks;

namespace Etg.Yams.Azure.UpdateSession
{
public interface IUpdateSessionTable
{
Task<UpdateSessionStatus> FetchUpdateSessionStatus(string clusterId, string appId);
Task<bool> TryExecuteTransaction(UpdateSessionTransaction transaction);
Task DeleteInstanceEntity(string clusterId, string instanceId, string appId);
Task<string> GetActiveUpdateDomain(string clusterId, string appId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System;
using System.Threading.Tasks;
using Etg.Yams.Update;

namespace Etg.Yams.Azure.UpdateSession.Retry
{
public class StartUpdateSessionRetryDecorator : IUpdateSessionManager
{
private readonly IUpdateSessionManager _updateSessionManager;
private readonly int _retryCount;
private readonly TimeSpan _retryInterval;

public StartUpdateSessionRetryDecorator(IUpdateSessionManager updateSessionManager, int retryCount,
TimeSpan retryInterval)
{
_updateSessionManager = updateSessionManager;
_retryCount = retryCount;
_retryInterval = retryInterval;
}

public async Task<bool> TryStartUpdateSession(string applicationId)
{
int count = 0;
while (count <= _retryCount)
{
if (await _updateSessionManager.TryStartUpdateSession(applicationId))
{
return true;
}

++count;
if (count <= _retryCount)
{
await Task.Delay(_retryInterval);
}
}
return false;
}

public Task EndUpdateSession(string applicationId)
{
return _updateSessionManager.EndUpdateSession(applicationId);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

namespace Etg.Yams.Azure.UpdateSession.Retry
{
public class UpdateSessionManagerRetryDecorator : IUpdateSessionManager
public class StorageExceptionUpdateSessionRetryDecorator : IUpdateSessionManager
{
private readonly IUpdateSessionManager _updateSessionManager;
private readonly RetryPolicy _retryPolicy;

public UpdateSessionManagerRetryDecorator(IUpdateSessionManager updateSessionManager,
public StorageExceptionUpdateSessionRetryDecorator(IUpdateSessionManager updateSessionManager,
RetryStrategy retryStrategy,
ITransientErrorDetectionStrategy errorDetectionStrategy)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Microsoft.WindowsAzure.Storage.Table;

namespace Etg.Yams.Azure.UpdateSession
{
public class UpdateDomainEntity : TableEntity
{
public UpdateDomainEntity()
{
}

public UpdateDomainEntity(string partitionKey, string rowKey, string updateDomain)
: base(partitionKey, rowKey)
{
UpdateDomain = updateDomain;
}

public string UpdateDomain
{
get; set;
}
}
}
Loading

0 comments on commit 29e0ba3

Please sign in to comment.