Azure / azure-sdk-for-python

This repository is for active development of the Azure SDK for Python. For consumers of the SDK we recommend visiting our public developer docs at https://learn.microsoft.com/python/azure/ or our versioned developer docs at https://azure.github.io/azure-sdk-for-python.
MIT License
4.56k stars 2.78k forks source link

Backup Client Restores .result() is always Null, not able to retrieve JobID #37664

Open MartinZvelebil opened 1 day ago

MartinZvelebil commented 1 day ago

Describe the bug There are multiple things that are wrong regarding the backup_client.restores.begin_trigger. 1) For the IaasVMRestoreRequest, there is recover_point_id, but actually it is .name and not .id - missconception 2) When restore is triggered, there is no way how to get any particular information about the restore (for example JobID). I need to get information what was restored (disk objects), so atleast the JobID is needed. The only working method is .status() but for other atributes in the response body methods are totally missing. I would assume that .result() will help me with that, but that is also problem since it always returns Null 3) When the restore ends up in Succesfull state, the response does not even contain body anymore, since the token used in the request URL is already dead. Which means that response is with code 204.

To Reproduce

def az_restore_data_disks(backup_client: RecoveryServicesBackupClient, restore_point: object, rsv: object, container_uri: str, protected_item_uri: str, storage_account_id: str, restored_vm_rg_id: str, restored_vm_name: str, bckp_item_source_resource_id: str):
    """
    Restore the data disks of the VM - separate process, will be mounted to the VM later
    :param backup_client: The Recovery Services Backup client object.
    :param restore_point: The restore point object used for restoring disks as source - retrieved from az_retrieve_latest_recovery_point.
    :param rsv: The Recovery Services Vault object - retrieve from az_retrieve_recovery_services_point.
    :param container_uri: The container name of the backup item (Prefix as: learn.microsoft.com/en-us/rest/api/backup/recovery-points/list).
    :param protected_item_uri: The protected item URI of the backup item.
    :param storage_account_id: The ID of the storage account, where the restore should be performed.
    :param restored_vm_rg_id: The ID of the resource group - VM from which the restore is located.
    :param restored_vm_name: The name of the restored VM - questdb.
    :param bckp_item_source_resource_id: The source backup item Resource ID.
    Manual reference to this task: https://learn.microsoft.com/en-us/azure/backup/backup-azure-arm-restore-vms#restore-disks
    NOTE: Yes really - the restore point is called ID in the API, but it is actually the NAME of the restore point.
    """
    myprint(f"Restoring data disks for VM {restored_vm_name} within resource group {restored_vm_rg_id}")    # Define the restore request
    myprint("All variables to check:")
    myprint(f"  Vault name: {rsv.name}")
    myprint(f"  Storage account ID: {storage_account_id}")
    myprint(f"  Source resource ID: {bckp_item_source_resource_id}")
    myprint(f"  Location/region: {rsv.location}")
    myprint(f"  Resource group name: {rsv.rg_name}")
    myprint(f"  Container name: {container_uri}")
    myprint(f"  Protected item name: {protected_item_uri}")
    myprint(f"  Recovery point NAME (Although called ID in params): {restore_point.name}")
    myprint(f"  Target VM resource group ID: {restored_vm_rg_id}")

    # Create the restore request
    restore_request = IaasVMRestoreRequest(
        create_new_cloud_service=False,
        encryption_details={
            "encryption_enabled": False,
        },
        original_storage_account_option=False,
        recovery_point_id=restore_point.name,
        recovery_type='RestoreDisks',
        region=rsv.location,
        source_resource_id=bckp_item_source_resource_id,
        storage_account_id=storage_account_id,
        target_resource_group_id=restored_vm_rg_id,
    )
    # Wrapping it into restore request resource object
    restore_request_resource = RestoreRequestResource(properties=restore_request)

    # Trigger the restore request
    restore_operation = backup_client.restores.begin_trigger(
        vault_name=rsv.name,
        resource_group_name=rsv.rg_name,
        fabric_name="Azure",
        container_name=container_uri,
        protected_item_name=protected_item_uri,
        recovery_point_id=restore_point.name,
        parameters=restore_request_resource
    )

    # Wait for the restore operation to finisht
    myprint(f"Restore operation initiated. Polling for completion...")

    # Polling mechanism to wait for the job to complete
    timeout = 3600  # Timeout in seconds (1 hour)
    interval = 30  # Polling interval in seconds
    elapsed_time = 0

    while elapsed_time < timeout:
        if restore_operation.done():
            break
        status = restore_operation.status()
        myprint(f"Current restore job status: {status}")

        time.sleep(interval)
        elapsed_time += interval

    if elapsed_time >= timeout:
        myprint("Timeout reached while waiting for the restore job to complete.")
    else:
        myprint(f"Restore operation completed with status: {status}")
        if status == "Failed":
            myprint("Restore operation failed!")
            sys.exit(1)
        elif status == "Cancelled":
            myprint("Restore operation was cancelled!")
            sys.exit(1)

Expected behavior 1) Should be clear - missnamed 2) I don't think it should respond with Null always to the .result(). It should give us back the response as an object for example (for the response see below). I need to get the disks, that were recovered, so I can further work with them (mount them etc.).

Screenshots From this response, I can get only status with the .status(). So once the restores.begin_trigger is finished I can only tell it is finished, but I cannot work further with the retrieved disks. Properties, name etc. is not reachable.

image

If there is anything I missunderstood I would appreciate any help. Thank you and have a great day!

MartinZvelebil commented 1 day ago

Forgot to add to the code - if anywhere I put the .result() - directly on the restore_operation or later on - is always null + the missing methods for retrieving other atributes cannot really be described anyway. From other examples, it seems, that it should return everything needed, for example here.

xiangyan99 commented 1 day ago

Thanks for the feedback, we’ll investigate asap.