Trabaja con operaciones de larga duración

Algunos métodos de la API de Google Cloud tardan mucho tiempo en completarse. En lugar de bloquear hasta que finalice el trabajo, estos métodos devuelven un recurso Operation que representa una operación en curso para que tu aplicación pueda sondear su estado y resultado final.

Las bibliotecas cliente de Cloud para Rust proporcionan ayudantes para trabajar con estas operaciones de larga duración (LRO). En este documento, se muestra cómo iniciar una LRO, realizar un sondeo automático hasta que se complete, inspeccionar las actualizaciones de progreso intermedias y realizar un sondeo manual.

Antes de comenzar

En este documento, se usa Cloud Storage para mantener los ejemplos de código concretos, aunque los mismos conceptos se aplican a cualquier servicio de Google Cloud que use LRO.

Antes de ejecutar el código de este documento, completa los siguientes requisitos previos:

  • Crea o selecciona un proyecto deGoogle Cloud con la facturación habilitada.
  • Sigue las instrucciones de Comienza a usar Rust para configurar tu entorno de desarrollo y tus credenciales.
  • Crea un bucket de Cloud Storage con el espacio de nombres jerárquico habilitado, crea una carpeta de origen para cambiarle el nombre y otorga a tu principal un rol de Identity and Access Management (IAM) con permiso para administrar carpetas, como Administrador de almacenamiento (roles/storage.admin).

Dependencias

Agrega la biblioteca cliente y los crates de LRO a tu archivo Cargo.toml:

cargo add google-cloud-storage google-cloud-lro google-cloud-longrunning anyhow

Agrega tokio para la ejecución asíncrona y los temporizadores:

cargo add tokio --features full

Inicia una operación de larga duración

En los ejemplos de este documento, se llama a la operación cambiar nombre de carpeta de Cloud Storage, que devuelve una LRO. Para iniciar una LRO directamente y obtener el recurso Operation inicial, completa los siguientes pasos:

  1. Agrega declaraciones use para acortar las rutas de acceso de tipos:

    use anyhow::anyhow;
    use google_cloud_longrunning as longrunning;
    use google_cloud_storage::client::StorageControl;

  2. Define una función async que acepte el ID de bucket, el ID de la carpeta de origen y el ID de la carpeta de destino:

    pub async fn manual(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {

  3. Inicializa el cliente de StorageControl:

    let client = StorageControl::builder().build().await?;

  4. Crea el compilador de solicitudes rename_folder:

    let operation = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)

  5. Envía la solicitud y espera el recurso Operation inicial, que representa la operación en segundo plano:

    .send()
    .await?;

Después de iniciar la operación, espera a que finalice para recuperar el resultado final.

Sondea automáticamente una operación de larga duración

En la mayoría de los casos de uso, utiliza el método until_done del atributo Poller en lugar de llamar a send directamente. El sondeo inicia la LRO, sondea según las políticas de retirada y error del cliente, y se resuelve en la respuesta final:

  1. Incluye el atributo Poller en el alcance:

    use google_cloud_lro::Poller;

  2. Inicializa un cliente StorageControl y configura el compilador de solicitudes rename_folder:

    let response = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)

  3. Llama a poller y until_done en el compilador de solicitudes para esperar a que se complete y, luego, imprime el resultado:

        .poller()
        .until_done()
        .await?;
    
    println!("LRO completed, response={response:?}");

Código completo de sondeo automático

En la siguiente función, se muestra el ejemplo completo de sondeo automático:

pub async fn automatic(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_lro::Poller;

    let client = StorageControl::builder().build().await?;

    let response = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .poller()
        .until_done()
        .await?;

    println!("LRO completed, response={response:?}");

    Ok(())
}

Sondea una operación de larga duración con resultados intermedios

El método until_done devuelve solo el resultado final y omite las actualizaciones de metadatos intermedias. Si tu aplicación necesita actualizaciones de progreso mientras se ejecuta la LRO, obtén un poller con poller y llama a poll en un bucle:

  1. Importa Poller y PollingResult:

    use google_cloud_lro::{Poller, PollingResult};

  2. Convierte el compilador de solicitudes en un poller con poller:

    .poller();

  3. Llama a poller.poll().await en un bucle para procesar los estados InProgress, PollingError y Completed:

    while let Some(p) = poller.poll().await {
        match p {
            PollingResult::Completed(r) => {
                println!("LRO completed, response={r:?}");
            }
            PollingResult::InProgress(m) => {
                println!("LRO in progress, metadata={m:?}");
            }
            PollingResult::PollingError(e) => {
                println!("Transient error polling the LRO: {e}");
            }
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
    }

Cuando llamas a poll directamente, tu bucle controla la demora entre los intentos de sondeo. Elige un retraso que se adapte a la duración prevista de la operación.

Código completo para el sondeo con resultados intermedios

En la siguiente función, se muestra el bucle poll completo:

pub async fn polling(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_lro::{Poller, PollingResult};

    let client = StorageControl::builder().build().await?;

    let mut poller = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .poller();

    while let Some(p) = poller.poll().await {
        match p {
            PollingResult::Completed(r) => {
                println!("LRO completed, response={r:?}");
            }
            PollingResult::InProgress(m) => {
                println!("LRO in progress, metadata={m:?}");
            }
            PollingResult::PollingError(e) => {
                println!("Transient error polling the LRO: {e}");
            }
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
    }

    Ok(())
}

Sondea manualmente una operación de larga duración

Aunque el atributo Poller controla casi todos los casos de uso, también puedes sondear un Operation de forma manual con get_operation:

  1. Después de iniciar la LRO con send, ingresa un bucle y verifica el campo done:

    let response: anyhow::Result<Folder> = loop {
        if operation.done {

  2. Inspecciona operation.result. El campo result es un Option porque algunas operaciones, como las eliminaciones, finalizan sin devolver una carga útil. Para rename_folder, el servicio devuelve un recurso Folder:

    match &operation.result {
        None => {
            break Err(anyhow!("missing result for finished operation"));
        }

  3. Verifica si la operación finalizó con un error. La variante de error contiene un mensaje Status que el siguiente ejemplo formatea en un error o que puedes convertir en un error del cliente con Error::service:

    Some(r) => {
        break match r {
            longrunning::model::operation::Result::Error(s) => {
                Err(anyhow!("operation completed with error {s:?}"))
            }

  4. Si la operación se realizó correctamente, desempaca la carga útil de Any en el tipo de respuesta Folder esperado:

    longrunning::model::operation::Result::Response(any) => {
        let response = any.to_msg::<Folder>()?;
        Ok(response)
    }

  5. Agrega un brazo comodín porque las enumeraciones en las bibliotecas cliente de Cloud para Rust están marcadas como #[non_exhaustive]:

    _ => Err(anyhow!("unexpected result branch {r:?}")),

  6. Opcional: Mientras operation.done sea false, desempaca operation.metadata para inspeccionar las actualizaciones de progreso:

    if let Some(any) = &operation.metadata {
        let metadata = any.to_msg::<RenameFolderMetadata>()?;
        println!("LRO in progress, metadata={metadata:?}");
    }

  7. Espera antes de volver a realizar el sondeo. En este ejemplo, se suspende durante 500 ms fijos para abreviar; en el código de producción, usa la retirada exponencial truncada:

    tokio::time::sleep(std::time::Duration::from_millis(500)).await;

  8. Llama a get_operation con operation.name para actualizar el estado de la operación:

    if let Ok(attempt) = client
        .get_operation()
        .set_name(&operation.name)
        .send()
        .await
    {
        operation = attempt;
    }

Código completo de sondeo manual

La siguiente función combina todos los pasos de sondeo manual:

pub async fn manual(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_storage::model::Folder;
    use google_cloud_storage::model::RenameFolderMetadata;

    let client = StorageControl::builder().build().await?;

    let operation = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .send()
        .await?;
    println!("LRO started, response={operation:?}");

    let mut operation = operation;
    let response: anyhow::Result<Folder> = loop {
        if operation.done {
            match &operation.result {
                None => {
                    break Err(anyhow!("missing result for finished operation"));
                }
                Some(r) => {
                    break match r {
                        longrunning::model::operation::Result::Error(s) => {
                            Err(anyhow!("operation completed with error {s:?}"))
                        }
                        longrunning::model::operation::Result::Response(any) => {
                            let response = any.to_msg::<Folder>()?;
                            Ok(response)
                        }
                        _ => Err(anyhow!("unexpected result branch {r:?}")),
                    };
                }
            }
        }
        if let Some(any) = &operation.metadata {
            let metadata = any.to_msg::<RenameFolderMetadata>()?;
            println!("LRO in progress, metadata={metadata:?}");
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
        if let Ok(attempt) = client
            .get_operation()
            .set_name(&operation.name)
            .send()
            .await
        {
            operation = attempt;
        }
    };
    println!("LRO completed, response={response:?}");

    Ok(())
}