Importación sencilla de archivos desde OpenStack Swift en Java
OpenStack Swift es un sistema de almacenamiento de objetos altamente escalable que te permite almacenar y recuperar archivos de forma eficiente en la nube. En esta guía exploramos cómo importar archivos desde OpenStack Swift a tus aplicaciones Java con librerías de código abierto como OpenStack4j, y cubrimos las mejores prácticas para una integración fluida con el almacenamiento en la nube.
Configuración de tu entorno Java
Antes de empezar, asegúrate de contar con los siguientes requisitos previos:
- Java Development Kit (JDK) 11 o posterior
- Maven o Gradle para la gestión de dependencias
- Credenciales de OpenStack Swift (URL de autenticación, nombre de usuario, contraseña y nombre de dominio/proyecto)
Agrega la dependencia de OpenStack4j a tu proyecto. Para Maven, incluye lo siguiente en tu pom.xml:
<dependency>
<groupId>com.github.openstack4j.core</groupId>
<artifactId>openstack4j</artifactId>
<version>3.12</version>
</dependency>
Para Gradle, agrega lo siguiente a tu build.gradle:
implementation 'com.github.openstack4j.core:openstack4j:3.12'
Conexión con OpenStack Swift
Establece una conexión con tu instancia de OpenStack Swift mediante OpenStack4j. El siguiente ejemplo muestra la autenticación con alcance de proyecto. Asegúrate de proporcionar tanto un dominio como un nombre de proyecto para evitar problemas de autorización:
import org.openstack4j.api.OSClient.OSClientV3;
import org.openstack4j.model.common.Identifier;
import org.openstack4j.openstack.OSFactory;
public class SwiftConnector {
private final OSClientV3 os;
public SwiftConnector(String authUrl, String username, String password,
String domainName, String projectName) {
this.os = OSFactory.builderV3()
.endpoint(authUrl)
.credentials(username, password, Identifier.byName(domainName))
.scopeToProject(Identifier.byName(projectName), Identifier.byName(domainName))
.authenticate();
}
public OSClientV3 getClient() {
return os;
}
}
Importación de archivos desde Swift
El siguiente ejemplo muestra cómo importar un archivo de un contenedor de Swift a tu aplicación
Java. La descarga se hace en un archivo temporal junto al destino y ese archivo solo se mueve a su
ubicación definitiva una vez que la transferencia ha finalizado y el número de bytes coincide con el
que informa Swift. Escribir directamente en localPath
dejaría un archivo truncado con apariencia de completo cada vez que se corte la conexión.
import org.openstack4j.api.OSClient.OSClientV3;
import org.openstack4j.model.storage.object.SwiftObject;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
public class SwiftFileImporter {
private final OSClientV3 os;
public SwiftFileImporter(OSClientV3 os) {
this.os = os;
}
public void importFile(String containerName, String objectName, String localPath)
throws IOException {
SwiftObject obj = os.objectStorage()
.objects()
.get(containerName, objectName);
if (obj == null) {
// NoSuchFileException's single-argument form takes a file, not a sentence.
throw new NoSuchFileException(
containerName + "/" + objectName, null, "Object not found");
}
Path destination = Paths.get(localPath).toAbsolutePath();
// The temporary file has to share a directory with the destination, otherwise the move
// below crosses filesystems and stops being atomic.
Path partial = Files.createTempFile(destination.getParent(), ".swift-import-", ".part");
try {
long written;
try (InputStream is = obj.download().getInputStream()) {
written = Files.copy(is, partial, StandardCopyOption.REPLACE_EXISTING);
}
// getSizeInBytes() is a primitive long: it is 0 both for an empty object and for a
// listing that carried no size, so only a positive value is worth checking against.
long expected = obj.getSizeInBytes();
if (expected > 0 && written != expected) {
throw new IOException(
"Truncated download of " + objectName + ": expected " + expected
+ " bytes but wrote " + written);
}
Files.move(partial, destination,
StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
} finally {
Files.deleteIfExists(partial);
}
}
}
Manejo eficiente de archivos grandes
Para archivos grandes, implementa un streaming eficiente con seguimiento del progreso para garantizar operaciones fluidas sin agotar la memoria:
import org.openstack4j.api.OSClient.OSClientV3;
import org.openstack4j.model.storage.object.SwiftObject;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
public class LargeFileImporter {
private static final int BUFFER_SIZE = 1024 * 1024; // 1MB buffer
private static final long PROGRESS_INTERVAL = 32L * BUFFER_SIZE;
private final OSClientV3 os;
public LargeFileImporter(OSClientV3 os) {
this.os = os;
}
public void importLargeFile(String containerName, String objectName, String localPath)
throws IOException {
SwiftObject obj = os.objectStorage()
.objects()
.get(containerName, objectName);
if (obj == null) {
// NoSuchFileException's single-argument form takes a file, not a sentence.
throw new NoSuchFileException(
containerName + "/" + objectName, null, "Object not found");
}
long contentLength = obj.getSizeInBytes();
Path destination = Paths.get(localPath).toAbsolutePath();
Path partial = Files.createTempFile(destination.getParent(), ".swift-import-", ".part");
try {
long totalBytesRead = 0;
try (InputStream is = obj.download().getInputStream();
OutputStream out = Files.newOutputStream(partial)) {
byte[] buffer = new byte[BUFFER_SIZE];
long nextReport = PROGRESS_INTERVAL;
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
// A size of 0 means Swift reported none, and a per-buffer report on a
// multi-GB object is thousands of log lines.
if (contentLength > 0 && totalBytesRead >= nextReport) {
nextReport += PROGRESS_INTERVAL;
System.out.printf("Download progress: %.2f%%%n",
(double) totalBytesRead / contentLength * 100);
}
}
}
if (contentLength > 0 && totalBytesRead != contentLength) {
throw new IOException(
"Truncated download of " + objectName + ": expected " + contentLength
+ " bytes but wrote " + totalBytesRead);
}
Files.move(partial, destination,
StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
} finally {
Files.deleteIfExists(partial);
}
}
}
Implementación del manejo de errores y los reintentos
Un manejo de errores robusto es esencial, sobre todo cuando surgen problemas de red o errores transitorios. El siguiente ejemplo reintenta la descarga hasta tres veces antes de fallar definitivamente. Un objeto inexistente no es una condición transitoria, así que falla de inmediato en lugar de gastar tres intentos y seis segundos en un nombre que nunca se resolverá. Los fallos de conexión y las respuestas HTTP reintentables son excepciones no comprobadas del SDK, distintas de los fallos de E/S locales. Los errores de autenticación y otros errores HTTP permanentes no se reintentan:
import org.openstack4j.api.OSClient.OSClientV3;
import org.openstack4j.api.exceptions.ConnectionException;
import org.openstack4j.api.exceptions.ResponseException;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.util.Set;
public class RetryableSwiftImporter {
private static final int MAX_RETRIES = 3;
private static final long RETRY_DELAY_MS = 2000;
private static final Set<Integer> RETRYABLE_STATUS = Set.of(408, 429, 500, 502, 503, 504);
private final SwiftFileImporter importer;
public RetryableSwiftImporter(OSClientV3 os) {
this.importer = new SwiftFileImporter(os);
}
public void importWithRetry(String containerName, String objectName, String localPath)
throws IOException, InterruptedException {
Exception lastException = null;
for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
// importFile leaves the destination untouched until a whole, size-checked copy has
// landed, so retrying can never observe a half-written file.
importer.importFile(containerName, objectName, localPath);
return;
} catch (NoSuchFileException e) {
throw e;
} catch (ResponseException e) {
if (!(e instanceof ConnectionException) && !RETRYABLE_STATUS.contains(e.getStatus())) {
throw e;
}
lastException = e;
} catch (IOException e) {
lastException = e;
}
if (attempt < MAX_RETRIES) {
Thread.sleep(RETRY_DELAY_MS * attempt);
}
}
throw new IOException("Failed to import after " + MAX_RETRIES + " attempts", lastException);
}
}
Consideraciones sobre el manejo de errores
Cuando trabajes con OpenStack Swift, ten en cuenta los siguientes escenarios de error comunes y adapta tu manejo de errores en consecuencia:
- Contenedor no encontrado: asegúrate de que el contenedor especificado exista en tu instancia de Swift.
- Objeto no encontrado: verifica el nombre del objeto y su ruta.
- Fallos de autenticación: revisa con cuidado tus credenciales, el nombre de dominio y el nombre de proyecto para lograr un alcance correcto.
- Cuota excedida: monitorea tus límites de almacenamiento y tu uso para no superar las cuotas asignadas.
Proporcionar mensajes de error detallados y manejar estos casos de forma explícita puede mejorar notablemente la resolución de problemas y la fiabilidad del sistema.
Consejos para optimizar el rendimiento
Para optimizar tu flujo de trabajo de importación de archivos con Swift, ten en cuenta las siguientes prácticas:
-
Gestión de conexiones:
- Reutiliza un
OSClientV3dentro del hilo que lo autenticó.SwiftObject.download()llama aApis.get(...), que resuelve la sesión que OpenStack4j guarda en unThreadLocal, por lo que pasar el cliente o unSwiftObjecta un hilo de trabajo deja a ese hilo sin ninguna sesión - Para importaciones concurrentes, autentica una vez por cada hilo de trabajo, o vuelve a derivar un cliente en ese hilo a partir del token que ya tienes, en lugar de agrupar un único cliente compartido
- Establece tiempos de espera adecuados según tu caso de uso
- Reutiliza un
-
Optimización del tamaño del búfer:
- Usa búferes más grandes (1 MB o más) para transferir archivos grandes
- Ajusta el tamaño del búfer según la memoria disponible y el tamaño de los archivos
- Monitorea el uso de memoria durante las transferencias
-
Gestión de recursos:
- Usa siempre try-with-resources para liberar correctamente los streams y las conexiones
- Descarga a un archivo temporal en el directorio de destino y muévelo a su ubicación definitiva solo después de que pase la comprobación de tamaño, para que una transferencia fallida nunca deje un archivo parcial con apariencia válida
- Registra las excepciones y libera los recursos para evitar fugas de memoria
-
Mejores prácticas de seguridad:
- Almacena las credenciales de forma segura mediante variables de entorno o bóvedas seguras
- Usa endpoints HTTPS en todas las conexiones para proteger los datos en tránsito
- Implementa controles de acceso adecuados y registro de auditoría
-
Operaciones masivas con archivos:
- Cuando importes numerosos archivos, considera usar las operaciones masivas nativas de Swift si están disponibles, o procesa los archivos de forma secuencial para gestionar la memoria con mayor eficacia
Conclusión
Al aprovechar OpenStack4j, los desarrolladores de Java pueden integrar OpenStack Swift de forma eficiente en sus aplicaciones para lograr una importación de archivos fluida y una gestión ágil del almacenamiento en la nube. Los ejemplos y las mejores prácticas que se presentan aquí son un punto de partida sólido para crear soluciones eficientes de importación de archivos.
Para conocer capacidades más avanzadas de manejo de archivos y flujos de trabajo de procesamiento automatizado, considera explorar Transloadit.
