-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a new update session manager based on Azure Table optimistic locking
- Loading branch information
Nehme Bilal
committed
Nov 5, 2017
1 parent
3cf2be4
commit 29e0ba3
Showing
20 changed files
with
685 additions
and
205 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 0 additions & 81 deletions
81
src/AzureBlobStorageUpdateSession/UpdateSession/AzureBlobStorageUpdateSessionDiModule.cs
This file was deleted.
Oops, something went wrong.
73 changes: 73 additions & 0 deletions
73
src/AzureBlobStorageUpdateSession/UpdateSession/AzureStorageUpdateSessionDiModule.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>(); | ||
} | ||
} |
96 changes: 96 additions & 0 deletions
96
src/AzureBlobStorageUpdateSession/UpdateSession/AzureTableUpdateSessionManager.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}"); | ||
|
||
} | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
src/AzureBlobStorageUpdateSession/UpdateSession/IUpdateSessionTable.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
src/AzureBlobStorageUpdateSession/UpdateSession/Retry/StartUpdateSessionRetryDecorator.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
src/AzureBlobStorageUpdateSession/UpdateSession/UpdateDomainEntity.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
Oops, something went wrong.