Skip to content

Commit b57230f

Browse files
committed
Added support for reregistration of a backup item
from one vault to another.
1 parent 4b28e88 commit b57230f

File tree

6 files changed

+611
-13
lines changed

6 files changed

+611
-13
lines changed

src/RecoveryServices/RecoveryServices.Backup.Providers/Providers/AzureFilesPsBackupProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1061,7 +1061,7 @@ private RestAzureNS.AzureOperationResponse<ProtectedItemResource> EnableOrModify
10611061
PolicyBase policy = ProviderData.ContainsKey(ItemParams.Policy) ?
10621062
(PolicyBase)ProviderData[ItemParams.Policy] : null;
10631063

1064-
ItemBase itemBase = (ItemBase)ProviderData[ItemParams.Item];
1064+
ItemBase itemBase = ProviderData.ContainsKey(ItemParams.Item) ? (ItemBase)ProviderData[ItemParams.Item] : null;
10651065

10661066
AzureFileShareItem item = (AzureFileShareItem)ProviderData[ItemParams.Item];
10671067

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
// ----------------------------------------------------------------------------------
2+
//
3+
// Copyright Microsoft Corporation
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
// ----------------------------------------------------------------------------------
14+
15+
using Microsoft.Azure.Commands.RecoveryServices.Backup.Cmdlets.Models;
16+
using Microsoft.Azure.Commands.RecoveryServices.Backup.Cmdlets.ProviderModel;
17+
using Microsoft.Azure.Commands.RecoveryServices.Backup.Cmdlets.ServiceClientAdapterNS;
18+
using Microsoft.Azure.Commands.RecoveryServices.Backup.Helpers;
19+
using Microsoft.Azure.Commands.RecoveryServices.Backup.Properties;
20+
using Microsoft.Azure.Management.Internal.Resources.Utilities.Models;
21+
using Microsoft.Rest.Azure.OData;
22+
using Microsoft.WindowsAzure.Commands.Utilities.Common;
23+
using Newtonsoft.Json;
24+
using System;
25+
using System.Collections.Generic;
26+
using System.Linq;
27+
using System.Management.Automation;
28+
using ServiceClientModel = Microsoft.Azure.Management.RecoveryServices.Backup.Models;
29+
30+
31+
namespace Microsoft.Azure.Commands.RecoveryServices.Backup.Cmdlets
32+
{
33+
/// <summary>
34+
/// Reconfigure protection for an item protected by the recovery services vault.
35+
/// Combines stop protection, unregister container, and configure backup steps.
36+
/// </summary>
37+
[Cmdlet("Redo", ResourceManager.Common.AzureRMConstants.AzureRMPrefix + "RecoveryServicesBackupProtection", SupportsShouldProcess = true), OutputType(typeof(JobBase))]
38+
public class RedoAzureRmRecoveryServicesBackupProtection : RSBackupVaultCmdletBase
39+
{
40+
[Parameter(Position = 1, Mandatory = true, HelpMessage = ParamHelpMsgs.Item.ProtectedItem, ValueFromPipeline = true)]
41+
[ValidateNotNullOrEmpty]
42+
public ItemBase Item { get; set; }
43+
44+
[Parameter(Position = 2, Mandatory = true, HelpMessage = "Target Recovery Services vault ID where the item will be reconfigured")]
45+
[ValidateNotNullOrEmpty]
46+
public string TargetVaultId { get; set; }
47+
48+
[Parameter(Position = 3, Mandatory = true, HelpMessage = "Backup policy to be applied in the target vault")]
49+
[ValidateNotNullOrEmpty]
50+
public PolicyBase TargetPolicy { get; set; }
51+
52+
[Parameter(Position = 4, Mandatory = false, HelpMessage = ParamHelpMsgs.Item.SuspendBackupOption)]
53+
public SwitchParameter RetainRecoveryPointsAsPerPolicy { get; set; }
54+
55+
[Parameter(Mandatory = false, HelpMessage = ParamHelpMsgs.ResourceGuard.AuxiliaryAccessToken, ValueFromPipeline = false)]
56+
[ValidateNotNullOrEmpty]
57+
public System.Security.SecureString SecureToken;
58+
59+
[Parameter(Mandatory = false, HelpMessage = ParamHelpMsgs.Item.ForceOption)]
60+
public SwitchParameter Force { get; set; }
61+
62+
public override void ExecuteCmdlet()
63+
{
64+
ExecutionBlock(() =>
65+
{
66+
base.ExecuteCmdlet();
67+
PsBackupProviderManager providerManager;
68+
JobBase jobObj = null;
69+
70+
// chck with nandini, source vault is default and can we name targetvault ?
71+
ResourceIdentifier resourceIdentifier = new ResourceIdentifier(VaultId);
72+
string vaultName = resourceIdentifier.ResourceName;
73+
string resourceGroupName = resourceIdentifier.ResourceGroupName;
74+
75+
// Step 1: Stop protection
76+
ConfirmAction(
77+
Force.IsPresent,
78+
string.Format(Resources.DisableProtectionWarning, Item.Name),
79+
Resources.DisableProtectionMessage,
80+
Item.Name, () =>
81+
{
82+
string plainToken = HelperUtils.GetPlainToken(null, SecureToken);
83+
84+
providerManager =
85+
new PsBackupProviderManager(new Dictionary<System.Enum, object>()
86+
{
87+
{ VaultParams.VaultName, vaultName },
88+
{ VaultParams.ResourceGroupName, resourceGroupName },
89+
{ ItemParams.Item, Item },
90+
{ ResourceGuardParams.Token, plainToken },
91+
}, ServiceClientAdapter);
92+
93+
IPsBackupProvider psBackupProvider =
94+
providerManager.GetProviderInstance(Item.WorkloadType, Item.BackupManagementType);
95+
96+
if (RetainRecoveryPointsAsPerPolicy.IsPresent)
97+
{
98+
var itemResponse = psBackupProvider.SuspendBackup();
99+
Logger.Instance.WriteDebug("Suspend backup response " + JsonConvert.SerializeObject(itemResponse));
100+
jobObj = HandleCreatedJob(
101+
itemResponse,
102+
Resources.DisableProtectionOperation,
103+
vaultName: vaultName,
104+
resourceGroupName: resourceGroupName,
105+
returnJobObject: true);
106+
}
107+
else
108+
{
109+
var itemResponse = psBackupProvider.DisableProtection();
110+
Logger.Instance.WriteDebug("Stop protection with retain data forever response " + JsonConvert.SerializeObject(itemResponse));
111+
112+
jobObj = HandleCreatedJob(
113+
itemResponse,
114+
Resources.DisableProtectionOperation,
115+
vaultName: vaultName,
116+
resourceGroupName: resourceGroupName,
117+
returnJobObject: true);
118+
}
119+
120+
// Wait for job completion and ensure it succeeded
121+
CmdletHelper.EnsureJobCompletedOrThrow(jobObj, vaultName, resourceGroupName, "disable protection", this);
122+
WriteVerbose("Disabled protection successfully");
123+
}
124+
);
125+
126+
// Parse target vault information
127+
ResourceIdentifier targetResourceIdentifier = new ResourceIdentifier(TargetVaultId);
128+
string targetVaultName = targetResourceIdentifier.ResourceName;
129+
string targetResourceGroupName = targetResourceIdentifier.ResourceGroupName;
130+
131+
// Step 2: Unregister/register container (only for supported workloads)
132+
ProtectableItemBase protectableItem = null;
133+
if (CmdletHelper.IsContainerUnregistrationRequired(Item.ContainerType, Item.BackupManagementType))
134+
{
135+
var unregisterJobObj = CmdletHelper.UnregisterContainer(Item, vaultName, resourceGroupName, ServiceClientAdapter, this);
136+
CmdletHelper.EnsureJobCompletedOrThrow(unregisterJobObj, vaultName, resourceGroupName, "container unregistration", this);
137+
138+
// After registration, trigger inquiry if needed to discover protectable items
139+
if (Item.BackupManagementType == BackupManagementType.AzureWorkload)
140+
{
141+
// Register container in target vault using provider pattern
142+
WriteVerbose("Registering container in target vault...");
143+
CmdletHelper.RegisterContainerInTargetVault(Item, targetVaultName, targetResourceGroupName, ServiceClientAdapter);
144+
145+
WriteVerbose($"Triggering inquiry to discover {Item.WorkloadType} protectable items...");
146+
protectableItem = CmdletHelper.TriggerInquiryAndGetProtectableItem(Item, targetVaultName, targetResourceGroupName, ServiceClientAdapter);
147+
}
148+
}
149+
150+
// Step 3: Configure backup in target vault
151+
WriteVerbose("Configuring backup in target vault now");
152+
// chck : switch context to the target vault's subscription
153+
154+
// Create provider manager for target vault with workload-specific parameters
155+
Dictionary<Enum, object> targetProviderParams = new Dictionary<Enum, object>()
156+
{
157+
{ VaultParams.VaultName, targetVaultName },
158+
{ VaultParams.ResourceGroupName, targetResourceGroupName },
159+
{ ItemParams.Policy, TargetPolicy },
160+
{ ResourceGuardParams.IsMUAOperation, false }
161+
};
162+
163+
// Add workload-specific parameters based on item type
164+
if (Item.WorkloadType == WorkloadType.AzureVM)
165+
{
166+
// For VM: extract VM name and resource group from VirtualMachineId
167+
AzureVmItem vmItem = (AzureVmItem)Item;
168+
string vmName = BackupUtils.ExtractVmNameFromVmId(vmItem.VirtualMachineId); // chck if we should use sourceresourceid here
169+
170+
Logger.Instance.WriteDebug($"Reconfiguring Azure VM protection - SourceResourceId: {vmItem.SourceResourceId}, VirtualMachineId: {vmItem.VirtualMachineId}");
171+
172+
string vmResourceGroupName = BackupUtils.ExtractVmResourceGroupFromVmId(vmItem.VirtualMachineId);
173+
174+
targetProviderParams.Add(ItemParams.ItemName, vmName);
175+
targetProviderParams.Add(ItemParams.AzureVMResourceGroupName, vmResourceGroupName);
176+
targetProviderParams.Add(ItemParams.ParameterSetName, "AzureVMComputeEnableProtection");
177+
if ((bool)vmItem.IsInclusionList) {
178+
targetProviderParams.Add(ItemParams.InclusionDisksList, vmItem.DiskLunList);
179+
}
180+
else targetProviderParams.Add(ItemParams.ExclusionDisksList, vmItem.DiskLunList);
181+
182+
// chck handling for ItemParams.AzureVMCloudServiceName, { ItemParams.ResetExclusionSettings, ResetExclusionSettings },
183+
//{ ItemParams.ExcludeAllDataDisks, ExcludeAllDataDisks.IsPresent },
184+
//{ ResourceGuardParams.Token, plainToken },
185+
//{ ResourceGuardParams.IsMUAOperation, isMUAOperation },
186+
}
187+
else if (Item.WorkloadType == WorkloadType.MSSQL )
188+
{
189+
// For AzureWorkload: need to get protectable item
190+
targetProviderParams.Add(ItemParams.ProtectableItem, protectableItem);
191+
targetProviderParams.Add(ItemParams.ParameterSetName, "AzureWorkloadParameterSet");
192+
}
193+
else if (Item.WorkloadType == WorkloadType.AzureFiles)
194+
{
195+
// For AzureFiles: extract file share name and storage account name
196+
AzureFileShareItem afsItem = (AzureFileShareItem)Item;
197+
string fileShareName = afsItem.FriendlyName; // chck : do we need name here?
198+
string storageAccountName = BackupUtils.GetStorageAccountNameFromContainerName(afsItem.ContainerName);
199+
200+
targetProviderParams.Add(ItemParams.ItemName, fileShareName);
201+
targetProviderParams.Add(ItemParams.StorageAccountName, storageAccountName);
202+
targetProviderParams.Add(ItemParams.ParameterSetName, "AzureFileShareParameterSet");
203+
}
204+
205+
PsBackupProviderManager targetProviderManager = new PsBackupProviderManager(targetProviderParams, ServiceClientAdapter);
206+
207+
WriteVerbose("Initialized provider manager for target vault");
208+
209+
IPsBackupProvider targetPsBackupProvider = targetProviderManager.GetProviderInstance(Item.WorkloadType, Item.BackupManagementType);
210+
211+
var enableResponse = targetPsBackupProvider.EnableProtection();
212+
213+
WriteVerbose("Enabled protection successfully in target vault, tracking the job");
214+
215+
// Track Response and display job details
216+
var enableProtectionJob = HandleCreatedJob(
217+
enableResponse,
218+
Resources.EnableProtectionOperation,
219+
vaultName: targetVaultName,
220+
resourceGroupName: targetResourceGroupName, returnJobObject: true);
221+
222+
enableProtectionJob = CmdletHelper.EnsureJobCompletedOrThrow(enableProtectionJob, targetVaultName, targetResourceGroupName, "enable protection", this);
223+
WriteObject(enableProtectionJob);
224+
WriteVerbose("Reconfigure backup protection operation completed successfully.");
225+
226+
}, ShouldProcess(Item.Name, "Reconfigure"));
227+
}
228+
}
229+
}

src/RecoveryServices/RecoveryServices.Backup/Helpers/BackupUtils.cs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@
1212
// limitations under the License.
1313
// ----------------------------------------------------------------------------------
1414

15+
using Microsoft.Azure.Commands.RecoveryServices.Backup.Cmdlets.Models;
1516
using Microsoft.Azure.Commands.RecoveryServices.Backup.Cmdlets.ServiceClientAdapterNS;
1617
using Microsoft.Azure.Management.RecoveryServices.Backup.Models;
1718
using Microsoft.Rest.Azure.OData;
19+
using Microsoft.WindowsAzure.Commands.Utilities.Common;
20+
using System;
1821
using System.Collections.Generic;
1922

2023
namespace Microsoft.Azure.Commands.RecoveryServices.Backup.Cmdlets
@@ -148,7 +151,58 @@ public static List<ProtectedItemResource> GetProtectedItems(
148151
}
149152

150153
return containersCount;
151-
}
154+
}
155+
156+
// Helper method to check if a job is still running
157+
public static bool IsJobInProgress(JobBase job)
158+
{
159+
return string.Equals(job.Status, "InProgress", StringComparison.OrdinalIgnoreCase)
160+
|| string.Equals(job.Status, "Cancelling", StringComparison.OrdinalIgnoreCase);
161+
}
162+
163+
/// <summary>
164+
/// Extracts VM name from VM ID
165+
/// </summary>
166+
public static string ExtractVmNameFromVmId(string vmId)
167+
{
168+
// Expected format: /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Compute/virtualMachines/{vmName}
169+
var parts = vmId.Split('/');
170+
var vmIndex = Array.IndexOf(parts, "virtualMachines");
171+
if (vmIndex >= 0 && vmIndex + 1 < parts.Length)
172+
{
173+
return parts[vmIndex + 1];
174+
}
175+
throw new ArgumentException($"Invalid VM ID format: {vmId}");
176+
}
177+
178+
/// <summary>
179+
/// Extracts VM resource group from VM ID
180+
/// </summary>
181+
public static string ExtractVmResourceGroupFromVmId(string vmId)
182+
{
183+
// Expected format: /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Compute/virtualMachines/{vmName}
184+
var parts = vmId.Split('/');
185+
var rgIndex = Array.IndexOf(parts, "resourceGroups");
186+
if (rgIndex >= 0 && rgIndex + 1 < parts.Length)
187+
{
188+
return parts[rgIndex + 1];
189+
}
190+
throw new ArgumentException($"Invalid VM ID format: {vmId}");
191+
}
192+
193+
/// <summary>
194+
/// Extracts storage account name from container name
195+
/// </summary>
196+
public static string GetStorageAccountNameFromContainerName(string containerName)
197+
{
198+
if (containerName.StartsWith("StorageContainer;"))
199+
{
200+
var parts = containerName.Split(';');
201+
return parts[parts.Length - 1];
202+
}
203+
return null;
204+
}
205+
152206
}
153207
}
154208

0 commit comments

Comments
 (0)