Importer des fichiers depuis OpenStack Swift en Java
Téléchargez un objet OpenStack Swift dans un fichier local avec OpenStack4j. Le programme Java complet ci-dessous accepte les objets vides, vérifie la réponse HTTP et le nombre d’octets téléchargés, et laisse la destination intacte jusqu’à la réussite du téléchargement.
Configurer votre environnement Java
Cet exemple utilise un shell POSIX sous Linux, une locale UTF-8 et un système de fichiers local prenant en charge les déplacements atomiques. La locale détermine la façon dont la JVM décode les noms de fichiers passés en ligne de commande. Installez séparément un JDK 21 maintenu et Apache Maven 3.10. La procédure ci-dessous a été testée avec OpenJDK 21.0.12.1 et Maven 3.10.0 ; Maven 3.9.11 a également passé les tests.
Il vous faut aussi un compte Keystone v3 disposant, dans le périmètre d’un projet, d’un accès à un conteneur Swift et à un objet existants. Mettez ces variables d’environnement à disposition via la configuration des informations d’accès de votre fournisseur ou votre gestionnaire de secrets :
| Variable | Valeur |
|---|---|
OS_AUTH_URL | URL Keystone se terminant par /v3 |
OS_USERNAME, OS_PASSWORD | Votre nom d’utilisateur et votre mot de passe |
OS_USER_DOMAIN_NAME | Domaine auquel appartient l’utilisateur |
OS_PROJECT_NAME, OS_PROJECT_DOMAIN_NAME | Projet et son domaine |
OS_REGION_NAME | Région contenant le point de terminaison public du stockage d’objets |
L’utilisateur et le projet peuvent appartenir à des domaines différents. Utilisez HTTPS pour Keystone et pour le point de terminaison Swift figurant dans son catalogue de services, avec des certificats reconnus comme fiables par la JVM. Ne versionnez pas les mots de passe et ne les insérez pas dans des commandes shell copiées. La procédure décrite ici lit un objet existant ; elle ne crée pas de conteneurs et ne modifie pas les politiques d’accès.
Vérifiez chaque exécutable avant de créer le projet. Collez ce bloc dans votre shell actuel ;
le sous-shell préserve le répertoire de travail et les options de ce shell. La présence d’un
répertoire swift-import fait échouer l’initialisation sans modifier son contenu.
(
cd -P . &&
swift_parent=$PWD &&
while :; do
if [ -e "$swift_parent/.mvn" ] || [ -L "$swift_parent/.mvn" ]; then
printf 'Run setup outside an existing Maven project.\n' >&2
exit 1
fi
[ "$swift_parent" = / ] && break
swift_parent=${swift_parent%/*}
[ -n "$swift_parent" ] || swift_parent=/
done &&
command -v java >/dev/null &&
command -v javac >/dev/null &&
command -v mvn >/dev/null &&
command -v locale >/dev/null &&
[ "$(locale charmap)" = UTF-8 ] &&
mkdir swift-import &&
cd swift-import &&
mkdir -p src/main/java
)
Enregistrez le fichier complet suivant sous swift-import/pom.xml. OpenStack4j 3.12 est
la version publiée sur Maven Central.
Cet artefact inclut le connecteur RESTEasy.
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>swift-import</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.github.openstack4j.core</groupId>
<artifactId>openstack4j</artifactId>
<version>3.12</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.8.1</version>
</plugin>
</plugins>
</build>
</project>
Le garde-fou d’initialisation refuse toute configuration .mvn provenant
d’un répertoire parent avant de créer des fichiers. Maven peut hériter d’options de lancement d’un
projet englobant ; examinez-les avant d’intégrer cet exemple à une application existante. Si la
résolution des dépendances échoue, corrigez les problèmes de réseau ou d’accès aux dépôts et
relancez la commande de compilation ci-dessous. Conservez le code source enregistré et
pom.xml.
Se connecter à OpenStack Swift
Enregistrez ce programme sous swift-import/src/main/java/SwiftImport.java. Il s’authentifie auprès de Keystone,
limite la portée du jeton au projet et au domaine de projet indiqués, puis sélectionne la région
demandée. Il s’agit de choix distincts dans le
modèle d’identité de Keystone.
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import org.openstack4j.api.OSClient.OSClientV3;
import org.openstack4j.api.exceptions.ResponseException;
import org.openstack4j.core.transport.Config;
import org.openstack4j.core.transport.HttpResponse;
import org.openstack4j.model.common.Identifier;
import org.openstack4j.model.storage.object.SwiftObject;
import org.openstack4j.openstack.OSFactory;
public class SwiftImport {
private static String encodeName(String value, boolean object) {
String[] segments = value.split("/", -1);
if (!object && segments.length != 1) {
throw new IllegalArgumentException("A container name cannot contain a slash");
}
for (int index = 0; index < segments.length; index++) {
String segment = segments[index];
if (segment.isEmpty() || segment.equals(".") || segment.equals("..")) {
throw new IllegalArgumentException("Empty and dot path segments are unsupported");
}
segments[index] = URLEncoder.encode(segment, StandardCharsets.UTF_8).replace("+", "%20");
}
return String.join("/", segments);
}
private static String required(String name) {
String value = System.getenv(name);
if (value == null || value.isBlank()) {
throw new IllegalArgumentException("Missing environment variable: " + name);
}
return value;
}
private static long download(OSClientV3 os, String container, String object,
Path destination) throws IOException {
SwiftObject remote = os.objectStorage().objects().get(container, object);
if (remote == null) {
throw new IOException("Object not found");
}
long expected = remote.getSizeInBytes();
Path partial = Files.createTempFile(destination.getParent(), ".swift-import-", ".part");
try {
long written;
// The download API exposes its response without mapping HTTP errors to exceptions.
try (HttpResponse response = remote.download().getHttpResponse()) {
if (response.getStatus() != 200) {
throw new IOException("Download rejected (HTTP " + response.getStatus() + ")");
}
try (InputStream input = response.getInputStream()) {
if (input == null) {
throw new IOException("Missing download stream");
}
written = Files.copy(input, partial, StandardCopyOption.REPLACE_EXISTING);
}
}
if (written != expected) {
throw new IOException("Downloaded byte count does not match object metadata");
}
Files.move(partial, destination,
StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
return written;
} finally {
Files.deleteIfExists(partial);
}
}
public static void main(String[] args) {
if (args.length != 3) {
System.err.println("Usage: SwiftImport CONTAINER OBJECT DESTINATION");
System.exit(2);
}
try {
String container = encodeName(args[0], false);
String object = encodeName(args[1], true);
Config config = Config.newConfig().withConnectionTimeout(10_000);
OSClientV3 os = OSFactory.builderV3()
.endpoint(required("OS_AUTH_URL"))
.credentials(required("OS_USERNAME"), required("OS_PASSWORD"),
Identifier.byName(required("OS_USER_DOMAIN_NAME")))
.scopeToProject(Identifier.byName(required("OS_PROJECT_NAME")),
Identifier.byName(required("OS_PROJECT_DOMAIN_NAME")))
.withConfig(config)
.authenticate()
.useRegion(required("OS_REGION_NAME"));
Path destination = Path.of(args[2]).toAbsolutePath();
long written = download(os, container, object, destination);
System.out.println("Imported " + written + " bytes to " + destination);
} catch (ResponseException error) {
System.err.println("OpenStack request failed (HTTP " + error.getStatus() + ").");
System.exit(1);
} catch (Exception error) {
System.err.println("Import failed (" + error.getClass().getSimpleName()
+ "). Check configuration, object access, and local storage before retrying.");
System.exit(1);
}
}
}
Importer des fichiers depuis Swift
Depuis le répertoire contenant swift-import, collez le bloc suivant. Remplacez
exports et reports/report.bin par votre conteneur existant et le
nom exact de votre objet. La destination locale est relative à swift-import, et
son répertoire parent doit déjà exister.
Fournissez les noms bruts du conteneur et de l’objet en UTF-8, y compris les espaces ou les signes
pour cent littéraux. Le programme encode chaque segment du chemin de l’URL avant d’appeler
OpenStack4j. Les noms de conteneurs ne peuvent pas contenir / ;
dans cet exemple, les chemins d’objets ne doivent comporter aucun segment vide,
. ou ... Les chemins de destination locaux
doivent eux aussi être encodés en UTF-8 valide.
(
cd swift-import &&
mvn -B package dependency:copy-dependencies &&
java -cp 'target/classes:target/dependency/*' SwiftImport \
'exports' 'reports/report.bin' './download.bin'
)
En cas de réussite, le nombre d’octets importés et le chemin absolu de destination sont affichés.
Pour un objet vide, le programme produit un fichier de zéro octet et indique zéro octet importé.
Un échec de la compilation empêche l’exécution de la commande Java, même si une ancienne classe
compilée subsiste dans target/.
Le programme obtient la taille attendue à partir de la réponse HEAD de Swift, exige la réussite d’un GET complet et compare le nombre d’octets copiés même lorsque la taille attendue est nulle. Swift décrit ces opérations dans sa référence de l’API des objets. Gardez l’objet distant inchangé pendant l’importation. Une comparaison des tailles ne peut pas détecter les modifications de contenu à taille égale ; comparez séparément une somme de contrôle fiable fournie par l’éditeur lorsque vous avez besoin de cette vérification.
Une destination existante n’est remplacée que par un téléchargement terminé dont la taille a été vérifiée. Le fichier temporaire est créé dans le même répertoire pour permettre une publication par déplacement atomique. Si le système de fichiers ne peut pas effectuer ce déplacement, l’importation échoue sans se rabattre sur une copie non atomique. En Java, le comportement du remplacement atomique dépend du système de fichiers ; la procédure de remplacement utilisée ici a été testée sous Linux. Utilisez un seul importateur par destination et gardez le contrôle de son répertoire parent.
Gérer efficacement les fichiers volumineux
Le connecteur RESTEasy utilisé par défaut dans OpenStack4j 3.12 met en mémoire tampon l’intégralité de la réponse avant de fournir un flux d’entrée. Choisissez des objets dont la taille laisse une marge suffisante dans la mémoire disponible de la JVM. Augmenter la taille du tampon de copie ou afficher la progression depuis la boucle de copie ne supprime pas cette allocation mémoire.
Mettre en œuvre la gestion des erreurs et des nouvelles tentatives
La commande renvoie un code de sortie non nul en cas d’objet manquant, de lecture refusée, de
transfert incomplet ou d’échec d’écriture locale. Elle effectue une seule tentative au niveau de
l’application et demande un délai d’expiration de connexion de 10 secondes. Aucun délai maximal ne
s’applique au transfert dans son ensemble. Dans cette version de RESTEasy,
withReadTimeout configure l’acquisition d’une connexion dans le pool
plutôt qu’un délai d’expiration de lecture sur socket ; ce paramètre est donc omis ici. Une réponse
bloquée peut rester en attente jusqu’à la fermeture de la connexion. Utilisez un processus supervisé
si votre application a besoin d’une limite de durée.
Après un échec intercepté et signalé par le programme, corrigez sa cause et relancez le même bloc de compilation et
d’exécution. En cas d’échec du téléchargement, le fichier partiel est supprimé et une éventuelle
destination préexistante reste intacte. Un arrêt forcé du processus ou un plantage de la machine
peut laisser un fichier .swift-import-*.part ; supprimez uniquement le fichier abandonné
qui appartient à votre importation arrêtée. Ne lancez pas de nettoyage des fichiers temporaires
tant qu’un autre importateur est actif.
Points à prendre en compte pour la gestion des erreurs
- En cas d’échec d’authentification, vérifiez l’URL Keystone v3, le domaine de l’utilisateur, le domaine du projet et les informations d’accès.
- Si l’objet est introuvable ou si l’accès est refusé, vérifiez son nom exact et les droits de lecture de votre projet. Une authentification réussie ne signifie pas que vous avez l’autorisation de le télécharger.
- En cas d’erreur locale d’E/S, vérifiez le répertoire parent existant de la destination, les droits d’écriture, l’espace disque et la prise en charge des déplacements atomiques. Évitez de supprimer une destination préexistante pour contourner un échec de téléchargement.
Conseils d’optimisation des performances
Exécutez l’authentification et les téléchargements dans le même thread. OpenStack4j stocke la session active dans une valeur locale au thread ; son guide sur les threads explique comment créer un client à partir d’un jeton dans un autre thread. Cette commande, qui porte sur un seul objet, utilise un thread et n’implémente ni pool de threads de travail ni nouvelles tentatives automatiques.
Lors de l’intégration à une application, gardez le téléchargement, la vérification du nombre d’octets
et la publication regroupés. Ne renvoyez le chemin local au code qui l’utilise qu’après la réussite
de download.
