Skip to content

Commit fa6f157

Browse files
committed
import job create command
1 parent 9081a3a commit fa6f157

File tree

18 files changed

+864
-14
lines changed

18 files changed

+864
-14
lines changed

.github/CODEOWNERS

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,10 @@
135135
# ServiceLabel: %tools-Monitor
136136
# ServiceOwners: @smritiy @srnagar @jongio
137137

138-
# PRLabel: %tools-ManagedLustre
139-
/tools/Azure.Mcp.Tools.ManagedLustre/ @wolfgang-desalvador @microsoft/azure-mcp
140-
# ServiceLabel: %tools-ManagedLustre
141-
# ServiceOwners: @wolfgang-desalvador
138+
# PRLabel: %tools-AzureManagedLustre
139+
/tools/Azure.Mcp.Tools.AzureManagedLustre/ @wolfgang-desalvador @kinorirosnow @microsoft/azure-mcp
140+
# ServiceLabel: %tools-AzureManagedLustre
141+
# ServiceOwners: @wolfgang-desalvador @kinorirosnow
142142

143143
# PRLabel: %tools-MySQL
144144
/tools/Azure.Mcp.Tools.MySql/ @ramnov @mattkohnms @microsoft/azure-mcp
@@ -211,7 +211,7 @@
211211

212212

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

216216
# ServiceLabel: %tools-EventGrid
217217
# ServiceOwners: @microsoft/azure-mcp

eng/common/TestResources/New-TestResources.ps1

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ try {
179179
}
180180
Write-Verbose "Overriding test resources search directory to '$root'"
181181
}
182-
182+
183183
$templateFiles = @()
184184

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

204204
# returns empty string if $ServiceDirectory is not set
205205
$serviceName = GetServiceLeafDirectoryName $ServiceDirectory
206-
206+
207207
# in ci, random names are used
208208
# in non-ci, without BaseName, ResourceGroupName or ServiceDirectory, all invocations will
209209
# generate the same resource group name and base name for a given user
@@ -310,7 +310,7 @@ try {
310310
}
311311
}
312312

313-
# This needs to happen after we set the TenantId but before we use the ResourceGroupName
313+
# This needs to happen after we set the TenantId but before we use the ResourceGroupName
314314
if ($wellKnownTMETenants.Contains($TenantId)) {
315315
# Add a prefix to the resource group name to avoid flagging the usages of local auth
316316
# 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
@@ -606,6 +606,21 @@ try {
606606
$templateJson = Get-Content -LiteralPath $templateFile.jsonFilePath | ConvertFrom-Json
607607
$templateParameterNames = $templateJson.parameters.PSObject.Properties.Name
608608

609+
# Auto-resolve hpcCacheRpObjectId for AMLFS test resources if template expects it and it's not already supplied
610+
if ($templateParameterNames -contains 'hpcCacheRpObjectId' -and -not $templateParameters.ContainsKey('hpcCacheRpObjectId')) {
611+
try {
612+
$sp = Get-AzADServicePrincipal -DisplayName 'HPC Cache Resource Provider' -ErrorAction Stop
613+
if ($sp -and $sp.Id) {
614+
$templateParameters['hpcCacheRpObjectId'] = $sp.Id
615+
Write-Verbose "Resolved hpcCacheRpObjectId to '$($sp.Id)'"
616+
} else {
617+
Write-Warning "HPC Cache Resource Provider service principal not found; 'hpcCacheRpObjectId' will be missing and deployment may fail."
618+
}
619+
} catch {
620+
Write-Warning "Failed to resolve HPC Cache Resource Provider service principal: $_"
621+
}
622+
}
623+
609624
$templateFileParameters = $templateParameters.Clone()
610625
foreach ($key in $templateParameters.Keys) {
611626
if ($templateParameterNames -notcontains $key) {

eng/tools/ToolDescriptionEvaluator/prompts.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,10 @@
424424
"azmcp_azuremanagedlustre_filesystem_subnetsize_validate": [
425425
"Validate if <subnet_id> can host <filesystem_size> of <amlfs_sku>"
426426
],
427+
"azmcp_azuremanagedlustre_filesystem_importjob_create": [
428+
"Create an import job for the Azure Managed Lustre filesystem <filesystem_name> in resource group <resource_group_name>",
429+
"Start a filesystem import job for AMLFS <filesystem_name> with prefixes <prefixes>"
430+
],
427431
"azmcp_marketplace_product_get": [
428432
"Get details about marketplace product <product_name>"
429433
],

servers/Azure.Mcp.Server/CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,14 @@ The Azure MCP Server updates automatically by default whenever a new release com
205205
- `azmcp_foundry_agents_evaluate`: Evaluate a response from an agent by passing query and response inline
206206
- `azmcp_foundry_agents_query_and_evaluate`: Connect to an agent in an AI Foundry project, query it, and evaluate the response in one step
207207
- Enhanced AKS managed cluster information with comprehensive properties. [[#490](https://github.com/microsoft/mcp/pull/490)]
208-
- 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)]
208+
- 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)]
209+
- Added elicitation support. An elicitation request is sent if the tool annotation secret hint is true. [[#404](https://github.com/microsoft/mcp/pull/404)]
210+
- 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)]
211+
- Added the following Azure Managed Lustre commands: [[#100](https://github.com/microsoft/mcp/issues/100)]
212+
- `azmcp_azuremanagedlustre_filesystem_get_sku_info`: Get information about Azure Managed Lustre SKU.
213+
- `azmcp_functionapp_get` can now list Function Apps on a resource group level.
214+
- Added the following Azure Managed Lustre command (preview / placeholder implementation):
215+
- `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).
209216

210217
### Breaking Changes
211218

servers/Azure.Mcp.Server/docs/azmcp-commands.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1243,7 +1243,7 @@ azmcp monitor metrics query --subscription <subscription> \
12431243
# List Azure Managed Lustre Filesystems available in a subscription or resource group
12441244
# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired
12451245
azmcp managedlustre filesystem list --subscription <subscription> \
1246-
--resource-group <resource-group>
1246+
--resource-group <resource-group>
12471247
12481248
# Create an Azure Managed Lustre filesystem
12491249
# ❌ Destructive | ❌ Idempotent | ❌ OpenWorld | ❌ ReadOnly | ❌ Secret | ❌ LocalRequired
@@ -1296,6 +1296,16 @@ azmcp managedlustre filesystem subnetsize validate --subscription <subscription>
12961296
# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired
12971297
azmcp managedlustre filesystem sku get --subscription <subscription> \
12981298
--location <location>
1299+
1300+
# Create an Azure Managed Lustre filesystem import job (preview / placeholder)
1301+
azmcp azuremanagedlustre filesystem importjob create --subscription <subscription> \
1302+
--resource-group <resource-group> \
1303+
--file-system <filesystem-name> \
1304+
[--import-prefixes <prefix1> <prefix2> ... <prefixN>] \
1305+
[--conflict-resolution-mode <conflict-mode>] \
1306+
[--maximum-errors <maximum-errors>] \
1307+
[--admin-status <admin-status>] \
1308+
[--name <name>]
12991309
```
13001310

13011311
### Azure Native ISV Operations

servers/Azure.Mcp.Server/docs/e2eTestPrompts.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,8 @@ This file contains prompts used for end-to-end testing to ensure each tool is in
362362
| 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> |
363363
| 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> |
364364
| azmcp_managedlustre_filesystem_update | Update the maintenance window of the Azure Managed Lustre filesystem <filesystem_name> to <maintenance_window_day> at <maintenance_window_time> |
365+
| azmcp_azuremanagedlustre_filesystem_importjob_create | Create an import job for the Azure Managed Lustre filesystem <filesystem_name> in resource group <resource_group_name> |
366+
| azmcp_azuremanagedlustre_filesystem_importjob_create | Start a filesystem import job for AMLFS <filesystem_name> with prefixes <prefixes> |
365367

366368
## Azure Marketplace
367369

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Net;
5+
using Azure.Mcp.Core.Commands;
6+
using Azure.Mcp.Core.Extensions;
7+
using Azure.Mcp.Core.Models.Option;
8+
using Azure.Mcp.Tools.ManagedLustre.Options;
9+
using Azure.Mcp.Tools.ManagedLustre.Options.FileSystem;
10+
using Azure.Mcp.Tools.ManagedLustre.Services;
11+
using Microsoft.Extensions.Logging;
12+
13+
namespace Azure.Mcp.Tools.ManagedLustre.Commands.FileSystem;
14+
15+
public sealed class FileSystemImportJobCreateCommand(ILogger<FileSystemImportJobCreateCommand> logger)
16+
: BaseManagedLustreCommand<FileSystemImportJobCreateOptions>(logger)
17+
{
18+
private const string CommandTitle = "Create AMLFS Import Job";
19+
20+
public override string Name => "create";
21+
22+
public override string Description =>
23+
"""
24+
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.
25+
""";
26+
27+
public override string Title => CommandTitle;
28+
29+
public override ToolMetadata Metadata => new()
30+
{
31+
Destructive = false,
32+
Idempotent = true,
33+
OpenWorld = true,
34+
ReadOnly = false,
35+
LocalRequired = false,
36+
Secret = false
37+
};
38+
39+
protected override void RegisterOptions(Command command)
40+
{
41+
base.RegisterOptions(command);
42+
// Required common option
43+
command.Options.Add(OptionDefinitions.Common.ResourceGroup.AsRequired());
44+
// Service-specific options
45+
command.Options.Add(ManagedLustreOptionDefinitions.FileSystemOption);
46+
command.Options.Add(ManagedLustreOptionDefinitions.ImportPrefixesOption);
47+
command.Options.Add(ManagedLustreOptionDefinitions.ConflictResolutionModeOption);
48+
command.Options.Add(ManagedLustreOptionDefinitions.MaximumErrorsOption);
49+
command.Options.Add(ManagedLustreOptionDefinitions.JobNameOption);
50+
51+
// Validation for conflict resolution mode (Skip|Fail) – consistent with validator style in SubnetSizeAskCommand
52+
command.Validators.Add(cmdResult =>
53+
{
54+
if (cmdResult.TryGetValue(ManagedLustreOptionDefinitions.ConflictResolutionModeOption, out var mode)
55+
&& !string.IsNullOrWhiteSpace(mode)
56+
&& !string.Equals(mode, "Skip", StringComparison.OrdinalIgnoreCase)
57+
&& !string.Equals(mode, "Fail", StringComparison.OrdinalIgnoreCase))
58+
{
59+
cmdResult.AddError("Invalid conflict resolution mode. Allowed values: Skip, Fail.");
60+
}
61+
});
62+
}
63+
64+
protected override FileSystemImportJobCreateOptions BindOptions(ParseResult parseResult)
65+
{
66+
var options = base.BindOptions(parseResult);
67+
options.FileSystem = parseResult.GetValueOrDefault<string>(ManagedLustreOptionDefinitions.FileSystemOption.Name);
68+
options.ResourceGroup ??= parseResult.GetValueOrDefault<string>(OptionDefinitions.Common.ResourceGroup.Name);
69+
var prefixes = parseResult.GetValueOrDefault<string[]>(ManagedLustreOptionDefinitions.ImportPrefixesOption.Name);
70+
if (prefixes == null || prefixes.Length == 0)
71+
{
72+
options.ImportPrefixes = new List<string> { "/" };
73+
}
74+
else
75+
{
76+
options.ImportPrefixes = prefixes.ToList();
77+
}
78+
var conflictMode = parseResult.GetValueOrDefault<string>(ManagedLustreOptionDefinitions.ConflictResolutionModeOption.Name);
79+
conflictMode = string.IsNullOrWhiteSpace(conflictMode)
80+
? "Skip"
81+
: char.ToUpperInvariant(conflictMode[0]) + conflictMode.Substring(1).ToLowerInvariant();
82+
options.ConflictResolutionMode = conflictMode;
83+
options.MaximumErrors = parseResult.GetValueOrDefault<int?>(ManagedLustreOptionDefinitions.MaximumErrorsOption.Name) ?? -1;
84+
options.AdminStatus = "Active"; // Hard-coded since service no longer accepts parameter
85+
options.Name = parseResult.GetValueOrDefault<string>(ManagedLustreOptionDefinitions.JobNameOption.Name);
86+
return options;
87+
}
88+
89+
public override async Task<CommandResponse> ExecuteAsync(CommandContext context, ParseResult parseResult)
90+
{
91+
var options = BindOptions(parseResult);
92+
try
93+
{
94+
if (!Validate(parseResult.CommandResult, context.Response).IsValid)
95+
{
96+
return context.Response;
97+
}
98+
99+
var svc = context.GetService<IManagedLustreService>();
100+
var result = await svc.CreateImportJobAsync(
101+
options.Subscription!,
102+
options.ResourceGroup!,
103+
options.FileSystem!,
104+
options.Name,
105+
options.ImportPrefixes,
106+
options.ConflictResolutionMode!,
107+
options.MaximumErrors,
108+
options.Tenant,
109+
options.RetryPolicy);
110+
111+
context.Response.Results = ResponseResult.Create(
112+
new FileSystemImportJobCreateResult(result),
113+
ManagedLustreJsonContext.Default.FileSystemImportJobCreateResult);
114+
}
115+
catch (Exception ex)
116+
{
117+
_logger.LogError(ex,
118+
"Error creating AMLFS import job. FileSystem: {FileSystem} ResourceGroup: {ResourceGroup} Options: {@Options}",
119+
options.FileSystem, options.ResourceGroup, options);
120+
HandleException(context, ex);
121+
}
122+
123+
return context.Response;
124+
}
125+
126+
protected override HttpStatusCode GetStatusCode(Exception ex) => ex switch
127+
{
128+
Azure.RequestFailedException reqEx => (HttpStatusCode)reqEx.Status,
129+
_ => base.GetStatusCode(ex)
130+
};
131+
132+
internal record FileSystemImportJobCreateResult(Models.ImportJobInfo ImportJob);
133+
}

tools/Azure.Mcp.Tools.ManagedLustre/src/Commands/ManagedLustreJsonContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,7 @@ namespace Azure.Mcp.Tools.ManagedLustre.Commands;
1616
[JsonSerializable(typeof(LustreFileSystem))]
1717
[JsonSerializable(typeof(ManagedLustreSkuInfo))]
1818
[JsonSerializable(typeof(ManagedLustreSkuCapability))]
19+
[JsonSerializable(typeof(FileSystemImportJobCreateCommand.FileSystemImportJobCreateResult))]
20+
[JsonSerializable(typeof(ImportJobInfo))]
1921
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
2022
internal partial class ManagedLustreJsonContext : JsonSerializerContext;

tools/Azure.Mcp.Tools.ManagedLustre/src/ManagedLustreSetup.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,16 @@ public void ConfigureServices(IServiceCollection services)
2323
services.AddSingleton<SubnetSizeAskCommand>();
2424
services.AddSingleton<SubnetSizeValidateCommand>();
2525
services.AddSingleton<SkuGetCommand>();
26+
services.AddSingleton<FileSystemImportJobCreateCommand>();
2627
}
2728

2829
public CommandGroup RegisterCommands(IServiceProvider serviceProvider)
2930
{
3031
var managedLustre = new CommandGroup(Name,
31-
"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.");
32+
"""
33+
Azure Managed Lustre operations - Azure Managed Lustre file systems (AMLFS) interaction for high-performance computing workloads.
34+
Use this tool to list and manage Azure Managed Lustre file systems, including creating import jobs to hydrate the file system namespace.
35+
""");
3236

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

64+
var importJob = new CommandGroup("importjob", "Azure Managed Lustre file system import job operations - Create manual import jobs to hydrate the file system namespace.");
65+
fileSystem.AddSubGroup(importJob);
66+
67+
var importJobCreate = serviceProvider.GetRequiredService<FileSystemImportJobCreateCommand>();
68+
importJob.AddCommand(importJobCreate.Name, importJobCreate);
69+
6070
return managedLustre;
6171
}
6272
}

tools/Azure.Mcp.Tools.ManagedLustre/src/Models/LustreFileSystem.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,15 @@ public sealed record LustreFileSystem(
2626
[property: JsonPropertyName("squashUid")] long? SquashUid,
2727
[property: JsonPropertyName("squashGid")] long? SquashGid
2828
);
29+
30+
public sealed record ImportJobInfo(
31+
[property: JsonPropertyName("name")] string Name,
32+
[property: JsonPropertyName("fileSystemName")] string FileSystemName,
33+
[property: JsonPropertyName("resourceGroupName")] string ResourceGroupName,
34+
[property: JsonPropertyName("subscriptionId")] string SubscriptionId,
35+
[property: JsonPropertyName("status")] string Status,
36+
[property: JsonPropertyName("conflictResolutionMode")] string ConflictResolutionMode,
37+
[property: JsonPropertyName("maximumErrors")] int? MaximumErrors,
38+
[property: JsonPropertyName("adminStatus")] string? AdminStatus,
39+
[property: JsonPropertyName("importPrefixes")] IList<string>? ImportPrefixes
40+
);

0 commit comments

Comments
 (0)