src/ccf/ccf-provider-common/NodeStorageProviders/AzureFileShareNodeStorageProvider.cs (284 lines of code) (raw):
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Nodes;
using Azure.ResourceManager.ContainerInstance;
using Azure.ResourceManager.ContainerInstance.Models;
using Docker.DotNet.Models;
using Microsoft.Extensions.Logging;
namespace CcfProvider;
internal class AzureFilesNodeStorageProvider : INodeStorageProvider
{
private readonly string networkName;
private readonly JsonObject providerConfig;
private readonly InfraType infraType;
private readonly ILogger logger;
public AzureFilesNodeStorageProvider(
string networkName,
JsonObject? providerConfig,
InfraType infraType,
ILogger logger)
{
if (providerConfig == null)
{
throw new ArgumentNullException("proivderInput cannot be null for AzureFiles");
}
this.networkName = networkName;
this.providerConfig = providerConfig;
this.infraType = infraType;
this.logger = logger;
}
public NodeStorageType GetNodeStorageType()
{
return NodeStorageType.AzureFiles;
}
public async Task<bool> NodeStorageDirectoryExists(string nodeName)
{
return await AzFileShare.FileShareExists(
ShareName(nodeName),
this.providerConfig!);
}
public async Task CreateNodeStorageDirectory(string nodeName)
{
await AzFileShare.CreateFileShare(
ShareName(nodeName),
nodeName,
this.networkName,
this.providerConfig!,
this.logger.ProgressReporter());
}
public async Task DeleteNodeStorageDirectory(string nodeName)
{
var shareName = ShareName(nodeName);
this.logger.LogWarning($"Deleting file share {shareName}.");
await AzFileShare.DeleteFileShare(
shareName,
this.networkName,
this.providerConfig!);
}
public async Task DeleteUncommittedLedgerFiles(string nodeName)
{
await AzFileShare.DeleteUncommittedLedgerFiles(
this.networkName,
ShareName(nodeName),
this.providerConfig!,
this.logger.ProgressReporter());
}
public Task<(IDirectory rwLedgerDir, IDirectory rwSnapshotsDir, IDirectory? logsDir)>
GetReadWriteLedgerSnapshotsDir(string nodeName)
{
var shareName = ShareName(nodeName);
IDirectory rwLedgerDir = new AzFileShareDirectory
{
MountPath = "/mnt/storage/ledger",
VolumeMountPath = "/mnt/storage",
ShareName = shareName
};
IDirectory rwSnapshotsDir = new AzFileShareDirectory
{
MountPath = "/mnt/storage/snapshots",
VolumeMountPath = "/mnt/storage",
ShareName = shareName
};
IDirectory logsDir = new AzFileShareDirectory
{
MountPath = "/mnt/storage/logs",
VolumeMountPath = "/mnt/storage",
ShareName = shareName
};
return Task.FromResult((rwLedgerDir, rwSnapshotsDir, (IDirectory?)logsDir));
}
public Task<(List<IDirectory> roLedgerDirs, IDirectory? roSnapshotsDir)>
GetReadonlyLedgerSnapshotsDir()
{
return this.GetReadonlyLedgerSnapshotsDirForNetwork(this.networkName);
}
public async Task<(List<IDirectory> roLedgerDirs, IDirectory? roSnapshotsDir)>
GetReadonlyLedgerSnapshotsDirForNetwork(string targetNetworkName)
{
IDirectory? roSnapshotsDir = null;
string? latestSnapshotShareName;
latestSnapshotShareName = await AzFileShare.FindShareWithLatestSnapshot(
targetNetworkName,
this.providerConfig!,
this.logger.ProgressReporter());
if (latestSnapshotShareName != null)
{
roSnapshotsDir = new AzFileShareDirectory
{
ShareName = latestSnapshotShareName,
MountPath = "/mnt/ro-snapshots/snapshots",
VolumeMountPath = "/mnt/ro-snapshots"
};
}
List<IDirectory> roLedgerDirs = new();
List<(string nodeName, string shareName)> ledgerShares =
await AzFileShare.FindSharesWithLedgers(
targetNetworkName,
committedLedgerFilesOnly: true,
this.providerConfig!,
this.logger.ProgressReporter());
int index = 0;
foreach ((string _, string shareName) in ledgerShares)
{
roLedgerDirs.Add(new AzFileShareDirectory
{
ShareName = shareName,
MountPath = $"/mnt/ro-ledgers-{index}/ledger",
VolumeMountPath = $"/mnt/ro-ledgers-{index}"
});
}
return (roLedgerDirs, roSnapshotsDir);
}
public async Task<List<string>> GetNodesWithLedgers()
{
List<(string nodeName, string shareName)> ledgerShares =
await AzFileShare.FindSharesWithLedgers(
this.networkName,
committedLedgerFilesOnly: false,
this.providerConfig!,
this.logger.ProgressReporter());
return ledgerShares.ConvertAll(s => s.nodeName);
}
public async Task CopyLatestSnapshot(IDirectory? roSnapshotsDir, IDirectory rwSnapshotsDir)
{
if (roSnapshotsDir == null)
{
return;
}
var roSnapDir = (AzFileShareDirectory)roSnapshotsDir;
var rwSnapDir = (AzFileShareDirectory)rwSnapshotsDir;
if (roSnapDir.ShareName == rwSnapDir.ShareName)
{
return;
}
// Eg "snapshots/snapshot_13_14.committed".
string? latestSnapshotName = await AzFileShare.FindLatestSnapshot(
roSnapDir.ShareName,
this.providerConfig,
this.logger.ProgressReporter());
if (!string.IsNullOrEmpty(latestSnapshotName))
{
if (!await AzFileShare.DirectoryExists(
rwSnapDir.ShareName,
"snapshots",
this.providerConfig))
{
this.logger.LogInformation($"Creating destination directory " +
$"{rwSnapDir.ShareName}/snapshots for copying snapshot.");
await AzFileShare.CreateDirectory(
rwSnapDir.ShareName,
"snapshots",
this.providerConfig);
}
this.logger.LogInformation($"Copying {latestSnapshotName} to " +
$"{rwSnapDir.ShareName}/snapshots.");
Stopwatch timeTaken = Stopwatch.StartNew();
await AzFileShare.Copy(
roSnapDir.ShareName,
latestSnapshotName,
rwSnapDir.ShareName,
this.providerConfig,
this.logger.ProgressReporter());
timeTaken.Stop();
this.logger.LogInformation($"Copy finished in {timeTaken.ElapsedMilliseconds}ms: " +
$"{latestSnapshotName} to {rwSnapDir.ShareName}/snapshots.");
}
}
public Task AddNodeStorageProviderConfiguration(
string nodeConfigDataDir,
IDirectory rwLedgerDir,
IDirectory rwSnapshotsDir,
List<IDirectory>? roLedgerDirs,
IDirectory? roSnapshotsDir)
{
return Task.CompletedTask;
}
public Task UpdateCreateContainerParams(
string nodeName,
List<IDirectory>? roLedgerDirs,
IDirectory? roSnapshotsDir,
CreateContainerParameters createContainerParams)
{
throw new NotImplementedException(
"UpdateCreateContainerParams not supported for AzureFiles.");
}
public async Task UpdateCreateContainerGroupParams(
IDirectory rwLedgerDir,
IDirectory rwSnapshotsDir,
List<IDirectory>? roLedgerDirs,
IDirectory? roSnapshotsDir,
ContainerGroupData createContainerParams)
{
var rwLedgerShare = (AzFileShareDirectory)rwLedgerDir;
var rwSnapshotsShare = (AzFileShareDirectory)rwSnapshotsDir;
if (rwLedgerShare.VolumeMountPath != rwSnapshotsShare.VolumeMountPath ||
rwLedgerShare.ShareName != rwSnapshotsShare.ShareName)
{
throw new NotSupportedException(
"Both rw ledger and snapshots dir should point to the same " +
"volume mount path and backing file share. " +
$"rwLedgerShare: {JsonSerializer.Serialize(rwLedgerShare)}, " +
$"rwSnapshotsShare: {JsonSerializer.Serialize(rwSnapshotsShare)}.");
}
var cchost = createContainerParams.Containers.Single(
c => c.Name == AciConstants.ContainerName.CcHost);
string accountName = AzStorage.GetStorageAccountName(
this.providerConfig.AzureFilesStorageAccountId());
string accountKey =
await AzStorage.GetStorageAccountKey(this.providerConfig.AzureFilesStorageAccountId());
string rwShareName = rwLedgerShare.ShareName;
string rwVolumeMountPath = rwLedgerShare.VolumeMountPath;
string rwVolumeName = "storagevolume";
createContainerParams.Volumes.Add(new ContainerVolume(rwVolumeName)
{
AzureFile = new ContainerInstanceAzureFileVolume(rwShareName, accountName)
{
StorageAccountKey = accountKey
}
});
cchost.VolumeMounts.Add(new ContainerVolumeMount(rwVolumeName, rwVolumeMountPath));
if (roLedgerDirs != null)
{
int index = 0;
foreach (var roLedgerDir in roLedgerDirs)
{
var dir = (AzFileShareDirectory)roLedgerDir;
string roLedegerShareName = dir.ShareName;
string volumeName = $"roledgervolume-{index}";
createContainerParams.Volumes.Add(new ContainerVolume(volumeName)
{
AzureFile = new ContainerInstanceAzureFileVolume(roLedegerShareName, accountName)
{
StorageAccountKey = accountKey,
// TODO (gsinha): Getting "invalid mount list" error during container group
// provisioning if this constraint is enforced by rego policy.
//IsReadOnly = true
}
});
cchost.VolumeMounts.Add(new ContainerVolumeMount(volumeName, dir.VolumeMountPath));
index++;
}
}
if (roSnapshotsDir != null)
{
var dir = (AzFileShareDirectory)roSnapshotsDir;
string roSnapshotsShareName = dir.ShareName;
string volumeName = "rosnapshotsvolume";
createContainerParams.Volumes.Add(new ContainerVolume(volumeName)
{
AzureFile = new ContainerInstanceAzureFileVolume(roSnapshotsShareName, accountName)
{
StorageAccountKey = accountKey,
// TODO (gsinha): Getting "invalid mount list" error during container group
// provisioning if this constraint is enforced by rego policy.
//IsReadOnly = true
}
});
cchost.VolumeMounts.Add(new ContainerVolumeMount(volumeName, dir.VolumeMountPath));
}
}
private static string ShareName(string nodeName)
{
return nodeName.ToLower();
}
}
internal class AzFileShareDirectory : IDirectory
{
public string ShareName { get; set; } = default!;
public string MountPath { get; set; } = default!;
public string VolumeMountPath { get; set; } = default!;
}