Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,10 @@
# ServiceLabel: %tools-Monitor
# ServiceOwners: @smritiy @srnagar @jongio

# PRLabel: %tools-ManagedLustre
/tools/Azure.Mcp.Tools.ManagedLustre/ @wolfgang-desalvador @microsoft/azure-mcp
# ServiceLabel: %tools-ManagedLustre
# ServiceOwners: @wolfgang-desalvador
# PRLabel: %tools-AzureManagedLustre
/tools/Azure.Mcp.Tools.AzureManagedLustre/ @wolfgang-desalvador @kinorirosnow @microsoft/azure-mcp
# ServiceLabel: %tools-AzureManagedLustre
# ServiceOwners: @wolfgang-desalvador @kinorirosnow

# PRLabel: %tools-MySQL
/tools/Azure.Mcp.Tools.MySql/ @ramnov @mattkohnms @microsoft/azure-mcp
Expand Down Expand Up @@ -211,7 +211,7 @@


# PRLabel: %tools-EventGrid
/tools/Azure.Mcp.Tools.EventGrid/ @microsoft/azure-mcp
/tools/Azure.Mcp.Tools.EventGrid/ @microsoft/azure-mcp

# ServiceLabel: %tools-EventGrid
# ServiceOwners: @microsoft/azure-mcp
Expand Down
21 changes: 18 additions & 3 deletions eng/common/TestResources/New-TestResources.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ try {
}
Write-Verbose "Overriding test resources search directory to '$root'"
}

$templateFiles = @()

"$ResourceType-resources.json", "$ResourceType-resources.bicep" | ForEach-Object {
Expand All @@ -203,7 +203,7 @@ try {

# returns empty string if $ServiceDirectory is not set
$serviceName = GetServiceLeafDirectoryName $ServiceDirectory

# in ci, random names are used
# in non-ci, without BaseName, ResourceGroupName or ServiceDirectory, all invocations will
# generate the same resource group name and base name for a given user
Expand Down Expand Up @@ -310,7 +310,7 @@ try {
}
}

# This needs to happen after we set the TenantId but before we use the ResourceGroupName
# This needs to happen after we set the TenantId but before we use the ResourceGroupName
if ($wellKnownTMETenants.Contains($TenantId)) {
# Add a prefix to the resource group name to avoid flagging the usages of local auth
# See details at https://eng.ms/docs/products/onecert-certificates-key-vault-and-dsms/key-vault-dsms/certandsecretmngmt/credfreefaqs#how-can-i-disable-s360-reporting-when-testing-customer-facing-3p-features-that-depend-on-use-of-unsafe-local-auth
Expand Down Expand Up @@ -606,6 +606,21 @@ try {
$templateJson = Get-Content -LiteralPath $templateFile.jsonFilePath | ConvertFrom-Json
$templateParameterNames = $templateJson.parameters.PSObject.Properties.Name

# Auto-resolve hpcCacheRpObjectId for AMLFS test resources if template expects it and it's not already supplied
if ($templateParameterNames -contains 'hpcCacheRpObjectId' -and -not $templateParameters.ContainsKey('hpcCacheRpObjectId')) {
try {
$sp = Get-AzADServicePrincipal -DisplayName 'HPC Cache Resource Provider' -ErrorAction Stop
if ($sp -and $sp.Id) {
$templateParameters['hpcCacheRpObjectId'] = $sp.Id
Write-Verbose "Resolved hpcCacheRpObjectId to '$($sp.Id)'"
} else {
Write-Warning "HPC Cache Resource Provider service principal not found; 'hpcCacheRpObjectId' will be missing and deployment may fail."
}
} catch {
Write-Warning "Failed to resolve HPC Cache Resource Provider service principal: $_"
}
}

$templateFileParameters = $templateParameters.Clone()
foreach ($key in $templateParameters.Keys) {
if ($templateParameterNames -notcontains $key) {
Expand Down
4 changes: 4 additions & 0 deletions eng/tools/ToolDescriptionEvaluator/prompts.json
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,10 @@
"azmcp_azuremanagedlustre_filesystem_subnetsize_validate": [
"Validate if <subnet_id> can host <filesystem_size> of <amlfs_sku>"
],
"azmcp_azuremanagedlustre_filesystem_importjob_create": [
"Create an import job for the Azure Managed Lustre filesystem <filesystem_name> in resource group <resource_group_name>",
"Start a filesystem import job for AMLFS <filesystem_name> with prefixes <prefixes>"
],
"azmcp_marketplace_product_get": [
"Get details about marketplace product <product_name>"
],
Expand Down
9 changes: 8 additions & 1 deletion servers/Azure.Mcp.Server/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,14 @@ The Azure MCP Server updates automatically by default whenever a new release com
- `azmcp_foundry_agents_evaluate`: Evaluate a response from an agent by passing query and response inline
- `azmcp_foundry_agents_query_and_evaluate`: Connect to an agent in an AI Foundry project, query it, and evaluate the response in one step
- Enhanced AKS managed cluster information with comprehensive properties. [[#490](https://github.com/microsoft/mcp/pull/490)]
- Added support retrieving Key Vault Managed HSM account settings via the command `azmcp-keyvault-admin-settings-get`. [[#358](https://github.com/microsoft/mcp/pull/358)]
- Added support retrieving Key Vault Managed HSM account settings via the command `azmcp-keyvault-admin-settings-get`. [[358](https://github.com/microsoft/mcp/pull/358)]
- Added elicitation support. An elicitation request is sent if the tool annotation secret hint is true. [[#404](https://github.com/microsoft/mcp/pull/404)]
- Added `azmcp sql server create`, `azmcp sql server delete`, `azmcp sql server show` to support SQL server create, delete, and show commands. [[#312](https://github.com/microsoft/mcp/pull/312)]
- Added the following Azure Managed Lustre commands: [[#100](https://github.com/microsoft/mcp/issues/100)]
- `azmcp_azuremanagedlustre_filesystem_get_sku_info`: Get information about Azure Managed Lustre SKU.
- `azmcp_functionapp_get` can now list Function Apps on a resource group level.
- Added the following Azure Managed Lustre command (preview / placeholder implementation):
- `azmcp_azuremanagedlustre_filesystem_importjob_create`: Create a manual import job for an Azure Managed Lustre filesystem (hydrates namespace from linked HSM/Blob; current release returns placeholder status until REST API integration ships).

### Breaking Changes

Expand Down
12 changes: 11 additions & 1 deletion servers/Azure.Mcp.Server/docs/azmcp-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -1243,7 +1243,7 @@ azmcp monitor metrics query --subscription <subscription> \
# List Azure Managed Lustre Filesystems available in a subscription or resource group
# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired
azmcp managedlustre filesystem list --subscription <subscription> \
--resource-group <resource-group>
--resource-group <resource-group>

# Create an Azure Managed Lustre filesystem
# ❌ Destructive | ❌ Idempotent | ❌ OpenWorld | ❌ ReadOnly | ❌ Secret | ❌ LocalRequired
Expand Down Expand Up @@ -1296,6 +1296,16 @@ azmcp managedlustre filesystem subnetsize validate --subscription <subscription>
# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired
azmcp managedlustre filesystem sku get --subscription <subscription> \
--location <location>

# Create an Azure Managed Lustre filesystem import job (preview / placeholder)
azmcp azuremanagedlustre filesystem importjob create --subscription <subscription> \
--resource-group <resource-group> \
--file-system <filesystem-name> \
[--import-prefixes <prefix1> <prefix2> ... <prefixN>] \
[--conflict-resolution-mode <conflict-mode>] \
[--maximum-errors <maximum-errors>] \
[--admin-status <admin-status>] \
[--name <name>]
```

### Azure Native ISV Operations
Expand Down
2 changes: 2 additions & 0 deletions servers/Azure.Mcp.Server/docs/e2eTestPrompts.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,8 @@ This file contains prompts used for end-to-end testing to ensure each tool is in
| azmcp_managedlustre_filesystem_subnetsize_ask | Tell me how many IP addresses I need for an Azure Managed Lustre filesystem of size <filesystem_size> using the SKU <sku> |
| azmcp_managedlustre_filesystem_subnetsize_validate | Validate if the network <subnet_id> can host Azure Managed Lustre filesystem of size <filesystem_size> using the SKU <sku> |
| azmcp_managedlustre_filesystem_update | Update the maintenance window of the Azure Managed Lustre filesystem <filesystem_name> to <maintenance_window_day> at <maintenance_window_time> |
| azmcp_azuremanagedlustre_filesystem_importjob_create | Create an import job for the Azure Managed Lustre filesystem <filesystem_name> in resource group <resource_group_name> |
| azmcp_azuremanagedlustre_filesystem_importjob_create | Start a filesystem import job for AMLFS <filesystem_name> with prefixes <prefixes> |

## Azure Marketplace

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Net;
using Azure.Mcp.Core.Commands;
using Azure.Mcp.Core.Extensions;
using Azure.Mcp.Core.Models.Option;
using Azure.Mcp.Tools.ManagedLustre.Options;
using Azure.Mcp.Tools.ManagedLustre.Options.FileSystem;
using Azure.Mcp.Tools.ManagedLustre.Services;
using Microsoft.Extensions.Logging;

namespace Azure.Mcp.Tools.ManagedLustre.Commands.FileSystem;

public sealed class FileSystemImportJobCreateCommand(ILogger<FileSystemImportJobCreateCommand> logger)
: BaseManagedLustreCommand<FileSystemImportJobCreateOptions>(logger)
{
private const string CommandTitle = "Create AMLFS Import Job";

public override string Name => "create";

public override string Description =>
"""
Creates a manual import job for an Azure Managed Lustre (AMLFS) file system. The import job scans the linked HSM/Blob container and imports specified path prefixes (or all when omitted) honoring the chosen conflict resolution mode. Use to hydrate the AMLFS namespace or refresh content.
""";

public override string Title => CommandTitle;

public override ToolMetadata Metadata => new()
{
Destructive = false,
Idempotent = true,
OpenWorld = true,
ReadOnly = false,
LocalRequired = false,
Secret = false
};

protected override void RegisterOptions(Command command)
{
base.RegisterOptions(command);
// Required common option
command.Options.Add(OptionDefinitions.Common.ResourceGroup.AsRequired());
// Service-specific options
command.Options.Add(ManagedLustreOptionDefinitions.FileSystemOption);
command.Options.Add(ManagedLustreOptionDefinitions.ImportPrefixesOption);
command.Options.Add(ManagedLustreOptionDefinitions.ConflictResolutionModeOption);
command.Options.Add(ManagedLustreOptionDefinitions.MaximumErrorsOption);
command.Options.Add(ManagedLustreOptionDefinitions.JobNameOption);

// Validation for conflict resolution mode (Skip|Fail) – consistent with validator style in SubnetSizeAskCommand
command.Validators.Add(cmdResult =>
{
if (cmdResult.TryGetValue(ManagedLustreOptionDefinitions.ConflictResolutionModeOption, out var mode)
&& !string.IsNullOrWhiteSpace(mode)
&& !string.Equals(mode, "Skip", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(mode, "Fail", StringComparison.OrdinalIgnoreCase))
{
cmdResult.AddError("Invalid conflict resolution mode. Allowed values: Skip, Fail.");
}
});
}

protected override FileSystemImportJobCreateOptions BindOptions(ParseResult parseResult)
{
var options = base.BindOptions(parseResult);
options.FileSystem = parseResult.GetValueOrDefault<string>(ManagedLustreOptionDefinitions.FileSystemOption.Name);
options.ResourceGroup ??= parseResult.GetValueOrDefault<string>(OptionDefinitions.Common.ResourceGroup.Name);
var prefixes = parseResult.GetValueOrDefault<string[]>(ManagedLustreOptionDefinitions.ImportPrefixesOption.Name);
if (prefixes == null || prefixes.Length == 0)
{
options.ImportPrefixes = new List<string> { "/" };
}
else
{
options.ImportPrefixes = prefixes.ToList();
}
var conflictMode = parseResult.GetValueOrDefault<string>(ManagedLustreOptionDefinitions.ConflictResolutionModeOption.Name);
conflictMode = string.IsNullOrWhiteSpace(conflictMode)
? "Skip"
: char.ToUpperInvariant(conflictMode[0]) + conflictMode.Substring(1).ToLowerInvariant();
options.ConflictResolutionMode = conflictMode;
options.MaximumErrors = parseResult.GetValueOrDefault<int?>(ManagedLustreOptionDefinitions.MaximumErrorsOption.Name) ?? -1;
options.AdminStatus = "Active"; // Hard-coded since service no longer accepts parameter
options.Name = parseResult.GetValueOrDefault<string>(ManagedLustreOptionDefinitions.JobNameOption.Name);
return options;
}

public override async Task<CommandResponse> ExecuteAsync(CommandContext context, ParseResult parseResult)
{
var options = BindOptions(parseResult);
try
{
if (!Validate(parseResult.CommandResult, context.Response).IsValid)
{
return context.Response;
}

var svc = context.GetService<IManagedLustreService>();
var result = await svc.CreateImportJobAsync(
options.Subscription!,
options.ResourceGroup!,
options.FileSystem!,
options.Name,
options.ImportPrefixes,
options.ConflictResolutionMode!,
options.MaximumErrors,
options.Tenant,
options.RetryPolicy);

context.Response.Results = ResponseResult.Create(
new FileSystemImportJobCreateResult(result),
ManagedLustreJsonContext.Default.FileSystemImportJobCreateResult);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Error creating AMLFS import job. FileSystem: {FileSystem} ResourceGroup: {ResourceGroup} Options: {@Options}",
options.FileSystem, options.ResourceGroup, options);
HandleException(context, ex);
}

return context.Response;
}

protected override HttpStatusCode GetStatusCode(Exception ex) => ex switch
{
Azure.RequestFailedException reqEx => (HttpStatusCode)reqEx.Status,
_ => base.GetStatusCode(ex)
};

internal record FileSystemImportJobCreateResult(Models.ImportJobInfo ImportJob);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,7 @@ namespace Azure.Mcp.Tools.ManagedLustre.Commands;
[JsonSerializable(typeof(LustreFileSystem))]
[JsonSerializable(typeof(ManagedLustreSkuInfo))]
[JsonSerializable(typeof(ManagedLustreSkuCapability))]
[JsonSerializable(typeof(FileSystemImportJobCreateCommand.FileSystemImportJobCreateResult))]
[JsonSerializable(typeof(ImportJobInfo))]
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
internal partial class ManagedLustreJsonContext : JsonSerializerContext;
12 changes: 11 additions & 1 deletion tools/Azure.Mcp.Tools.ManagedLustre/src/ManagedLustreSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,16 @@ public void ConfigureServices(IServiceCollection services)
services.AddSingleton<SubnetSizeAskCommand>();
services.AddSingleton<SubnetSizeValidateCommand>();
services.AddSingleton<SkuGetCommand>();
services.AddSingleton<FileSystemImportJobCreateCommand>();
}

public CommandGroup RegisterCommands(IServiceProvider serviceProvider)
{
var managedLustre = new CommandGroup(Name,
"Azure Managed Lustre operations - Commands for creating, updating, listing and inspecting Azure Managed Lustre file systems (AMLFS) used for high-performance computing workloads. The tool focuses on managing all the aspects related to Azure Managed Lustre file system instances.");
"""
Azure Managed Lustre operations - Azure Managed Lustre file systems (AMLFS) interaction for high-performance computing workloads.
Use this tool to list and manage Azure Managed Lustre file systems, including creating import jobs to hydrate the file system namespace.
""");

var fileSystem = new CommandGroup("filesystem", "Azure Managed Lustre file system operations - Commands for listing managed Lustre file systems.");
managedLustre.AddSubGroup(fileSystem);
Expand Down Expand Up @@ -57,6 +61,12 @@ public CommandGroup RegisterCommands(IServiceProvider serviceProvider)
var skuGet = serviceProvider.GetRequiredService<SkuGetCommand>();
sku.AddCommand(skuGet.Name, skuGet);

var importJob = new CommandGroup("importjob", "Azure Managed Lustre file system import job operations - Create manual import jobs to hydrate the file system namespace.");
fileSystem.AddSubGroup(importJob);

var importJobCreate = serviceProvider.GetRequiredService<FileSystemImportJobCreateCommand>();
importJob.AddCommand(importJobCreate.Name, importJobCreate);

return managedLustre;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,15 @@ public sealed record LustreFileSystem(
[property: JsonPropertyName("squashUid")] long? SquashUid,
[property: JsonPropertyName("squashGid")] long? SquashGid
);

public sealed record ImportJobInfo(
[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("fileSystemName")] string FileSystemName,
[property: JsonPropertyName("resourceGroupName")] string ResourceGroupName,
[property: JsonPropertyName("subscriptionId")] string SubscriptionId,
[property: JsonPropertyName("status")] string Status,
[property: JsonPropertyName("conflictResolutionMode")] string ConflictResolutionMode,
[property: JsonPropertyName("maximumErrors")] int? MaximumErrors,
[property: JsonPropertyName("adminStatus")] string? AdminStatus,
[property: JsonPropertyName("importPrefixes")] IList<string>? ImportPrefixes
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Text.Json.Serialization;

namespace Azure.Mcp.Tools.ManagedLustre.Options.FileSystem;

public sealed class FileSystemImportJobCreateOptions : BaseManagedLustreOptions
{
[JsonPropertyName(ManagedLustreOptionDefinitions.fileSystem)]
public string? FileSystem { get; set; }

[JsonPropertyName(ManagedLustreOptionDefinitions.importPrefixes)]
public IList<string>? ImportPrefixes { get; set; }

[JsonPropertyName(ManagedLustreOptionDefinitions.conflictResolutionMode)]
public string? ConflictResolutionMode { get; set; }

[JsonPropertyName(ManagedLustreOptionDefinitions.maximumErrors)]
public int? MaximumErrors { get; set; }

[JsonPropertyName(ManagedLustreOptionDefinitions.adminStatus)]
public string? AdminStatus { get; set; }

[JsonPropertyName(ManagedLustreOptionDefinitions.jobName)]
public string? Name { get; set; }
}
Loading
Loading