Images personnalisées de grande taille – Téléchargement de fichiers > 5 Go sur le stockage objet Fujitsu K5 (Swift)
2016-10-08
Machine-translated — the English original is authoritative.
Dans un article de blog précédent, j’ai détaillé un processus simple pour télécharger des images personnalisées sur la plateforme IaaS Fujitsu K5. Un défi que j’avais négligé concerne les tailles d’images personnalisées volumineuses, que j’aborderai ici.
Contexte : Les objets OpenStack Swift ont une limitation de taille maximale de 5 Go. Cependant, les conteneurs Swift peuvent contenir des milliers de ces objets. Les fichiers de plus de 5 Go doivent être divisés en un sous-ensemble de fichiers plus petits avant d’être téléchargés dans le conteneur. Une fois tous les fichiers composants téléchargés, un fichier de zéro octet avec un en-tête « manifeste » est téléchargé dans le conteneur. Cet en-tête est composé du nom du conteneur et du préfixe utilisés pour construire les noms des fichiers composants. Lorsque ce fichier de zéro octet est référencé via l’API Swift, tous les fichiers composants sont concaténés et le fichier volumineux original est téléchargé.
Le script Python suivant téléchargera des images personnalisées de moins de 1 Go directement dans le stockage objet de K5. Les fichiers plus volumineux sont divisés en chunks de 1 Go avant d’être téléchargés dans le stockage objet. La taille par défaut de 1 Go peut être modifiée à l’aide des paramètres de la ligne de commande.
Une fois l’image téléchargée, elle est ensuite enregistrée avec le projet K5 par défaut. L’image doit maintenant être partagée avec les autres projets membres qui souhaitent consommer cette image – cela sera partagé dans le prochain article.
Prérequis : Le script s’appuie sur un fichier de paramètres, k5contractsettings.py, qui doit contenir tous vos détails de contrat et être placé dans le même répertoire – par exemple :
Ce fichier contient des caractères Unicode cachés ou bidirectionnels qui peuvent être interprétés ou compilés différemment de ce qui est affiché ci-dessous. Pour les examiner, ouvrez le fichier dans un éditeur qui révèle les caractères Unicode cachés.
En savoir plus sur les caractères Unicode bidirectionnels
Afficher les caractères cachés
| #!/usr/bin/python | |
| adminUser = 'username' | |
| adminPassword = 'password' | |
| contract = 'contract_name' | |
| contractid = 'contract_id' | |
| defaultid = 'default_project_id' | |
| project = 'working_project' | |
| region = 'uk-1' |
voir le code brut
k5contractsettings.py
hébergé avec ❤ par GitHub
Script d’exemple de téléchargement d’image
Ce fichier contient des caractères Unicode cachés ou bidirectionnels qui peuvent être interprétés ou compilés différemment de ce qui est affiché ci-dessous. Pour les examiner, ouvrez le fichier dans un éditeur qui révèle les caractères Unicode cachés.
En savoir plus sur les caractères Unicode bidirectionnels
Afficher les caractères cachés
| #!/usr/bin/python | |
| # Auteur : Graham Land | |
| # Date : 08/10/2016 | |
| # | |
| # Objectif : Télécharger une image personnalisée dans le stockage objet K5, puis l’enregistrer dans K5 Glance | |
| # Si l’image fait plus de 1 Go, elle sera décomposée en chunks de 1 Go | |
| # puis téléchargée | |
| # Paramètres de la ligne de commande – | |
| # -i image_path | |
| # -c container_name | |
| # -s chunk_size (octets) | |
| # -n display_name | |
| # -t image_type | |
| # -p project | |
| # | |
| # Prérequis : fichier k5contractsettings.py dans le même répertoire avec les identifiants de connexion | |
| # | |
| # adminUser = 'username' | |
| # adminPassword = 'password' | |
| # contract = 'contract_name' | |
| # contractid = 'contract_id' | |
| # defaultid = 'default_project_id' | |
| # project = 'working_project' | |
| # region = 'uk-1' | |
| # | |
| # blog : https://allthingscloud.eu | |
| # twitter : @allthingsclowd | |
| import sys | |
| import os | |
| import requests | |
| import uuid | |
| import base64 | |
| import time | |
| import getopt | |
| import ntpath | |
| # charger vos détails de contrat K5 depuis le fichier k5contractsettings.py | |
| from k5contractsettings import * | |
| # obtenir un jeton d’authentification avec scope | |
| def get_scoped_token(uname,upassword,uproject,udomain,uregion): | |
| identityURL = 'https://identity.' + uregion + '.cloud.global.fujitsu.com/v3/auth/tokens' | |
| response = requests.post(identityURL, | |
| headers={'Content-Type': 'application/json','Accept':'application/json'}, | |
| json={"auth": | |
| {"identity": | |
| {"methods":["password"],"password": | |
| {"user": | |
| {"domain": | |
| {"name":udomain}, | |
| "name":uname, | |
| "password": upassword | |
| }}}, | |
| "scope": | |
| { "project": | |
| {"id":uproject | |
| }}}}) | |
| return response.headers['X-Subject-Token'] | |
| def get_unscoped_token(uname,upassword,udomain,uregion): | |
| identityURL = 'https://identity.' + uregion + '.cloud.global.fujitsu.com/v3/auth/tokens' | |
| response = requests.post(identityURL, | |
| headers={'Content-Type': 'application/json','Accept':'application/json'}, | |
| json={"auth": | |
| {"identity": | |
| {"methods":["password"],"password": | |
| {"user": | |
| {"domain": | |
| {"name":udomain}, | |
| "name":uname, | |
| "password": upassword | |
| }}}}}) | |
| return response.headers['X-Subject-Token'] | |
| # obtenir un jeton du portail d’identité central | |
| def get_unscoped_idtoken(uname,upassword,udomain): | |
| response = requests.post('https://auth-api.jp-east-1.paas.cloud.global.fujitsu.com/API/paas/auth/token', | |
| headers={'Content-Type': 'application/json'}, | |
| json={"auth": | |
| {"identity": | |
| {"password": | |
| {"user": | |
| {"contract_number":udomain, | |
| "name":uname, | |
| "password": upassword | |
| }}}}}) | |
| return response.headers['X-Access-Token'] | |
| # créer un conteneur | |
| def create_new_storage_container(adminUser,adminPassword,project,container_name,contract,region): | |
| # obtenir un jeton avec scope de domaine régional pour effectuer des requêtes afin de faciliter la conversion des noms d’objets en identifiants | |
| scoped_k5token = get_scoped_token(adminUser,adminPassword,project,contract,region) | |
| print scoped_k5token | |
| identityURL = 'https://objectstorage.' + region + '.cloud.global.fujitsu.com/v1/AUTH_' + project + '/' + container_name | |
| print identityURL | |
| response = requests.put(identityURL, | |
| headers={'X-Auth-Token':scoped_k5token,'Content-Type': 'application/json'}) | |
| return response | |
| def upload_file_to_container(adminUser,adminPassword,project,container_name,file_name,file_path,contract,region): | |
| # obtenir un jeton avec scope de domaine régional pour effectuer des requêtes afin de faciliter la conversion des noms d’objets en identifiants | |
| scoped_k5token = get_scoped_token(adminUser,adminPassword,project,contract,region) | |
| uploadfile = open(file_path, 'rb') | |
| data = uploadfile.read() | |
| identityURL = 'https://objectstorage.' + region + '.cloud.global.fujitsu.com/v1/AUTH_' + project + '/' + container_name + '/' + file_name | |
| response = requests.put(identityURL, | |
| data=data, | |
| headers={'X-Auth-Token':scoped_k5token,'Content-Type': 'application/octet-stream'}) | |
| uploadfile.close | |
| return response | |
| def import_from_container_to_k5(adminUser,adminPassword,project,container_name,file_name,display_name,file_path,os_type,contract,region): | |
| # obtenir un jeton avec scope de domaine régional pour effectuer des requêtes afin de faciliter la conversion des noms d’objets en identifiants | |
| scoped_k5token = get_scoped_token(adminUser,adminPassword,project,contract,region) | |
| k5ContainerURL = '/v1/AUTH_' + project + '/' + container_name + '/' + file_name | |
| image_id = str(uuid.uuid4()) | |
| encodedPassword = base64.b64encode(adminPassword) | |
| vmimportURL = 'https://vmimport.' + region + '.cloud.global.fujitsu.com/v1/imageimport' | |
| response = requests.post(vmimportURL, | |
| headers={'X-Auth-Token':scoped_k5token}, | |
| json={"name":display_name, | |
| "location":k5ContainerURL, | |
| "id":image_id, | |
| "conversion": True, | |
| "os_type":os_type, | |
| "user_name":adminUser, | |
| "password":encodedPassword, | |
| "domain_name":contract}) | |
| return response.json() | |
| def verify_image_import_status(adminUser,adminPassword,project,image_id,contract,region): | |
| # obtenir un jeton avec scope de domaine régional pour effectuer des requêtes afin de faciliter la conversion des noms d’objets en identifiants | |
| scoped_k5token = get_scoped_token(adminUser,adminPassword,project,contract,region) | |
| vmimportURL = 'https://vmimport.' + region + '.cloud.global.fujitsu.com/v1/imageimport/' + image_id + '/status' | |
| response = requests.get(vmimportURL, | |
| headers={'X-Auth-Token':scoped_k5token}) | |
| return response.json() | |
| def upload_manifest_to_container(adminUser,adminPassword,project,container_name,file_name,prefix,contract,region): | |
| # obtenir un jeton avec scope de domaine régional pour effectuer des requêtes afin de faciliter la conversion des noms d’objets en identifiants | |
| scoped_k5token = get_scoped_token(adminUser,adminPassword,project,contract,region) | |
| identityURL = 'https://objectstorage.' + region + '.cloud.global.fujitsu.com/v1/AUTH_' + project + '/' + container_name + '/' + file_name | |
| response = requests.put(identityURL, | |
| headers={'X-Auth-Token':scoped_k5token,'X-Object-Manifest': container_name + '/' + prefix}) | |
| return response | |
| # lister les éléments dans un conteneur | |
| def view_items_in_storage_container(adminUser,adminPassword,project,container_name,contract,region): | |
| # obtenir un jeton avec scope de domaine régional pour effectuer des requêtes afin de faciliter la conversion des noms d’objets en identifiants | |
| scoped_k5token = get_scoped_token(adminUser,adminPassword,project,contract,region) | |
| identityURL = 'https://objectstorage.' + region + '.cloud.global.fujitsu.com/v1/AUTH_' + project + '/' + container_name + '?format=json' | |
| response = requests.get(identityURL, | |
| headers={'X-Auth-Token':scoped_k5token,'Content-Type': 'application/json'}) | |
| return response | |
| # télécharger un élément dans un conteneur | |
| def download_item_in_storage_container(adminUser,adminPassword,project,container_name,contract,region): | |
| # obtenir un jeton avec scope de domaine régional pour effectuer des requêtes afin de faciliter la conversion des noms d’objets en identifiants | |
| scoped_k5token = get_scoped_token(adminUser,adminPassword,project,contract,region) | |
| identityURL = 'https://objectstorage.' + region + '.cloud.global.fujitsu.com/v1/AUTH_' + project + '/' + container_name + '/manifest' | |
| print identityURL | |
| response = requests.get(identityURL, | |
| headers={'X-Auth-Token':scoped_k5token,'Content-Type': 'application/json'}) | |
| return response | |
| def make_out_filename(prefix, idx): | |
| '''Créer un nom de fichier avec un suffixe de numéro de série.''' | |
| return prefix + str(idx).zfill(4) | |
| def bsplit(in_filename, bytes_per_file,os_type): | |
| '''Diviser le fichier d’entrée in_filename en fichiers de sortie de | |
| bytes_per_file octets chacun. Le dernier fichier peut contenir moins d’octets.''' | |
| in_fil = open(in_filename, "rb") | |
| outfil_idx = 1 | |
| out_filename = make_out_filename(os_type, outfil_idx) | |
| out_fil = open(out_filename, "wb") | |
| byte_count = tot_byte_count = file_count = 0 | |
| c = in_fil.read(1) | |
| # Boucler sur l’entrée et la diviser en plusieurs fichiers | |
| # de bytes_per_file octets chacun (sauf possiblement pour le | |
| # dernier fichier, qui peut contenir moins d’octets. | |
| while c != '': | |
| byte_count += 1 | |
| out_fil.write(c) | |
| # Incrémenter les variables ; passer au fichier de sortie suivant. | |
| if byte_count >= bytes_per_file: | |
| tot_byte_count += byte_count | |
| byte_count = 0 | |
| file_count += 1 | |
| out_fil.close() | |
| result = upload_file_to_container(adminUser,adminPassword,defaultid,container_name,out_filename,out_filename,contract,region) | |
| print "Package téléchargé – " + str(file_count) | |
| os.remove(out_filename) | |
| outfil_idx += 1 | |
| out_filename = make_out_filename(os_type, outfil_idx) | |
| out_fil = open(out_filename, "wb") | |
| c = in_fil.read(1) | |
| # Nettoyage. | |
| in_fil.close() | |
| if not out_fil.closed: | |
| out_fil.close() | |
| result = upload_file_to_container(adminUser,adminPassword,defaultid,container_name,out_filename,out_filename,contract,region) | |
| print "\nPackage téléchargé – " + str(file_count) | |
| os.remove(out_filename) | |
| if byte_count == 0: | |
| os.remove(out_filename) | |
| # maintenant créer le fichier manifeste | |
| result = upload_manifest_to_container(adminUser,adminPassword,defaultid,container_name,file_name,os_type,contract,region) | |
| return result | |
| def main(): | |
| try: | |
| # s’assurer que les paramètres de ligne de commande minimaux ont été fournis | |
| if (len(sys.argv)<6): | |
| print("Usage1: %s -i 'path_to_image' -c 'container_name' -n 'image_display_name' -p '{project1,project2,project3}' -t [ubuntu | |
| sys.exit(2) | |
| # charger les paramètres de la ligne de commande | |
| myopts, args = getopt.getopt(sys.argv[1:],"i:c:n:p:t:s:",["imagepath=","container=","name=","projects=","type=","size="]) | |
| except getopt.GetoptError: | |
| # si les paramètres sont incorrects, afficher le message d’erreur | |
| print("Usage2: %s -i 'path_to_image' -c 'container_name' -n 'image_display_name' -p '{project1,project2,project3}' -t [ubuntu | |
| sys.exit(2) | |
| # définir les variables globales à partir des paramètres de la ligne de commande | |
| global container_name | |
| global display_name | |
| global bytes_per_file | |
| global os_type | |
| global file_path | |
| global file_name | |
| # définir la taille par défaut du chunk pour les images volumineuses qui doivent être décomposées, elle doit être inférieure à 5 Go pour le stockage objet Swift | |
| bytes_per_file = 1048576000 #5242880 #1048576000 #262144000 # chunks de 250 Mo | |
| ############################### | |
| # o == option | |
| # a == argument passé à l’option o | |
| ############################### | |
| for o, a in myopts: | |
| if o in ('-i','–imagepath'): | |
| file_path=a | |
| elif o in ('-c','–container'): | |
| container_name=a | |
| elif o in ('-n','–name'): | |
| display_name=a | |
| elif o in ('-p','–projects'): | |
| projects=a | |
| elif o in ('-t','–type'): | |
| os_type=a | |
| elif o in ('-s','–size'): | |
| bytes_per_file=int(a) | |
| else: | |
| print("Usage3: %s -i 'path_to_image' -c 'container_name' -n 'image_display_name' -p '{project1,project2,project3}' -t [ubuntu | |
| # extraire le nom de fichier du chemin d’accès fourni en ligne de commande | |
| file_name = ntpath.basename(file_path) | |
| # tenter de lire le contenu du conteneur pour voir s’il existe déjà | |
| result = view_items_in_storage_container(adminUser,adminPassword,defaultid,container_name,contract,region) | |
| # vérifier si le conteneur existe déjà, sinon le créer | |
| if (result.status_code == 404): | |
| # créer le conteneur | |
| print "\nCréation du nouveau conteneur : " + container_name | |
| result = create_new_storage_container(adminUser,adminPassword,defaultid,container_name,contract,region) | |
| print "\nNouveau conteneur créé : " + container_name | |
| # vérifier que la taille du fichier à télécharger est inférieure à 250 Go, sinon le diviser en chunks plus petits pour le téléchargement | |
| if (os.path.getsize(file_path) > bytes_per_file): | |
| # boucler sur le fichier image pour le téléchargement multi-parties | |
| print "\n———- Démarrage du téléchargement de fichier multi-parties vers le stockage objet K5 —— \n" | |
| result = bsplit(file_path, bytes_per_file,os_type) | |
| print "\n———- Fin du téléchargement de fichier multi-parties vers le stockage objet K5 —— \n" | |
| else: | |
| # téléchargement simple de fichier dans le conteneur | |
| print "\n———- Démarrage du téléchargement simple de fichier vers le stockage objet K5 —— \n" | |
| result = upload_file_to_container(adminUser,adminPassword,defaultid,container_name,file_name,file_path,contract,region) | |
| print "\n———- Fin du téléchargement simple de fichier vers le stockage objet K5 —— \n" | |
| # lister le conteneur | |
| print "\n———- Début de la liste du contenu du conteneur du stockage objet K5 —— \n" | |
| result = view_items_in_storage_container(adminUser,adminPassword,defaultid,container_name,contract,region) | |
| print result | |
| print "\n———- Fin de la liste du contenu du conteneur du stockage objet K5 —— \n" | |
| # Enregistrer l’image avec K5 | |
| print "\n———- Enregistrement de l’image avec K5 —— \n" | |
| result = import_from_container_to_k5(adminUser,adminPassword,defaultid,container_name,file_name,display_name,file_path,os_type,contract,region) | |
| image_id = result['import_id'] | |
| print result | |
| print "\n———- K5 Image import_id : " + image_id + "\n" | |
| # Obtenir le statut d’importation | |
| print "\n———- Vérification du statut d’importation ———- \n\n" | |
| result = verify_image_import_status(adminUser,adminPassword,defaultid,image_id,contract,region) | |
| print result | |
| while ((result['import_status'] != "succeeded") and (result['import_status'] != "failed")): | |
| time.sleep(300) | |
| print "\n———- Vérification du statut d’importation ———- \n" | |
| result = verify_image_import_status(adminUser,adminPassword,defaultid,image_id,contract,region) | |
| print result | |
| print "Fin du processus d’importation – Statut d’importation >>> " + result['import_status'] | |
| if __name__ == "__main__": | |
| main() | |
voir le code brut
k5ImageUpload.py
hébergé avec ❤ par GitHub
Sortie du script d’exemple :
C:\Users\landg\>python K5ImageUpload.py -i "c:\Users\landg\Downloads\cirrosuploadtest.vmdk" -c uploaddemo12 -n "h
ello k5 milti image" -t ubuntu -p NotUsed -s 5242880
Creating new container : uploaddemo12
31c0f27e562c4b3089a546c175c144e4
https://objectstorage.uk-1.cloud.global.fujitsu.com/v1/AUTH_eadb882573ac40b1b101
eac93009a313/uploaddemo12
Created new container : uploaddemo12
---------- Starting multi-part file upload to K5 object storage ------
Uploaded Package - 1
Uploaded Package - 2
Uploaded Package - 3
Uploaded Package - 4
Uploaded Package - 4
---------- Finished multi-part file upload to K5 object storage ------
---------- List container contents K5 object storage start ------
<Response [200]>
---------- List container contents K5 object storage end ------
---------- Registering image with K5 ------
{u'import_id': u'6a0b58c5-bcda-4a64-919e-23f06b8338ad'}
---------- K5 Image import_id : 6a0b58c5-bcda-4a64-919e-23f06b8338ad
---------- Check import status ----------
{u'conversion': True, u'name': u'hello k5 milti image', u'container_format': u'b
are', u'min_ram': u'0', u'ovf_location': u'', u'disk_format': u'raw', u'domain_n
ame': u'YssmW1yI', u'location': u'/v1/AUTH_eadb882573ac40b1b101eac93009a313/uplo
addemo12/cirrosuploadtest.vmdk', u'min_disk': u'0', u'progress': u'0', u'os_type
': u'ubuntu', u'password': u'*', u'user_name': u'landg', u'id': u'70a38639-f819-
4375-b3d2-cfc99c2a148e', u'import_status': u'queued'}
---------- Check import status ----------
{u'conversion': True, u'name': u'hello k5 milti image', u'container_format': u'b
are', u'min_ram': u'0', u'ovf_location': u'', u'disk_format': u'raw', u'domain_n
ame': u'YssmW1yI', u'location': u'/v1/AUTH_eadb882573ac40b1b101eac93009a313/uplo
addemo12/cirrosuploadtest.vmdk', u'min_disk': u'0', u'progress': 0, u'os_type':
u'ubuntu', u'password': u'*', u'user_name': u'landg', u'id': u'70a38639-f819-437
5-b3d2-cfc99c2a148e', u'import_status': u'processing'}
---------- Check import status ----------
{u'container_format': u'bare', u'min_ram': 0, u'updated_at': u'2016-10-08T17:24:
38Z', u'file': u'/v2/images/70a38639-f819-4375-b3d2-cfc99c2a148e/file', u'owner'
: u'eadb882573ac40b1b101eac93009a313', u'id': u'70a38639-f819-4375-b3d2-cfc99c2a
148e', u'size': 41126400, u'conversion': True, u'self': u'/v2/images/70a38639-f8
19-4375-b3d2-cfc99c2a148e', u'disk_format': u'raw', u'domain_name': u'YssmW1yI',
u'location': u'/v1/AUTH_eadb882573ac40b1b101eac93009a313/uploaddemo12/cirrosupl
oadtest.vmdk', u'progress': 100, u'user_name': u'landg', u'schema': u'/v2/schema
s/image', u'status': u'active', u'import_status': u'succeeded', u'tags': [], u'v
isibility': u'private', u'BaseImageId': u'415b3a0a513aebc27d34c68bd8cdae8c', u'm
in_disk': 0, u'password': u'*', u'name': u'hello k5 milti image', u'created_at':
u'2016-10-08T17:24:32Z', u'ovf_location': u'', u'fcx.centos': u'true', u'protec
ted': False, u'os_type': u'ubuntu'}
End of Import Process - Import status >>> succeeded
Happy Stacking!
Originally published on allthingscloud.eu (2016-10-08).