Add Swift storage library
This commit is contained in:
parent
417c77f4d9
commit
5845e37e32
9 changed files with 341 additions and 33 deletions
|
@ -21,7 +21,6 @@ paramiko
|
||||||
xhtml2pdf
|
xhtml2pdf
|
||||||
redis
|
redis
|
||||||
hiredis
|
hiredis
|
||||||
docker-py
|
|
||||||
flask-restful==0.2.12
|
flask-restful==0.2.12
|
||||||
jsonschema
|
jsonschema
|
||||||
git+https://github.com/NateFerrero/oauth2lib.git
|
git+https://github.com/NateFerrero/oauth2lib.git
|
||||||
|
@ -52,3 +51,5 @@ mock
|
||||||
psutil
|
psutil
|
||||||
stringscore
|
stringscore
|
||||||
mockldap
|
mockldap
|
||||||
|
python-swiftclient
|
||||||
|
python-keystoneclient
|
|
@ -1,68 +1,91 @@
|
||||||
APScheduler==3.0.1
|
APScheduler==3.0.3
|
||||||
|
Babel==1.3
|
||||||
Flask==0.10.1
|
Flask==0.10.1
|
||||||
Flask-Login==0.2.11
|
Flask-Login==0.2.11
|
||||||
Flask-Mail==0.9.1
|
Flask-Mail==0.9.1
|
||||||
Flask-Principal==0.4.0
|
Flask-Principal==0.4.0
|
||||||
Flask-RESTful==0.2.12
|
Flask-RESTful==0.2.12
|
||||||
Jinja2==2.7.3
|
Jinja2==2.7.3
|
||||||
LogentriesLogger==0.2.1
|
Logentries==0.7
|
||||||
Mako==1.0.0
|
Mako==1.0.1
|
||||||
MarkupSafe==0.23
|
MarkupSafe==0.23
|
||||||
Pillow==2.7.0
|
Pillow==2.8.1
|
||||||
PyMySQL==0.6.3
|
PyMySQL==0.6.6
|
||||||
PyPDF2==1.24
|
PyPDF2==1.24
|
||||||
PyYAML==3.11
|
PyYAML==3.11
|
||||||
SQLAlchemy==0.9.8
|
SQLAlchemy==1.0.3
|
||||||
WebOb==1.4
|
WebOb==1.4.1
|
||||||
Werkzeug==0.9.6
|
Werkzeug==0.10.4
|
||||||
aiowsgi==0.3
|
aiowsgi==0.5
|
||||||
alembic==0.7.4
|
alembic==0.7.5.post2
|
||||||
|
argparse==1.3.0
|
||||||
autobahn==0.9.3-3
|
autobahn==0.9.3-3
|
||||||
backports.ssl-match-hostname==3.4.0.2
|
backports.ssl-match-hostname==3.4.0.2
|
||||||
beautifulsoup4==4.3.2
|
beautifulsoup4==4.3.2
|
||||||
blinker==1.3
|
blinker==1.3
|
||||||
boto==2.35.1
|
boto==2.38.0
|
||||||
cachetools==1.0.0
|
cachetools==1.0.0
|
||||||
docker-py==0.7.1
|
certifi==2015.04.28
|
||||||
ecdsa==0.11
|
cffi==0.9.2
|
||||||
|
cryptography==0.8.2
|
||||||
|
ecdsa==0.13
|
||||||
|
enum34==1.0.4
|
||||||
|
funcparserlib==0.3.6
|
||||||
futures==2.2.0
|
futures==2.2.0
|
||||||
gevent==1.0.1
|
gevent==1.0.1
|
||||||
gipc==0.5.0
|
gipc==0.5.0
|
||||||
greenlet==0.4.5
|
greenlet==0.4.5
|
||||||
gunicorn==18.0
|
gunicorn==18.0
|
||||||
hiredis==0.1.5
|
hiredis==0.2.0
|
||||||
html5lib==0.999
|
html5lib==0.99999
|
||||||
|
iso8601==0.1.10
|
||||||
itsdangerous==0.24
|
itsdangerous==0.24
|
||||||
jsonschema==2.4.0
|
jsonschema==2.4.0
|
||||||
marisa-trie==0.7
|
marisa-trie==0.7.2
|
||||||
mixpanel-py==3.2.1
|
mixpanel-py==4.0.2
|
||||||
mock==1.0.1
|
mock==1.0.1
|
||||||
mockldap==0.2.4
|
mockldap==0.2.4
|
||||||
|
msgpack-python==0.4.6
|
||||||
|
netaddr==0.7.14
|
||||||
|
netifaces==0.10.4
|
||||||
|
oauthlib==0.7.2
|
||||||
|
oslo.config==1.11.0
|
||||||
|
oslo.i18n==1.6.0
|
||||||
|
oslo.serialization==1.5.0
|
||||||
|
oslo.utils==1.5.0
|
||||||
paramiko==1.15.2
|
paramiko==1.15.2
|
||||||
peewee==2.4.7
|
pbr==0.11.0
|
||||||
|
peewee==2.6.0
|
||||||
|
prettytable==0.7.2
|
||||||
psutil==2.2.1
|
psutil==2.2.1
|
||||||
psycopg2==2.5.4
|
psycopg2==2.6
|
||||||
py-bcrypt==0.4
|
py-bcrypt==0.4
|
||||||
|
pyOpenSSL==0.15.1
|
||||||
|
pyasn1==0.1.7
|
||||||
|
pycparser==2.12
|
||||||
pycrypto==2.6.1
|
pycrypto==2.6.1
|
||||||
python-dateutil==2.4.0
|
pygpgme==0.3
|
||||||
|
python-dateutil==2.4.2
|
||||||
|
python-keystoneclient==1.4.0
|
||||||
python-ldap==2.4.19
|
python-ldap==2.4.19
|
||||||
python-magic==0.4.6
|
python-magic==0.4.6
|
||||||
pygpgme==0.3
|
python-swiftclient==2.4.0
|
||||||
pytz==2014.10
|
pytz==2015.2
|
||||||
pyOpenSSL==0.14
|
raven==5.3.0
|
||||||
raven==5.1.1
|
|
||||||
redis==2.10.3
|
redis==2.10.3
|
||||||
reportlab==2.7
|
reportlab==2.7
|
||||||
requests==2.5.1
|
requests==2.6.2
|
||||||
requests-oauthlib==0.4.2
|
requests-oauthlib==0.4.2
|
||||||
|
simplejson==3.7.1
|
||||||
six==1.9.0
|
six==1.9.0
|
||||||
|
stevedore==1.4.0
|
||||||
stringscore==0.1.0
|
stringscore==0.1.0
|
||||||
stripe==1.20.1
|
stripe==1.22.2
|
||||||
trollius==1.0.4
|
trollius==1.0.4
|
||||||
tzlocal==1.1.2
|
tzlocal==1.1.3
|
||||||
urllib3==1.10.2
|
urllib3==1.10.3
|
||||||
waitress==0.8.9
|
waitress==0.8.9
|
||||||
websocket-client==0.23.0
|
websocket-client==0.30.0
|
||||||
wsgiref==0.1.2
|
wsgiref==0.1.2
|
||||||
xhtml2pdf==0.0.6
|
xhtml2pdf==0.0.6
|
||||||
git+https://github.com/DevTable/aniso8601-fake.git
|
git+https://github.com/DevTable/aniso8601-fake.git
|
||||||
|
|
|
@ -388,6 +388,29 @@ a:focus {
|
||||||
width: 400px;
|
width: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.config-map-field-element table {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-map-field-element .form-control-container {
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-map-field-element .form-control-container select, .config-map-field-element .form-control-container input {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-map-field-element .empty {
|
||||||
|
color: #ccc;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-map-field-element .item-title {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
.config-contact-field {
|
.config-contact-field {
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
20
static/directives/config/config-map-field.html
Normal file
20
static/directives/config/config-map-field.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<div class="config-map-field-element">
|
||||||
|
<table class="table" ng-show="hasValues(binding)">
|
||||||
|
<tr class="item" ng-repeat="(key, value) in binding">
|
||||||
|
<td class="item-title">{{ key }}</td>
|
||||||
|
<td class="item-value">{{ value }}</td>
|
||||||
|
<td class="item-delete">
|
||||||
|
<a href="javascript:void(0)" ng-click="removeKey(key)">Remove</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<span class="empty" ng-if="!hasValues(binding)">No entries defined</span>
|
||||||
|
<form class="form-control-container" ng-submit="addEntry()">
|
||||||
|
Add Key-Value:
|
||||||
|
<select ng-model="newKey">
|
||||||
|
<option ng-repeat="key in keys" value="{{ key }}">{{ key }}</option>
|
||||||
|
</select>
|
||||||
|
<input type="text" class="form-control" placeholder="Value" ng-model="newValue">
|
||||||
|
<button class="btn btn-default" style="display: inline-block">Add Entry</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
|
@ -184,6 +184,7 @@
|
||||||
<option value="S3Storage">Amazon S3</option>
|
<option value="S3Storage">Amazon S3</option>
|
||||||
<option value="GoogleCloudStorage">Google Cloud Storage</option>
|
<option value="GoogleCloudStorage">Google Cloud Storage</option>
|
||||||
<option value="RadosGWStorage">Ceph Object Gateway (RADOS)</option>
|
<option value="RadosGWStorage">Ceph Object Gateway (RADOS)</option>
|
||||||
|
<option value="SwiftStorage">OpenStack Storage (Swift)</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -192,10 +193,15 @@
|
||||||
<tr ng-repeat="field in STORAGE_CONFIG_FIELDS[config.DISTRIBUTED_STORAGE_CONFIG.local[0]]">
|
<tr ng-repeat="field in STORAGE_CONFIG_FIELDS[config.DISTRIBUTED_STORAGE_CONFIG.local[0]]">
|
||||||
<td>{{ field.title }}:</td>
|
<td>{{ field.title }}:</td>
|
||||||
<td>
|
<td>
|
||||||
|
<span class="config-map-field"
|
||||||
|
binding="config.DISTRIBUTED_STORAGE_CONFIG.local[1][field.name]"
|
||||||
|
ng-if="field.kind == 'map'"
|
||||||
|
keys="field.keys"></span>
|
||||||
<span class="config-string-field"
|
<span class="config-string-field"
|
||||||
binding="config.DISTRIBUTED_STORAGE_CONFIG.local[1][field.name]"
|
binding="config.DISTRIBUTED_STORAGE_CONFIG.local[1][field.name]"
|
||||||
placeholder="{{ field.placeholder }}"
|
placeholder="{{ field.placeholder }}"
|
||||||
ng-if="field.kind == 'text'"></span>
|
ng-if="field.kind == 'text'"
|
||||||
|
is-optional="field.optional"></span>
|
||||||
<div class="co-checkbox" ng-if="field.kind == 'bool'">
|
<div class="co-checkbox" ng-if="field.kind == 'bool'">
|
||||||
<input id="dsc-{{ field.name }}" type="checkbox"
|
<input id="dsc-{{ field.name }}" type="checkbox"
|
||||||
ng-model="config.DISTRIBUTED_STORAGE_CONFIG.local[1][field.name]">
|
ng-model="config.DISTRIBUTED_STORAGE_CONFIG.local[1][field.name]">
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<form name="fieldform" novalidate>
|
<form name="fieldform" novalidate>
|
||||||
<input type="text" class="form-control" placeholder="{{ placeholder || '' }}"
|
<input type="text" class="form-control" placeholder="{{ placeholder || '' }}"
|
||||||
ng-model="binding" ng-trim="false" ng-minlength="1"
|
ng-model="binding" ng-trim="false" ng-minlength="1"
|
||||||
ng-pattern="getRegexp(pattern)" required>
|
ng-pattern="getRegexp(pattern)" ng-required="!isOptional">
|
||||||
<div class="alert alert-danger" ng-show="errorMessage">
|
<div class="alert alert-danger" ng-show="errorMessage">
|
||||||
{{ errorMessage }}
|
{{ errorMessage }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -78,6 +78,19 @@ angular.module("core-config-setup", ['angularFileUpload'])
|
||||||
{'name': 'secret_key', 'title': 'Secret Key', 'placeholder': 'secretkeyhere', 'kind': 'text'},
|
{'name': 'secret_key', 'title': 'Secret Key', 'placeholder': 'secretkeyhere', 'kind': 'text'},
|
||||||
{'name': 'bucket_name', 'title': 'Bucket Name', 'placeholder': 'my-cool-bucket', 'kind': 'text'},
|
{'name': 'bucket_name', 'title': 'Bucket Name', 'placeholder': 'my-cool-bucket', 'kind': 'text'},
|
||||||
{'name': 'storage_path', 'title': 'Storage Directory', 'placeholder': '/path/inside/bucket', 'kind': 'text'}
|
{'name': 'storage_path', 'title': 'Storage Directory', 'placeholder': '/path/inside/bucket', 'kind': 'text'}
|
||||||
|
],
|
||||||
|
|
||||||
|
'SwiftStorage': [
|
||||||
|
{'name': 'auth_url', 'title': 'Swift Auth URL', 'placeholder': '', 'kind': 'text'},
|
||||||
|
{'name': 'swift_container', 'title': 'Swift Container Name', 'placeholder': 'mycontainer', 'kind': 'text'},
|
||||||
|
{'name': 'storage_path', 'title': 'Storage Path', 'placeholder': '/path/inside/container', 'kind': 'text'},
|
||||||
|
|
||||||
|
{'name': 'swift_user', 'title': 'Username', 'placeholder': 'accesskeyhere', 'kind': 'text'},
|
||||||
|
{'name': 'swift_password', 'title': 'Password/Key', 'placeholder': 'secretkeyhere', 'kind': 'text'},
|
||||||
|
|
||||||
|
{'name': 'ca_cert_path', 'title': 'CA Cert Filename', 'placeholder': 'conf/stack/swift.cert', 'kind': 'text', 'optional': true},
|
||||||
|
{'name': 'os_options', 'title': 'OS Options', 'kind': 'map',
|
||||||
|
'keys': ['tenant_id', 'auth_token', 'service_type', 'endpoint_type', 'tenant_name', 'object_storage_url', 'region_name']}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -760,6 +773,42 @@ angular.module("core-config-setup", ['angularFileUpload'])
|
||||||
return directiveDefinitionObject;
|
return directiveDefinitionObject;
|
||||||
})
|
})
|
||||||
|
|
||||||
|
.directive('configMapField', function () {
|
||||||
|
var directiveDefinitionObject = {
|
||||||
|
priority: 0,
|
||||||
|
templateUrl: '/static/directives/config/config-map-field.html',
|
||||||
|
replace: false,
|
||||||
|
transclude: false,
|
||||||
|
restrict: 'C',
|
||||||
|
scope: {
|
||||||
|
'binding': '=binding',
|
||||||
|
'keys': '=keys'
|
||||||
|
},
|
||||||
|
controller: function($scope, $element) {
|
||||||
|
$scope.newKey = null;
|
||||||
|
$scope.newValue = null;
|
||||||
|
|
||||||
|
$scope.hasValues = function(binding) {
|
||||||
|
return binding && Object.keys(binding).length;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.removeKey = function(key) {
|
||||||
|
delete $scope.binding[key];
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.addEntry = function() {
|
||||||
|
if (!$scope.newKey || !$scope.newValue) { return; }
|
||||||
|
|
||||||
|
$scope.binding = $scope.binding || {};
|
||||||
|
$scope.binding[$scope.newKey] = $scope.newValue;
|
||||||
|
$scope.newKey = null;
|
||||||
|
$scope.newValue = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return directiveDefinitionObject;
|
||||||
|
})
|
||||||
|
|
||||||
.directive('configStringField', function () {
|
.directive('configStringField', function () {
|
||||||
var directiveDefinitionObject = {
|
var directiveDefinitionObject = {
|
||||||
priority: 0,
|
priority: 0,
|
||||||
|
@ -772,7 +821,8 @@ angular.module("core-config-setup", ['angularFileUpload'])
|
||||||
'placeholder': '@placeholder',
|
'placeholder': '@placeholder',
|
||||||
'pattern': '@pattern',
|
'pattern': '@pattern',
|
||||||
'defaultValue': '@defaultValue',
|
'defaultValue': '@defaultValue',
|
||||||
'validator': '&validator'
|
'validator': '&validator',
|
||||||
|
'isOptional': '=isOptional'
|
||||||
},
|
},
|
||||||
controller: function($scope, $element) {
|
controller: function($scope, $element) {
|
||||||
$scope.getRegexp = function(pattern) {
|
$scope.getRegexp = function(pattern) {
|
||||||
|
|
|
@ -2,6 +2,7 @@ from storage.local import LocalStorage
|
||||||
from storage.cloud import S3Storage, GoogleCloudStorage, RadosGWStorage
|
from storage.cloud import S3Storage, GoogleCloudStorage, RadosGWStorage
|
||||||
from storage.fakestorage import FakeStorage
|
from storage.fakestorage import FakeStorage
|
||||||
from storage.distributedstorage import DistributedStorage
|
from storage.distributedstorage import DistributedStorage
|
||||||
|
from storage.swift import SwiftStorage
|
||||||
|
|
||||||
|
|
||||||
STORAGE_DRIVER_CLASSES = {
|
STORAGE_DRIVER_CLASSES = {
|
||||||
|
@ -9,6 +10,7 @@ STORAGE_DRIVER_CLASSES = {
|
||||||
'S3Storage': S3Storage,
|
'S3Storage': S3Storage,
|
||||||
'GoogleCloudStorage': GoogleCloudStorage,
|
'GoogleCloudStorage': GoogleCloudStorage,
|
||||||
'RadosGWStorage': RadosGWStorage,
|
'RadosGWStorage': RadosGWStorage,
|
||||||
|
'SwiftStorage': SwiftStorage,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_storage_driver(storage_params):
|
def get_storage_driver(storage_params):
|
||||||
|
|
183
storage/swift.py
Normal file
183
storage/swift.py
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
""" Swift storage driver. Based on: github.com/bacongobbler/docker-registry-driver-swift/ """
|
||||||
|
from swiftclient.client import Connection, ClientException
|
||||||
|
from storage.basestorage import BaseStorage
|
||||||
|
|
||||||
|
from random import SystemRandom
|
||||||
|
import string
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class SwiftStorage(BaseStorage):
|
||||||
|
def __init__(self, swift_container, storage_path, auth_url, swift_user,
|
||||||
|
swift_password, auth_version=None, os_options=None, ca_cert_path=None):
|
||||||
|
self._swift_container = swift_container
|
||||||
|
self._storage_path = storage_path
|
||||||
|
|
||||||
|
self._auth_url = auth_url
|
||||||
|
self._ca_cert_path = ca_cert_path
|
||||||
|
|
||||||
|
self._swift_user = swift_user
|
||||||
|
self._swift_password = swift_password
|
||||||
|
|
||||||
|
self._auth_version = auth_version or 2
|
||||||
|
self._os_options = os_options or {}
|
||||||
|
|
||||||
|
self._initialized = False
|
||||||
|
self._swift_connection = None
|
||||||
|
|
||||||
|
def _initialize(self):
|
||||||
|
if self._initialized:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._initialized = True
|
||||||
|
self._swift_connection = self._get_connection()
|
||||||
|
|
||||||
|
def _get_connection(self):
|
||||||
|
return Connection(
|
||||||
|
authurl=self._auth_url,
|
||||||
|
cacert=self._ca_cert_path,
|
||||||
|
|
||||||
|
user=self._swift_user,
|
||||||
|
key=self._swift_password,
|
||||||
|
|
||||||
|
auth_version=self._auth_version,
|
||||||
|
os_options=self._os_options)
|
||||||
|
|
||||||
|
def _get_relative_path(self, path):
|
||||||
|
if path.startswith(self._storage_path):
|
||||||
|
path = path[len(self._storage_path)]
|
||||||
|
|
||||||
|
if path.endswith('/'):
|
||||||
|
path = path[:-1]
|
||||||
|
|
||||||
|
return path
|
||||||
|
|
||||||
|
def _normalize_path(self, path=None):
|
||||||
|
path = self._storage_path + (path or '')
|
||||||
|
|
||||||
|
# Openstack does not like paths starting with '/' and we always normalize
|
||||||
|
# to remove trailing '/'
|
||||||
|
if path.startswith('/'):
|
||||||
|
path = path[1:]
|
||||||
|
|
||||||
|
if path.endswith('/'):
|
||||||
|
path = path[:-1]
|
||||||
|
|
||||||
|
return path
|
||||||
|
|
||||||
|
def _get_container(self, path):
|
||||||
|
self._initialize()
|
||||||
|
path = self._normalize_path(path)
|
||||||
|
|
||||||
|
if path and not path.endswith('/'):
|
||||||
|
path += '/'
|
||||||
|
|
||||||
|
try:
|
||||||
|
_, container = self._swift_connection.get_container(
|
||||||
|
container=self._swift_container,
|
||||||
|
prefix=path, delimiter='/')
|
||||||
|
return container
|
||||||
|
except:
|
||||||
|
logger.exception('Could not get container: %s', path)
|
||||||
|
raise IOError('Unknown path: %s' % path)
|
||||||
|
|
||||||
|
def _get_object(self, path, chunk_size=None):
|
||||||
|
self._initialize()
|
||||||
|
path = self._normalize_path(path)
|
||||||
|
try:
|
||||||
|
_, obj = self._swift_connection.get_object(self._swift_container, path,
|
||||||
|
resp_chunk_size=chunk_size)
|
||||||
|
return obj
|
||||||
|
except Exception:
|
||||||
|
logger.exception('Could not get object: %s', path)
|
||||||
|
raise IOError('Path %s not found' % path)
|
||||||
|
|
||||||
|
def _put_object(self, path, content, chunk=None, content_type=None, content_encoding=None):
|
||||||
|
self._initialize()
|
||||||
|
path = self._normalize_path(path)
|
||||||
|
headers = {}
|
||||||
|
|
||||||
|
if content_encoding is not None:
|
||||||
|
headers['Content-Encoding'] = content_encoding
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._swift_connection.put_object(self._swift_container, path, content,
|
||||||
|
chunk_size=chunk, content_type=content_type,
|
||||||
|
headers=headers)
|
||||||
|
except ClientException:
|
||||||
|
raise
|
||||||
|
except Exception:
|
||||||
|
logger.exception('Could not put object: %s', path)
|
||||||
|
raise IOError("Could not put content: %s" % path)
|
||||||
|
|
||||||
|
def _head_object(self, path):
|
||||||
|
self._initialize()
|
||||||
|
path = self._normalize_path(path)
|
||||||
|
try:
|
||||||
|
return self._swift_connection.head_object(self._swift_container, path)
|
||||||
|
except Exception:
|
||||||
|
logger.exception('Could not head object: %s', path)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_direct_download_url(self, path, expires_in=60, requires_cors=False):
|
||||||
|
if requires_cors:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# TODO: http://docs.openstack.org/juno/config-reference/content/object-storage-tempurl.html
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_content(self, path):
|
||||||
|
return self._get_object(path)
|
||||||
|
|
||||||
|
def put_content(self, path, content):
|
||||||
|
self._put_object(path, content)
|
||||||
|
|
||||||
|
def stream_read(self, path):
|
||||||
|
for data in self._get_object(path, self.buffer_size):
|
||||||
|
yield data
|
||||||
|
|
||||||
|
def stream_read_file(self, path):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def stream_write(self, path, fp, content_type=None, content_encoding=None):
|
||||||
|
self._put_object(path, fp, self.buffer_size, content_type=content_type,
|
||||||
|
content_encoding=content_encoding)
|
||||||
|
|
||||||
|
def list_directory(self, path=None):
|
||||||
|
container = self._get_container(path)
|
||||||
|
if not container:
|
||||||
|
raise OSError('Unknown path: %s' % path)
|
||||||
|
|
||||||
|
for entry in container:
|
||||||
|
param = None
|
||||||
|
if 'name' in entry:
|
||||||
|
param = 'name'
|
||||||
|
elif 'subdir' in entry:
|
||||||
|
param = 'subdir'
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
yield self._get_relative_path(entry[param])
|
||||||
|
|
||||||
|
def exists(self, path):
|
||||||
|
return bool(self._head_object(path))
|
||||||
|
|
||||||
|
def remove(self, path):
|
||||||
|
self._initialize()
|
||||||
|
path = self._normalize_path(path)
|
||||||
|
try:
|
||||||
|
self._swift_connection.delete_object(self._swift_container, path)
|
||||||
|
except Exception:
|
||||||
|
raise IOError('Cannot delete path: %s' % path)
|
||||||
|
|
||||||
|
def _random_checksum(self, count):
|
||||||
|
chars = string.ascii_uppercase + string.digits
|
||||||
|
return ''.join(SystemRandom().choice(chars) for _ in range(count))
|
||||||
|
|
||||||
|
def get_checksum(self, path):
|
||||||
|
headers = self._head_object(path)
|
||||||
|
if not headers:
|
||||||
|
raise IOError('Cannot lookup path: %s' % path)
|
||||||
|
|
||||||
|
return headers.get('etag', '')[1:-1][:7] or self._random_checksum(7)
|
Reference in a new issue