From 94559e1d8ea54c97b830335b360e6f06ff1cbf11 Mon Sep 17 00:00:00 2001 From: Tiago Ilieve Date: Fri, 19 Feb 2016 19:14:06 -0200 Subject: [PATCH] oracle: add 'OracleStorageAPIClient' This client will be used to upload images to Oracle Compute Cloud, through the Oracle Storage Cloud API. --- bootstrapvz/providers/oracle/apiclient.py | 130 ++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 bootstrapvz/providers/oracle/apiclient.py diff --git a/bootstrapvz/providers/oracle/apiclient.py b/bootstrapvz/providers/oracle/apiclient.py new file mode 100644 index 0000000..c9f66a8 --- /dev/null +++ b/bootstrapvz/providers/oracle/apiclient.py @@ -0,0 +1,130 @@ +import hashlib +import logging +import os +import requests + + +class OracleStorageAPIClient: + MEGABYTE = 1024**2 + + def __init__(self, username, password, identity_domain, container): + self.username = username + self.password = password + self.identity_domain = identity_domain + self.container = container + self.base_url = 'https://' + identity_domain + '.storage.oraclecloud.com' + self.log = logging.getLogger(__name__) + + def _fail(self, error): + raise RuntimeError('Oracle Storage Cloud API - ' + error) + + @property + def auth_token(self): + headers = { + 'X-Storage-User': 'Storage-{id_domain}:{user}'.format( + id_domain=self.identity_domain, + user=self.username, + ), + 'X-Storage-Pass': self.password, + } + url = self.base_url + '/auth/v1.0' + response = requests.get(url, headers=headers) + if response.status_code == 200: + return response.headers.get('x-auth-token') + else: + self._fail(response.text) + + @property + def chunk_size(self): + file_size = os.path.getsize(self.file_path) + if file_size > (300 * self.MEGABYTE): + chunk_size = 100 * self.MEGABYTE + else: + chunk_size = 50 * self.MEGABYTE + return chunk_size + + def compare_files(self): + uploaded_file_md5 = hashlib.md5() + downloaded_file_md5 = hashlib.md5() + files = [self.file_path, self.target_file_path] + hashes = [uploaded_file_md5, downloaded_file_md5] + for f, h in zip(files, hashes): + with open(f, 'rb') as current_file: + while True: + data = current_file.read(self.MEGABYTE) + if not data: + break + h.update(data) + if uploaded_file_md5.hexdigest() != downloaded_file_md5.hexdigest(): + self.log.error('File hashes mismatch') + else: + self.log.info('Both files have the same hash') + + def create_manifest(self): + headers = { + 'X-Auth-Token': self.auth_token, + 'X-Object-Manifest': '{container}/{object_name}-'.format( + container=self.container, + object_name=self.file_name, + ), + 'Content-Length': '0', + } + url = self.object_url + self.log.info('Creating remote manifest to join chunks') + response = requests.put(url, headers=headers) + if response.status_code != 201: + self._fail(response.text) + + def download_file(self): + headers = { + 'X-Auth-Token': self.auth_token, + } + url = self.object_url + response = requests.get(url, headers=headers, stream=True) + if response.status_code != 200: + self._fail(response.text) + with open(self.target_file_path, 'wb') as f: + for chunk in response.iter_content(chunk_size=self.MEGABYTE): + if chunk: + f.write(chunk) + + @property + def file_name(self): + return os.path.basename(self.file_path) + + @property + def object_url(self): + url = '{base}/v1/Storage-{id_domain}/{container}/{object_name}'.format( + base=self.base_url, + id_domain=self.identity_domain, + container=self.container, + object_name=self.file_name, + ) + return url + + def upload_file(self): + f = open(self.file_path, 'rb') + n = 1 + while True: + chunk = f.read(self.chunk_size) + if not chunk: + break + chunk_name = '{name}-{number}'.format( + name=self.file_name, + number='{0:04d}'.format(n), + ) + headers = { + 'X-Auth-Token': self.auth_token, + } + url = '{base}/v1/Storage-{id_domain}/{container}/{object_chunk_name}'.format( + base=self.base_url, + id_domain=self.identity_domain, + container=self.container, + object_chunk_name=chunk_name, + ) + self.log.info('Uploading chunk ' + chunk_name) + response = requests.put(url, data=chunk, headers=headers) + if response.status_code != 201: + self._fail(response.text) + n += 1 + self.create_manifest()