Retire Packaging Deb project repos
This commit is part of a series to retire the Packaging Deb project. Step 2 is to remove all content from the project repos, replacing it with a README notification where to find ongoing work, and how to recover the repo if needed at some future point (as in https://docs.openstack.org/infra/manual/drivers.html#retiring-a-project). Change-Id: Ic0c68767aff92861baade50fe504541d46e3fa83
This commit is contained in:
53
.gitignore
vendored
53
.gitignore
vendored
@@ -1,53 +0,0 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
@@ -1,4 +0,0 @@
|
||||
[gerrit]
|
||||
host=review.openstack.org
|
||||
port=29418
|
||||
project=openstack/pyeclib.git
|
||||
8
.mailmap
8
.mailmap
@@ -1,8 +0,0 @@
|
||||
Kevin Greenan <kmgreen2@gmail.com> <kmg@box.com>
|
||||
Kevin Greenan <kmgreen2@gmail.com> <kmgreen@ubuntu.(none)>
|
||||
Kevin Greenan <kmgreen2@gmail.com> <kmgreen@Kevins-MacBook-Air-6.local>
|
||||
Tushar Gohad <tushar.gohad@intel.com> <tusharsg@gmail.com>
|
||||
Tushar Gohad <tushar.gohad@intel.com> <Tushar Gohad>
|
||||
Eric Lambert <eric_lambert@xyratex.com> <eric.lambert@seagate.com>
|
||||
Kota Tsuyuzaki <bloodeagle40234@gmail.com> <tsuyuzaki.kota@lab.ntt.co.jp>
|
||||
Kota Tsuyuzaki <bloodeagle40234@gmail.com> <bloodeagle40123@gmail.com>
|
||||
14
.travis.yml
14
.travis.yml
@@ -1,14 +0,0 @@
|
||||
language: python
|
||||
python:
|
||||
- "2.7"
|
||||
- "3.4"
|
||||
script: python setup.py install && nosetests test/ && tox
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
before_install:
|
||||
- echo "deb http://archive.ubuntu.com/ubuntu precise-backports main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list
|
||||
- sudo apt-get update
|
||||
- sudo apt-get install -y liberasurecode-dev libffi-dev python-setuptools
|
||||
- sudo easy_install pip
|
||||
- pip install tox
|
||||
10
.unittests
10
.unittests
@@ -1,10 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
TOP_DIR=$(python -c "import os; print os.path.dirname(os.path.realpath('$0'))")
|
||||
|
||||
python -c 'from distutils.version import LooseVersion as Ver; import nose, sys; sys.exit(0 if Ver(nose.__version__) >= Ver("1.2.0") else 1)'
|
||||
cd $TOP_DIR/test
|
||||
nosetests $@
|
||||
rvalue=$?
|
||||
cd -
|
||||
exit $rvalue
|
||||
22
AUTHORS
22
AUTHORS
@@ -1,22 +0,0 @@
|
||||
Original Authors
|
||||
----------------
|
||||
Kevin Greenan (kmgreen2@gmail.com)
|
||||
Tushar Gohad (tushar.gohad@intel.com)
|
||||
|
||||
Contributors
|
||||
------------
|
||||
Clay Gerrard (clay.gerrard@gmail.com)
|
||||
Davanum Srinivas (davanum@gmail.com)
|
||||
Eric Lambert (eric_lambert@xyratex.com)
|
||||
Jim Cheung (jim.cheung@phazr.io)
|
||||
John Dickinson (me@not.mn)
|
||||
Kota Tsuyuzaki (bloodeagle40234@gmail.com)
|
||||
Mark Storer (Mark.Storer@evault.com)
|
||||
Ondřej Nový (ondrej.novy@firma.seznam.cz)
|
||||
Pete Zaitcev (zaitcev@kotori.zaitcev.us)
|
||||
Thiago da Silva (thiago@redhat.com)
|
||||
Timur Alperovich (timuralp@swiftstack.com)
|
||||
Tim Burke (tim.burke@gmail.com)
|
||||
Victor Stinner (vstinner@redhat.com)
|
||||
Yuan Zhou (yuan.zhou@intel.com)
|
||||
|
||||
116
ChangeLog
116
ChangeLog
@@ -1,116 +0,0 @@
|
||||
New in 1.5.0
|
||||
------------
|
||||
|
||||
* Added support for Phazr.IO libphazr library.
|
||||
* Fixed error handling that caused segfaults.
|
||||
* Use version number exposed by liberasurecode.
|
||||
* various other minor bugs fixes and improvements.
|
||||
|
||||
|
||||
New in 1.4.0
|
||||
------------
|
||||
|
||||
* Added support for ISA-L Cauchy
|
||||
* Fixed memory leak in get_metadata
|
||||
* Added soft warning log line when using liberasurecode <1.3.1
|
||||
|
||||
New in 1.3.1
|
||||
------------
|
||||
|
||||
* Updated name in setup.py to work with release tooling.
|
||||
|
||||
|
||||
New in 1.3.0
|
||||
------------
|
||||
|
||||
* Updated liberasurecode dependency to 1.2.0.
|
||||
|
||||
* Fixed memory leak in get_segment_info (Launchpad bug #1604335).
|
||||
|
||||
* Properly return an error code if liberasurecode returns an
|
||||
invalid fragment size.
|
||||
|
||||
* ECDriver() now requries "k" and "m" values to be passed in.
|
||||
|
||||
* Fix some requirements and installation instruction.
|
||||
|
||||
|
||||
New in 1.2.1
|
||||
------------
|
||||
|
||||
* Eliminate spurious syslog messages and added cleaner
|
||||
mechanism for querying all available backends on a system.
|
||||
|
||||
* Moved source code hosting from bitbucket to Openstack infra.
|
||||
This is first release with Openstack Infra
|
||||
|
||||
New in 1.2.0
|
||||
------------
|
||||
|
||||
* Remove integrated liberasurecode - the prereq library is now
|
||||
available in major deb/rpm based distros
|
||||
|
||||
* Eliminate liberasurecode-related rpath checks, library searches
|
||||
and architecture validation
|
||||
|
||||
* Changes for compatibility with liberasurecode versions prior to
|
||||
1.1.0
|
||||
|
||||
* Make VALID_EC_TYPES a runtime property - dynamically look for
|
||||
liberasurecode EC schemes available at runtime
|
||||
|
||||
* More comprehensive unit test coverage
|
||||
|
||||
* Add travis-ci build config for automated build/tests
|
||||
|
||||
New in 1.1.0
|
||||
------------
|
||||
|
||||
* Eliminate pyeclib dependency on alloc functions internal to
|
||||
liberasurecode
|
||||
|
||||
* Update include subdirs to be explicit
|
||||
|
||||
* Update internal liberasurecode version to 1.1.0
|
||||
|
||||
New in 1.0.9
|
||||
------------
|
||||
|
||||
* Eliminate rpath handling in setup.py
|
||||
|
||||
* Clean py34 shared libraries created during build
|
||||
|
||||
* Fix integer truncation issue with PyBuildValue on Big Endian
|
||||
systems by explicitly casting the size argument passed in to
|
||||
Py_ssize_t. Also fix import issue with the API test where
|
||||
older versions of Python fail to import.
|
||||
|
||||
* Add --install-liberasurecode option to setup.py. Requested
|
||||
by Red Hat/Debian package maintainers.
|
||||
|
||||
* Update bundled liberasurecode version to 1.0.9
|
||||
|
||||
|
||||
New in 1.0.8
|
||||
------------
|
||||
|
||||
* Support for a new Reed-Soloman backend (liberasurecode_rs_vand)
|
||||
- naive, non-accelerated version, native to liberasurecode
|
||||
|
||||
* Single version, with liberasurecode distributed in the package,
|
||||
installed if necessary. No versions going forward with Jerasure
|
||||
included. Use 'liberasurecode_rs_vand' for default test backend.
|
||||
|
||||
* Test code refactor for eliminating duplicated code, add cases
|
||||
for liberasurecode_rs_vand and making jerasure/isa_l test cases
|
||||
conditional.
|
||||
|
||||
* Better Python3 support
|
||||
|
||||
* setup.py enhancements
|
||||
- improved library path detection on Mac OS X (and Linux) including
|
||||
workarounds for Mac OS X dyld bugs for library search paths
|
||||
- handling installroot better for optional liberasurecode installs
|
||||
|
||||
* tox support for automated py27 and py34 testing
|
||||
|
||||
23
License.txt
23
License.txt
@@ -1,23 +0,0 @@
|
||||
Copyright (c) 2013-2015, Kevin Greenan (kmgreen2@gmail.com)
|
||||
Copyright (c) 2013-2015, Tushar Gohad (tusharsg@gmail.com)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY
|
||||
THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,3 +0,0 @@
|
||||
recursive-include src *
|
||||
recursive-include test *
|
||||
recursive-include tools *
|
||||
52
Makefile
52
Makefile
@@ -1,52 +0,0 @@
|
||||
# Copyright (c) 2014, Tushar Gohad (tusharsg@gmail.com)
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution. THIS SOFTWARE IS
|
||||
# PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
|
||||
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
# NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
TOPDIR := $(PWD)
|
||||
|
||||
.PHONY: build test
|
||||
|
||||
build:
|
||||
python setup.py build
|
||||
|
||||
install: build
|
||||
python setup.py install
|
||||
|
||||
UNITS := test/test_pyeclib_api.py test/test_pyeclib_c.py
|
||||
|
||||
test: build
|
||||
$(eval SONAMES := $(shell find $(abs_top_builddir) -name '*.so'))
|
||||
$(eval SODIRS := $(dir $(SONAMES)))
|
||||
$(eval LD_LIBRARY_PATH := LD_LIBRARY_PATH="$(subst / ,/:,$(SODIRS))")
|
||||
$(eval DYLD_LIBRARY_PATH := DYLD_LIBRARY_PATH="$(subst / ,/:,$(SODIRS))")
|
||||
$(eval DYLD_FALLBACK_LIBRARY_PATH := DYLD_FALLBACK_LIBRARY_PATH="$(subst / ,/:,$(SODIRS))")
|
||||
rm -rf cover .coverage
|
||||
@$(LD_LIBRARY_PATH) $(DYLD_LIBRARY_PATH) $(DYLD_FALLBACK_LIBRARY_PATH) \
|
||||
nosetests --exe --with-coverage \
|
||||
--cover-package pyeclib --cover-erase \
|
||||
--cover-html --cover-html-dir=${TOPDIR}/cover \
|
||||
$(UNITS)
|
||||
|
||||
clean:
|
||||
-rm -f pyeclib_c*.so
|
||||
-rm -rf build
|
||||
python setup.py clean
|
||||
14
README
Normal file
14
README
Normal file
@@ -0,0 +1,14 @@
|
||||
This project is no longer maintained.
|
||||
|
||||
The contents of this repository are still available in the Git
|
||||
source code management system. To see the contents of this
|
||||
repository before it reached its end of life, please check out the
|
||||
previous commit with "git checkout HEAD^1".
|
||||
|
||||
For ongoing work on maintaining OpenStack packages in the Debian
|
||||
distribution, please see the Debian OpenStack packaging team at
|
||||
https://wiki.debian.org/OpenStack/.
|
||||
|
||||
For any further questions, please email
|
||||
openstack-dev@lists.openstack.org or join #openstack-dev on
|
||||
Freenode.
|
||||
285
README.rst
285
README.rst
@@ -1,285 +0,0 @@
|
||||
PyEClib
|
||||
-------
|
||||
|
||||
This library provides a simple Python interface for implementing erasure codes
|
||||
and is known to work with Python v2.6, 2.7 and 3.x.
|
||||
|
||||
To obtain the best possible performance, the library utilizes liberasurecode,
|
||||
which is a C based erasure code library. Please let us know if you have any
|
||||
issues building or installing (email: kmgreen2@gmail.com or tusharsg@gmail.com).
|
||||
|
||||
PyECLib supports a variety of Erasure Coding backends including the standard Reed
|
||||
Soloman implementations provided by Jerasure [1], liberasurecode [3] and Intel
|
||||
ISA-L [4]. It also provides support for a flat XOR-based encoder and decoder
|
||||
(part of liberasurecode) - a class of HD Combination Codes based on "Flat
|
||||
XOR-based erasure codes in storage systems: Constructions, efficient recovery,
|
||||
and tradeoffs" in IEEE MSST 2010[2]). These codes are well-suited to archival
|
||||
use-cases, have a simple construction and require a minimum number of
|
||||
participating disks during single-disk reconstruction (think XOR-based LRC code).
|
||||
|
||||
Examples of using PyECLib are provided in the "tools" directory:
|
||||
|
||||
Command-line encoder::
|
||||
|
||||
tools/pyeclib_encode.py
|
||||
|
||||
Command-line decoder::
|
||||
|
||||
tools/pyeclib_decode.py
|
||||
|
||||
Utility to determine what is needed to reconstruct missing fragments::
|
||||
|
||||
tools/pyeclib_fragments_needed.py
|
||||
|
||||
A configuration utility to help compare available EC schemes in terms of
|
||||
performance and redundancy::
|
||||
|
||||
tools/pyeclib_conf_tool.py
|
||||
|
||||
PyEClib initialization::
|
||||
|
||||
ec_driver = ECDriver(k=<num_encoded_data_fragments>,
|
||||
m=<num_encoded_parity_fragments>,
|
||||
ec_type=<ec_scheme>))
|
||||
|
||||
Supported ``ec_type`` values:
|
||||
|
||||
* ``liberasurecode_rs_vand`` => Vandermonde Reed-Solomon encoding, software-only backend implemented by liberasurecode [3]
|
||||
* ``jerasure_rs_vand`` => Vandermonde Reed-Solomon encoding, based on Jerasure [1]
|
||||
* ``jerasure_rs_cauchy`` => Cauchy Reed-Solomon encoding (Jerasure variant), based on Jerasure [1]
|
||||
* ``flat_xor_hd_3``, ``flat_xor_hd_4`` => Flat-XOR based HD combination codes, liberasurecode [3]
|
||||
* ``isa_l_rs_vand`` => Intel Storage Acceleration Library (ISA-L) - SIMD accelerated Erasure Coding backends [4]
|
||||
* ``isa_l_rs_cauchy`` => Cauchy Reed-Solomon encoding (ISA-L variant) [4]
|
||||
* ``shss`` => NTT Lab Japan's Erasure Coding Library [5]
|
||||
* ``libphazr`` => Phazr.IO's erasure code library with built-in privacy [6]
|
||||
|
||||
|
||||
The Python API supports the following functions:
|
||||
|
||||
- EC Encode
|
||||
|
||||
Encode N bytes of a data object into k (data) + m (parity) fragments::
|
||||
|
||||
def encode(self, data_bytes)
|
||||
|
||||
input: data_bytes - input data object (bytes)
|
||||
returns: list of fragments (bytes)
|
||||
throws:
|
||||
ECBackendInstanceNotAvailable - if the backend library cannot be found
|
||||
ECBackendNotSupported - if the backend is not supported by PyECLib (see ec_types above)
|
||||
ECInvalidParameter - if invalid parameters were provided
|
||||
ECOutOfMemory - if the process has run out of memory
|
||||
ECDriverError - if an unknown error occurs
|
||||
|
||||
- EC Decode
|
||||
|
||||
Decode between k and k+m fragments into original object::
|
||||
|
||||
def decode(self, fragment_payloads)
|
||||
|
||||
input: list of fragment_payloads (bytes)
|
||||
returns: decoded object (bytes)
|
||||
throws:
|
||||
ECBackendInstanceNotAvailable - if the backend library cannot be found
|
||||
ECBackendNotSupported - if the backend is not supported by PyECLib (see ec_types above)
|
||||
ECInvalidParameter - if invalid parameters were provided
|
||||
ECOutOfMemory - if the process has run out of memory
|
||||
ECInsufficientFragments - if an insufficient set of fragments has been provided (e.g. not enough)
|
||||
ECInvalidFragmentMetadata - if the fragment headers appear to be corrupted
|
||||
ECDriverError - if an unknown error occurs
|
||||
|
||||
|
||||
*Note*: ``bytes`` is a synonym to ``str`` in Python 2.6, 2.7.
|
||||
In Python 3.x, ``bytes`` and ``str`` types are non-interchangeable and care
|
||||
needs to be taken when handling input to and output from the ``encode()`` and
|
||||
``decode()`` routines.
|
||||
|
||||
|
||||
- EC Reconstruct
|
||||
|
||||
Reconstruct "missing_fragment_indexes" using "available_fragment_payloads"::
|
||||
|
||||
def reconstruct(self, available_fragment_payloads, missing_fragment_indexes)
|
||||
|
||||
input: available_fragment_payloads - list of fragment payloads
|
||||
input: missing_fragment_indexes - list of indexes to reconstruct
|
||||
output: list of reconstructed fragments corresponding to missing_fragment_indexes
|
||||
throws:
|
||||
ECBackendInstanceNotAvailable - if the backend library cannot be found
|
||||
ECBackendNotSupported - if the backend is not supported by PyECLib (see ec_types above)
|
||||
ECInvalidParameter - if invalid parameters were provided
|
||||
ECOutOfMemory - if the process has run out of memory
|
||||
ECInsufficientFragments - if an insufficient set of fragments has been provided (e.g. not enough)
|
||||
ECInvalidFragmentMetadata - if the fragment headers appear to be corrupted
|
||||
ECDriverError - if an unknown error occurs
|
||||
|
||||
|
||||
- Minimum parity fragments needed for durability gurantees::
|
||||
|
||||
def min_parity_fragments_needed(self)
|
||||
|
||||
NOTE: Currently hard-coded to 1, so this can only be trusted for MDS codes, such as
|
||||
Reed-Solomon.
|
||||
|
||||
output: minimum number of additional fragments needed to be synchronously written to tolerate
|
||||
the loss of any one fragment (similar guarantees to 2 out of 3 with 3x replication)
|
||||
throws:
|
||||
ECBackendInstanceNotAvailable - if the backend library cannot be found
|
||||
ECBackendNotSupported - if the backend is not supported by PyECLib (see ec_types above)
|
||||
ECInvalidParameter - if invalid parameters were provided
|
||||
ECOutOfMemory - if the process has run out of memory
|
||||
ECDriverError - if an unknown error occurs
|
||||
|
||||
|
||||
- Fragments needed for EC Reconstruct
|
||||
|
||||
Return the indexes of fragments needed to reconstruct "missing_fragment_indexes"::
|
||||
|
||||
def fragments_needed(self, missing_fragment_indexes)
|
||||
|
||||
input: list of missing_fragment_indexes
|
||||
output: list of fragments needed to reconstruct fragments listed in missing_fragment_indexes
|
||||
throws:
|
||||
ECBackendInstanceNotAvailable - if the backend library cannot be found
|
||||
ECBackendNotSupported - if the backend is not supported by PyECLib (see ec_types above)
|
||||
ECInvalidParameter - if invalid parameters were provided
|
||||
ECOutOfMemory - if the process has run out of memory
|
||||
ECDriverError - if an unknown error occurs
|
||||
|
||||
|
||||
- Get EC Metadata
|
||||
|
||||
Return an opaque header known by the underlying library or a formatted header (Python dict)::
|
||||
|
||||
def get_metadata(self, fragment, formatted = 0)
|
||||
|
||||
input: raw fragment payload
|
||||
input: boolean specifying if returned header is opaque buffer or formatted string
|
||||
output: fragment header (opaque or formatted)
|
||||
throws:
|
||||
ECBackendInstanceNotAvailable - if the backend library cannot be found
|
||||
ECBackendNotSupported - if the backend is not supported by PyECLib (see ec_types above)
|
||||
ECInvalidParameter - if invalid parameters were provided
|
||||
ECOutOfMemory - if the process has run out of memory
|
||||
ECDriverError - if an unknown error occurs
|
||||
|
||||
- Verify EC Stripe Consistency
|
||||
|
||||
Use opaque buffers from get_metadata() to verify a the consistency of a stripe::
|
||||
|
||||
def verify_stripe_metadata(self, fragment_metadata_list)
|
||||
|
||||
intput: list of opaque fragment headers
|
||||
output: formatted string containing the 'status' (0 is success) and 'reason' if verification fails
|
||||
throws:
|
||||
ECBackendInstanceNotAvailable - if the backend library cannot be found
|
||||
ECBackendNotSupported - if the backend is not supported by PyECLib (see ec_types above)
|
||||
ECInvalidParameter - if invalid parameters were provided
|
||||
ECOutOfMemory - if the process has run out of memory
|
||||
ECDriverError - if an unknown error occurs
|
||||
|
||||
|
||||
- Get EC Segment Info
|
||||
|
||||
Return a dict with the keys - segment_size, last_segment_size, fragment_size, last_fragment_size and num_segments::
|
||||
|
||||
def get_segment_info(self, data_len, segment_size)
|
||||
|
||||
input: total data_len of the object to store
|
||||
input: target segment size used to segment the object into multiple EC stripes
|
||||
output: a dict with keys - segment_size, last_segment_size, fragment_size, last_fragment_size and num_segments
|
||||
throws:
|
||||
ECBackendInstanceNotAvailable - if the backend library cannot be found
|
||||
ECBackendNotSupported - if the backend is not supported by PyECLib (see ec_types above)
|
||||
ECInvalidParameter - if invalid parameters were provided
|
||||
ECOutOfMemory - if the process has run out of memory
|
||||
ECDriverError - if an unknown error occurs
|
||||
|
||||
|
||||
- Get EC Segment Info given a list of ranges, data length and segment size::
|
||||
|
||||
def get_segment_info_byterange(self, ranges, data_len, segment_size)
|
||||
|
||||
input: byte ranges
|
||||
input: total data_len of the object to store
|
||||
input: target segment size used to segment the object into multiple EC stripes
|
||||
output: (see below)
|
||||
throws:
|
||||
ECBackendInstanceNotAvailable - if the backend library cannot be found
|
||||
ECBackendNotSupported - if the backend is not supported by PyECLib (see ec_types above)
|
||||
ECInvalidParameter - if invalid parameters were provided
|
||||
ECOutOfMemory - if the process has run out of memory
|
||||
ECDriverError - if an unknown error occurs
|
||||
|
||||
Assume a range request is given for an object with segment size 3K and
|
||||
a 1 MB file::
|
||||
|
||||
Ranges = (0, 1), (1, 12), (10, 1000), (0, segment_size-1),
|
||||
(1, segment_size+1), (segment_size-1, 2*segment_size)
|
||||
|
||||
This will return a map keyed on the ranges, where there is a recipe
|
||||
given for each range::
|
||||
|
||||
{
|
||||
(0, 1): {0: (0, 1)},
|
||||
(10, 1000): {0: (10, 1000)},
|
||||
(1, 12): {0: (1, 12)},
|
||||
(0, 3071): {0: (0, 3071)},
|
||||
(3071, 6144): {0: (3071, 3071), 1: (0, 3071), 2: (0, 0)},
|
||||
(1, 3073): {0: (1, 3071), 1: (0,0)}
|
||||
}
|
||||
|
||||
|
||||
Quick Start
|
||||
|
||||
Install pre-requisites::
|
||||
|
||||
* Python 2.6, 2.7 or 3.x (including development packages), argparse, setuptools
|
||||
* liberasurecode v1.2.0 or greater [3]
|
||||
* Erasure code backend libraries, gf-complete and Jerasure [1],[2], ISA-L [4] etc
|
||||
|
||||
An example for ubuntu to install dependency packages::
|
||||
|
||||
$ sudo apt-get install build-essential python-dev python-pip liberasurecode-dev
|
||||
$ sudo pip install -U bindep -r test-requirements.txt
|
||||
|
||||
If you want to confirm all dependency packages installed successfully, try::
|
||||
|
||||
$ sudo bindep -f bindep.txt
|
||||
|
||||
*Note*: currently liberasurecode-dev/liberasurecode-devel in package repo is older than v1.2.0
|
||||
|
||||
Install PyECLib::
|
||||
|
||||
$ sudo python setup.py install
|
||||
|
||||
Run test suite included::
|
||||
|
||||
$ ./.unittests
|
||||
|
||||
If all of this works, then you should be good to go. If not, send us an email!
|
||||
|
||||
If the test suite fails because it cannot find any of the shared libraries,
|
||||
then you probably need to add /usr/local/lib to the path searched when loading
|
||||
libraries. The best way to do this (on Linux) is to add '/usr/local/lib' to::
|
||||
|
||||
/etc/ld.so.conf
|
||||
|
||||
and then make sure to run::
|
||||
|
||||
$ sudo ldconfig
|
||||
|
||||
|
||||
References
|
||||
|
||||
[1] Jerasure, C library that supports erasure coding in storage applications, http://jerasure.org
|
||||
|
||||
[2] Greenan, Kevin M et al, "Flat XOR-based erasure codes in storage systems", http://www.kaymgee.com/Kevin_Greenan/Publications_files/greenan-msst10.pdf
|
||||
|
||||
[3] liberasurecode, C API abstraction layer for erasure coding backends, https://github.com/openstack/liberasurecode
|
||||
|
||||
[4] Intel(R) Storage Acceleration Library (Open Source Version), https://01.org/intel%C2%AE-storage-acceleration-library-open-source-version
|
||||
|
||||
[5] Kota Tsuyuzaki <tsuyuzaki.kota@lab.ntt.co.jp>, "NTT SHSS Erasure Coding backend"
|
||||
|
||||
[6] Jim Cheung <support@phazr.io>, "Phazr.IO libphazr erasure code backend with built-in privacy"
|
||||
10
bindep.txt
10
bindep.txt
@@ -1,10 +0,0 @@
|
||||
# This is a cross-platform list tracking distribution packages needed by tests;
|
||||
# see http://docs.openstack.org/infra/bindep/ for additional information.
|
||||
|
||||
build-essential [platform:dpkg]
|
||||
gcc [platform:rpm]
|
||||
make [platform:rpm]
|
||||
liberasurecode-dev [platform:dpkg]
|
||||
liberasurecode-devel [platform:rpm]
|
||||
python-dev [platform:dpkg]
|
||||
python-devel [platform:rpm]
|
||||
@@ -1,242 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# PyECLib documentation build configuration file, created by
|
||||
# sphinx-quickstart on Thu Aug 11 19:59:50 2016.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = []
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'PyECLib'
|
||||
copyright = u'2016, Kevin Greenan, Tushar Gohad, Kota Tsuyuzaki'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '1.5.0'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '1.5.0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = []
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'default'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'PyECLibdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'PyECLib.tex', u'PyECLib Documentation',
|
||||
u'Kevin Greenan, Tushar Gohad, Kota Tsuyuzaki', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'pyeclib', u'PyECLib Documentation',
|
||||
[u'Kevin Greenan, Tushar Gohad, Kota Tsuyuzaki'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output ------------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'PyECLib', u'PyECLib Documentation',
|
||||
u'Kevin Greenan, Tushar Gohad, Kota Tsuyuzaki', 'PyECLib', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
@@ -1,22 +0,0 @@
|
||||
.. PyECLib documentation master file, created by
|
||||
sphinx-quickstart on Thu Aug 11 19:59:50 2016.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to PyECLib's documentation!
|
||||
===================================
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
# Copyright (c) 2013, Kevin Greenan (kmgreen2@gmail.com)
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution. THIS SOFTWARE IS
|
||||
# PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
|
||||
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
# NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
289
pyeclib/core.py
289
pyeclib/core.py
@@ -1,289 +0,0 @@
|
||||
# Copyright (c) 2013, 2014, Kevin Greenan (kmgreen2@gmail.com)
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution. THIS SOFTWARE IS
|
||||
# PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
|
||||
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
# NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from .ec_iface import ECDriverError
|
||||
from .ec_iface import ECInsufficientFragments
|
||||
from .ec_iface import PyECLib_FRAGHDRCHKSUM_Types
|
||||
|
||||
import math
|
||||
import pyeclib_c
|
||||
import sys
|
||||
|
||||
pyver = float('%s.%s' % sys.version_info[:2])
|
||||
|
||||
|
||||
class ECPyECLibDriver(object):
|
||||
|
||||
def __init__(self, k, m, hd, ec_type,
|
||||
chksum_type=PyECLib_FRAGHDRCHKSUM_Types.none,
|
||||
validate=False):
|
||||
self.k = k
|
||||
self.m = m
|
||||
self.hd = hd
|
||||
self.ec_type = ec_type
|
||||
self.chksum_type = chksum_type
|
||||
|
||||
self.inline_chksum = 0
|
||||
self.algsig_chksum = 0
|
||||
# crc32 is the only inline checksum type currently supported
|
||||
if self.chksum_type is PyECLib_FRAGHDRCHKSUM_Types.inline_crc32:
|
||||
self.inline_chksum = 1
|
||||
|
||||
name = self.ec_type.name
|
||||
|
||||
self.handle = pyeclib_c.init(
|
||||
self.k,
|
||||
self.m,
|
||||
ec_type.value,
|
||||
self.hd,
|
||||
self.inline_chksum,
|
||||
self.algsig_chksum,
|
||||
validate)
|
||||
|
||||
def encode(self, data_bytes):
|
||||
return pyeclib_c.encode(self.handle, data_bytes)
|
||||
|
||||
def _validate_and_return_fragment_size(self, fragments):
|
||||
if len(fragments) == 0 or len(fragments[0]) == 0:
|
||||
return -1
|
||||
fragment_len = len(fragments[0])
|
||||
for fragment in fragments[1:]:
|
||||
if len(fragment) != fragment_len:
|
||||
return -1
|
||||
return fragment_len
|
||||
|
||||
def decode(self, fragment_payloads, ranges=None,
|
||||
force_metadata_checks=False):
|
||||
_fragment_payloads = list(fragment_payloads)
|
||||
fragment_len = self._validate_and_return_fragment_size(
|
||||
_fragment_payloads)
|
||||
if fragment_len < 0:
|
||||
raise ECDriverError(
|
||||
"Invalid fragment payload in ECPyECLibDriver.decode")
|
||||
|
||||
if len(_fragment_payloads) < self.k:
|
||||
raise ECInsufficientFragments(
|
||||
"Not enough fragments given in ECPyECLibDriver.decode")
|
||||
|
||||
return pyeclib_c.decode(self.handle, _fragment_payloads,
|
||||
fragment_len, ranges, force_metadata_checks)
|
||||
|
||||
def reconstruct(self, fragment_payloads, indexes_to_reconstruct):
|
||||
_fragment_payloads = list(fragment_payloads)
|
||||
fragment_len = self._validate_and_return_fragment_size(
|
||||
_fragment_payloads)
|
||||
if fragment_len < 0:
|
||||
raise ECDriverError(
|
||||
"Invalid fragment payload in ECPyECLibDriver.reconstruct")
|
||||
|
||||
reconstructed_data = []
|
||||
|
||||
# Reconstruct the data, then the parity
|
||||
# The parity cannot be reconstructed until
|
||||
# after all data is reconstructed
|
||||
indexes_to_reconstruct.sort()
|
||||
_indexes_to_reconstruct = indexes_to_reconstruct[:]
|
||||
|
||||
while len(_indexes_to_reconstruct) > 0:
|
||||
index = _indexes_to_reconstruct.pop(0)
|
||||
reconstructed = pyeclib_c.reconstruct(
|
||||
self.handle, _fragment_payloads, fragment_len, index)
|
||||
reconstructed_data.append(reconstructed)
|
||||
_fragment_payloads.append(reconstructed)
|
||||
|
||||
return reconstructed_data
|
||||
|
||||
def fragments_needed(self, reconstruct_indexes, exclude_indexes):
|
||||
required_fragments = pyeclib_c.get_required_fragments(
|
||||
self.handle, reconstruct_indexes, exclude_indexes)
|
||||
return required_fragments
|
||||
|
||||
def min_parity_fragments_needed(self):
|
||||
""" FIXME - fix this to return a function of HD """
|
||||
return 1
|
||||
|
||||
def get_metadata(self, fragment, formatted=0):
|
||||
fragment_metadata = pyeclib_c.get_metadata(self.handle, fragment,
|
||||
formatted)
|
||||
return fragment_metadata
|
||||
|
||||
def verify_stripe_metadata(self, fragment_metadata_list):
|
||||
success = pyeclib_c.check_metadata(self.handle, fragment_metadata_list)
|
||||
return success
|
||||
|
||||
def get_segment_info(self, data_len, segment_size):
|
||||
segment_info = pyeclib_c.get_segment_info(self.handle, data_len,
|
||||
segment_size)
|
||||
return segment_info
|
||||
|
||||
|
||||
class ECNullDriver(object):
|
||||
|
||||
def __init__(self, k, m, hd, ec_type=None, chksum_type=None,
|
||||
validate=False):
|
||||
self.k = k
|
||||
self.m = m
|
||||
self.hd = hd
|
||||
|
||||
def encode(self, data_bytes):
|
||||
pass
|
||||
|
||||
def decode(self, fragment_payloads, ranges, force_metadata_checks):
|
||||
pass
|
||||
|
||||
def reconstruct(self, available_fragment_payloads,
|
||||
missing_fragment_indexes):
|
||||
pass
|
||||
|
||||
def fragments_needed(self, missing_fragment_indexes):
|
||||
pass
|
||||
|
||||
def get_metadata(self, fragment, formatted=0):
|
||||
pass
|
||||
|
||||
def min_parity_fragments_needed(self):
|
||||
pass
|
||||
|
||||
def verify_stripe_metadata(self, fragment_metadata_list):
|
||||
pass
|
||||
|
||||
def get_segment_info(self, data_len, segment_size):
|
||||
pass
|
||||
|
||||
|
||||
#
|
||||
# A striping-only driver for EC. This is
|
||||
# pretty much RAID 0.
|
||||
#
|
||||
class ECStripingDriver(object):
|
||||
|
||||
def __init__(self, k, m, hd, ec_type=None, chksum_type=None,
|
||||
validate=False):
|
||||
"""Stripe an arbitrary-sized string into k fragments
|
||||
:param k: the number of data fragments to stripe
|
||||
:param m: the number of parity fragments to stripe
|
||||
:raises: ECDriverError if there is an error during encoding
|
||||
"""
|
||||
self.k = k
|
||||
|
||||
if m != 0:
|
||||
raise ECDriverError("This driver only supports m=0")
|
||||
|
||||
self.m = m
|
||||
self.hd = hd
|
||||
|
||||
def encode(self, data_bytes):
|
||||
"""Stripe an arbitrary-sized string into k fragments
|
||||
:param data_bytes: the buffer to encode
|
||||
:returns: a list of k buffers (data only)
|
||||
:raises: ECDriverError if there is an error during encoding
|
||||
"""
|
||||
# Main fragment size
|
||||
fragment_size = math.ceil(len(data_bytes) / float(self.k))
|
||||
|
||||
# Size of last fragment
|
||||
last_fragment_size = len(data_bytes) - (fragment_size * self.k - 1)
|
||||
|
||||
fragments = []
|
||||
offset = 0
|
||||
for i in range(self.k - 1):
|
||||
fragments.append(data_bytes[offset:fragment_size])
|
||||
offset += fragment_size
|
||||
|
||||
fragments.append(data_bytes[offset:last_fragment_size])
|
||||
|
||||
return fragments
|
||||
|
||||
def decode(self, fragment_payloads, ranges=None,
|
||||
force_metadata_checks=False):
|
||||
"""Convert a k-fragment data stripe into a string
|
||||
:param fragment_payloads: fragments (in order) to convert into a string
|
||||
:param ranges (unsupported): range decode
|
||||
:param force_metadata_checks (unsupported): verify fragment metadata
|
||||
:returns: a string containing original data
|
||||
:raises: ECDriverError if there is an error during decoding
|
||||
"""
|
||||
if ranges is not None:
|
||||
raise ECDriverError("Decode does not support range requests in the"
|
||||
" striping driver.")
|
||||
if force_metadata_checks is not False:
|
||||
raise ECDriverError(
|
||||
"Decode does not support metadata integrity checks in the "
|
||||
" striping driver.")
|
||||
if len(fragment_payloads) != self.k:
|
||||
raise ECInsufficientFragments(
|
||||
"Decode requires %d fragments, %d fragments were given" %
|
||||
(len(fragment_payloads), self.k))
|
||||
|
||||
ret_string = ''
|
||||
|
||||
for fragment in fragment_payloads:
|
||||
ret_string += fragment
|
||||
|
||||
return ret_string
|
||||
|
||||
def reconstruct(self, available_fragment_payloads,
|
||||
missing_fragment_indexes):
|
||||
"""We cannot reconstruct a fragment using other fragments. This means
|
||||
that reconstruction means all fragments must be specified, otherwise we
|
||||
cannot reconstruct and must raise an error.
|
||||
:param available_fragment_payloads: available fragments (in order)
|
||||
:param missing_fragment_indexes: indexes of missing fragments
|
||||
:returns: a string containing the original data
|
||||
:raises: ECDriverError if there is an error during reconstruction
|
||||
"""
|
||||
if len(available_fragment_payloads) != self.k:
|
||||
raise ECDriverError(
|
||||
"Reconstruction requires %d fragments, %d fragments given" %
|
||||
(len(available_fragment_payloads), self.k))
|
||||
|
||||
return available_fragment_payloads
|
||||
|
||||
def fragments_needed(self, missing_fragment_indexes):
|
||||
"""By definition, all missing fragment indexes are needed to
|
||||
reconstruct, so just return the list handed to this function.
|
||||
:param missing_fragment_indexes: indexes of missing fragments
|
||||
:returns: missing_fragment_indexes
|
||||
"""
|
||||
return missing_fragment_indexes
|
||||
|
||||
def min_parity_fragments_needed(self):
|
||||
pass
|
||||
|
||||
def get_metadata(self, fragment, formatted=0):
|
||||
"""This driver does not include fragment metadata, so return empty
|
||||
string
|
||||
:param fragment: a fragment
|
||||
:returns: empty string
|
||||
"""
|
||||
return ''
|
||||
|
||||
def verify_stripe_metadata(self, fragment_metadata_list):
|
||||
"""This driver does not include fragment metadata, so return true
|
||||
:param fragment_metadata_list: a list of fragments
|
||||
:returns: True
|
||||
"""
|
||||
return True
|
||||
|
||||
def get_segment_info(self, data_len, segment_size):
|
||||
pass
|
||||
@@ -1,559 +0,0 @@
|
||||
# Copyright (c) 2013-2014, Kevin Greenan (kmgreen2@gmail.com)
|
||||
# Copyright (c) 2014, Tushar Gohad (tushar.gohad@intel.com)
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution. THIS SOFTWARE IS
|
||||
# PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
|
||||
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
# NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from .enum import Enum
|
||||
from .enum import unique
|
||||
from .utils import create_instance
|
||||
from .utils import positive_int_value
|
||||
from pyeclib_c import get_liberasurecode_version
|
||||
|
||||
import logging
|
||||
from logging.handlers import SysLogHandler
|
||||
logger = logging.getLogger('pyeclib')
|
||||
syslog_handler = SysLogHandler()
|
||||
logger.addHandler(syslog_handler)
|
||||
|
||||
|
||||
def check_backend_available(backend_name):
|
||||
try:
|
||||
from pyeclib_c import check_backend_available
|
||||
|
||||
if backend_name.startswith('flat_xor_hd'):
|
||||
int_type = PyECLib_EC_Types.get_by_name('flat_xor_hd')
|
||||
else:
|
||||
int_type = PyECLib_EC_Types.get_by_name(backend_name)
|
||||
if not int_type:
|
||||
return False
|
||||
return check_backend_available(int_type.value)
|
||||
except ImportError:
|
||||
# check_backend_available has been supported since
|
||||
# liberasurecode>=1.2.0 so we need to define the func for older
|
||||
# liberasurecode version
|
||||
|
||||
# select available k, m values
|
||||
if backend_name.startswith('flat_xor_hd'):
|
||||
k, m = (10, 5)
|
||||
else:
|
||||
k, m = (10, 4)
|
||||
try:
|
||||
driver = ECDriver(ec_type=backend_name, k=k, m=m)
|
||||
except ECDriverError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def PyECLibVersion(z, y, x):
|
||||
return (((z) << 16) + ((y) << 8) + (x))
|
||||
|
||||
PYECLIB_MAJOR = 1
|
||||
PYECLIB_MINOR = 1
|
||||
PYECLIB_REV = 2
|
||||
PYECLIB_VERSION = PyECLibVersion(PYECLIB_MAJOR, PYECLIB_MINOR,
|
||||
PYECLIB_REV)
|
||||
|
||||
|
||||
PYECLIB_MAX_DATA = 32
|
||||
PYECLIB_MAX_PARITY = 32
|
||||
|
||||
|
||||
@unique
|
||||
class PyECLibEnum(Enum):
|
||||
|
||||
def describe(self):
|
||||
# returns supported types
|
||||
return list(self)
|
||||
|
||||
@classmethod
|
||||
def has_enum(cls, name):
|
||||
# returns True if name is a valid member of the enum
|
||||
try:
|
||||
cls.__getattr__(name)
|
||||
except AttributeError:
|
||||
return False
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def get_by_name(cls, name):
|
||||
try:
|
||||
obj = cls.__getattr__(name)
|
||||
except AttributeError:
|
||||
return None
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def names(cls):
|
||||
return [name for name, value in cls.__members__.items()]
|
||||
|
||||
@classmethod
|
||||
def values(cls):
|
||||
return [value for name, value in cls.__members__.items()]
|
||||
|
||||
def __str__(self):
|
||||
return "%s: %d" % (self.name, self.value)
|
||||
|
||||
|
||||
# Erasure Code backends supported as of this PyECLib API rev
|
||||
class PyECLib_EC_Types(PyECLibEnum):
|
||||
# Note: the Enum start value defaults to 1 as the starting value and not 0
|
||||
# 0 is False in the boolean sense but enum members evaluate to True
|
||||
jerasure_rs_vand = 1
|
||||
jerasure_rs_cauchy = 2
|
||||
flat_xor_hd = 3
|
||||
isa_l_rs_vand = 4
|
||||
shss = 5
|
||||
liberasurecode_rs_vand = 6
|
||||
isa_l_rs_cauchy = 7
|
||||
libphazr = 8
|
||||
|
||||
|
||||
# Output of Erasure (en)Coding process are data "fragments". Fragment data
|
||||
# integrity checks are provided by a checksum embedded in a header (prepend)
|
||||
# for each fragment.
|
||||
|
||||
# The following Enum defines the schemes supported for fragment checksums.
|
||||
# The checksum type is "none" unless specified.
|
||||
class PyECLib_FRAGHDRCHKSUM_Types(PyECLibEnum):
|
||||
# Note: the Enum start value defaults to 1 as the starting value and not 0
|
||||
# 0 is False in the boolean sense but enum members evaluate to True
|
||||
none = 1
|
||||
inline_crc32 = 2
|
||||
|
||||
|
||||
# Main ECDriver class
|
||||
class ECDriver(object):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.k = -1
|
||||
self.m = -1
|
||||
self.hd = -1
|
||||
self.ec_type = None
|
||||
self.chksum_type = None
|
||||
self.validate = False
|
||||
|
||||
for required in ('k', 'm'):
|
||||
if required not in kwargs:
|
||||
raise ECDriverError(
|
||||
"Invalid Argument: %s is required" % required)
|
||||
|
||||
for (key, value) in kwargs.items():
|
||||
if key == "k":
|
||||
try:
|
||||
self.k = positive_int_value(value)
|
||||
except ValueError:
|
||||
raise ECDriverError(
|
||||
"Invalid number of data fragments (k)")
|
||||
elif key == "m":
|
||||
try:
|
||||
self.m = positive_int_value(value)
|
||||
except ValueError:
|
||||
raise ECDriverError(
|
||||
"Invalid number of parity fragments (m)")
|
||||
elif key == "ec_type":
|
||||
if value in ["flat_xor_hd", "flat_xor_hd_3", "flat_xor_hd_4"]:
|
||||
if value == "flat_xor_hd" or value == "flat_xor_hd_3":
|
||||
self.hd = 3
|
||||
elif value == "flat_xor_hd_4":
|
||||
self.hd = 4
|
||||
value = "flat_xor_hd"
|
||||
elif value == "libphazr":
|
||||
self.hd = 1
|
||||
if PyECLib_EC_Types.has_enum(value):
|
||||
self.ec_type = PyECLib_EC_Types.get_by_name(value)
|
||||
else:
|
||||
raise ECBackendNotSupported(
|
||||
"%s is not a valid EC type for PyECLib!" % value)
|
||||
elif key == "chksum_type":
|
||||
if PyECLib_FRAGHDRCHKSUM_Types.has_enum(value):
|
||||
self.chksum_type = \
|
||||
PyECLib_FRAGHDRCHKSUM_Types.get_by_name(value)
|
||||
else:
|
||||
raise ECDriverError(
|
||||
"%s is not a valid checksum type for PyECLib!" % value)
|
||||
elif key == "validate":
|
||||
# validate if the ec type is available (runtime check)
|
||||
self.validate = value
|
||||
|
||||
if self.hd == -1:
|
||||
self.hd = self.m
|
||||
|
||||
self.library_import_str = kwargs.pop('library_import_str',
|
||||
'pyeclib.core.ECPyECLibDriver')
|
||||
#
|
||||
# Instantiate EC backend driver
|
||||
#
|
||||
self.ec_lib_reference = create_instance(
|
||||
self.library_import_str,
|
||||
k=self.k,
|
||||
m=self.m,
|
||||
hd=self.hd,
|
||||
ec_type=self.ec_type,
|
||||
chksum_type=self.chksum_type,
|
||||
validate=int(self.validate)
|
||||
)
|
||||
|
||||
#
|
||||
# Verify that the imported library implements the required functions
|
||||
#
|
||||
required_methods = [
|
||||
'decode',
|
||||
'encode',
|
||||
'reconstruct',
|
||||
'fragments_needed',
|
||||
'min_parity_fragments_needed',
|
||||
'get_metadata',
|
||||
'verify_stripe_metadata',
|
||||
'get_segment_info',
|
||||
]
|
||||
|
||||
missing_methods = ' '.join(
|
||||
method for method in required_methods
|
||||
if not callable(getattr(self.ec_lib_reference, method, None)))
|
||||
|
||||
if missing_methods:
|
||||
raise ECDriverError(
|
||||
"The following required methods are not implemented "
|
||||
"in %s: %s" % (self.library_import_str, missing_methods))
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(ec_type=%r, k=%r, m=%r)' % (
|
||||
type(self).__name__,
|
||||
'flat_xor_hd_%s' % self.hd if self.ec_type.name == 'flat_xor_hd'
|
||||
else self.ec_type.name,
|
||||
self.k,
|
||||
self.m)
|
||||
|
||||
def encode(self, data_bytes):
|
||||
"""
|
||||
Encode an arbitrary-sized string
|
||||
:param data_bytes: the buffer to encode
|
||||
:returns: a list of buffers (first k entries are data and
|
||||
the last m are parity)
|
||||
:raises: ECDriverError if there is an error during encoding
|
||||
"""
|
||||
return self.ec_lib_reference.encode(data_bytes)
|
||||
|
||||
def decode(self, fragment_payloads, ranges=None,
|
||||
force_metadata_checks=False):
|
||||
"""
|
||||
Decode a set of fragments into a buffer that represents the original
|
||||
buffer passed into encode().
|
||||
|
||||
:param fragment_payloads: a list of buffers representing a subset of
|
||||
the list generated by encode()
|
||||
:param ranges (optional): a list of byte ranges to return instead of
|
||||
the entire buffer
|
||||
:param force_metadata_checks (optional): validate collective integrity
|
||||
of the fragments before trying to decode
|
||||
:returns: a buffer
|
||||
:raises: ECDriverError if there is an error during decoding
|
||||
"""
|
||||
return self.ec_lib_reference.decode(fragment_payloads, ranges,
|
||||
force_metadata_checks)
|
||||
|
||||
def reconstruct(self, available_fragment_payloads,
|
||||
missing_fragment_indexes):
|
||||
"""
|
||||
Reconstruct a missing fragment from a subset of available fragments.
|
||||
|
||||
:param available_fragment_payloads: a list of buffers representing
|
||||
a subset of the list generated
|
||||
by encode()
|
||||
:param missing_fragment_indexes: a list of integers representing
|
||||
the indexes of the fragments to be
|
||||
reconstructed.
|
||||
:param destination_index: the index of the element to reconstruct
|
||||
:returns: a list of buffers (ordered by fragment index) containing
|
||||
the reconstructed payload associated with the indexes
|
||||
provided in missing_fragment_indexes
|
||||
:raises: ECDriverError if there is an error during decoding or there
|
||||
are not sufficient fragments to decode
|
||||
"""
|
||||
return self.ec_lib_reference.reconstruct(
|
||||
available_fragment_payloads, missing_fragment_indexes)
|
||||
|
||||
def fragments_needed(self, reconstruction_indexes,
|
||||
exclude_indexes=[]):
|
||||
"""
|
||||
Determine which fragments are needed to reconstruct some subset of
|
||||
missing fragments.
|
||||
|
||||
:param reconstruction_indexes: a list of integers representing the
|
||||
indexes of the fragments to be
|
||||
reconstructed.
|
||||
:param exclude_indexes: a list of integers representing the
|
||||
indexes of the fragments to be
|
||||
excluded from the reconstruction
|
||||
equations.
|
||||
:returns: a list containing fragment indexes that can be used to
|
||||
reconstruct the missing fragments.
|
||||
:raises: ECDriverError if there is an error during decoding or there
|
||||
are not sufficient fragments to decode
|
||||
"""
|
||||
return self.ec_lib_reference.fragments_needed(reconstruction_indexes,
|
||||
exclude_indexes)
|
||||
|
||||
def min_parity_fragments_needed(self):
|
||||
return self.ec_lib_reference.min_parity_fragments_needed()
|
||||
|
||||
def get_metadata(self, fragment, formatted=0):
|
||||
"""
|
||||
Get opaque metadata for a fragment. The metadata is opaque to the
|
||||
client, but meaningful to the underlying library. It is used to verify
|
||||
stripes in verify_stripe_metadata().
|
||||
|
||||
:param fragment: a buffer representing a single fragment generated by
|
||||
the encode() function.
|
||||
:returns: an opaque buffer to be passed into verify_stripe_metadata()
|
||||
:raises: ECDriverError if there was a problem getting the metadata.
|
||||
"""
|
||||
return self.ec_lib_reference.get_metadata(fragment, formatted)
|
||||
|
||||
def verify_stripe_metadata(self, fragment_metadata_list):
|
||||
"""
|
||||
Verify a subset of fragments generated by encode()
|
||||
|
||||
:param fragment_metadata_list: a list of buffers representing the
|
||||
metadata from a subset of the framgments
|
||||
generated by encode().
|
||||
:returns: 'None' if the metadata is consistent.
|
||||
a list of fragment indexes corresponding to inconsistent
|
||||
fragments
|
||||
:raises: ECDriverError if there was a problem verifying the metadata
|
||||
|
||||
"""
|
||||
return self.ec_lib_reference.verify_stripe_metadata(
|
||||
fragment_metadata_list)
|
||||
|
||||
def get_segment_info(self, data_len, segment_size):
|
||||
"""
|
||||
Get segmentation info for a given data length and
|
||||
segment size.
|
||||
|
||||
Semment info returns a dict with the following keys:
|
||||
|
||||
segment_size: size of the payload to give to encode()
|
||||
last_segment_size: size of the payload to give to encode()
|
||||
fragment_size: the fragment size returned by encode()
|
||||
last_fragment_size: the fragment size returned by encode()
|
||||
num_segments: number of segments
|
||||
|
||||
This allows the caller to prepare requests
|
||||
when segmenting a data stream to be EC'd.
|
||||
|
||||
Since the data length will rarely be aligned
|
||||
to the segment size, the last segment will be
|
||||
a different size than the others.
|
||||
|
||||
There are restrictions on the length given to encode(),
|
||||
so calling this before encode is highly recommended when
|
||||
segmenting a data stream.
|
||||
"""
|
||||
return self.ec_lib_reference.get_segment_info(data_len, segment_size)
|
||||
|
||||
#
|
||||
# Map of segment indexes with a list of tuples
|
||||
#
|
||||
def get_segment_info_byterange(self, ranges, data_len, segment_size):
|
||||
"""
|
||||
Get segmentation info for a byterange request, given a data length and
|
||||
segment size.
|
||||
|
||||
This will return a map-of-maps that represents a recipe describing
|
||||
the segments and ranges within each segment needed to satisfy a range
|
||||
request.
|
||||
|
||||
Assume a range request is given for an object with segment size 3K and
|
||||
a 1 MB file:
|
||||
|
||||
Ranges = (0, 1), (1, 12), (10, 1000), (0, segment_size-1),
|
||||
(1, segment_size+1), (segment_size-1, 2*segment_size)
|
||||
|
||||
This will return a map keyed on the ranges, where there is a recipe
|
||||
given for each range:
|
||||
|
||||
{
|
||||
(0, 1): {0: (0, 1)},
|
||||
(10, 1000): {0: (10, 1000)},
|
||||
(1, 12): {0: (1, 12)},
|
||||
(0, 3071): {0: (0, 3071)},
|
||||
(3071, 6144): {0: (3071, 3071), 1: (0, 3071), 2: (0, 0)},
|
||||
(1, 3073): {0: (1, 3071), 1: (0,0)}
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
segment_info = self.ec_lib_reference.get_segment_info(
|
||||
data_len, segment_size)
|
||||
|
||||
segment_size = segment_info['segment_size']
|
||||
|
||||
sorted_ranges = ranges[:]
|
||||
sorted_ranges.sort(key=lambda obj: obj[0])
|
||||
|
||||
recipe = {}
|
||||
|
||||
for r in ranges:
|
||||
segment_map = {}
|
||||
begin_off = r[0]
|
||||
end_off = r[1]
|
||||
begin_segment = begin_off // segment_size
|
||||
end_segment = end_off // segment_size
|
||||
|
||||
if begin_segment == end_segment:
|
||||
begin_relative_off = begin_off % segment_size
|
||||
end_relative_off = end_off % segment_size
|
||||
segment_map[begin_segment] = (begin_relative_off,
|
||||
end_relative_off)
|
||||
else:
|
||||
begin_relative_off = begin_off % segment_size
|
||||
end_relative_off = end_off % segment_size
|
||||
|
||||
segment_map[begin_segment] = (begin_relative_off,
|
||||
segment_size - 1)
|
||||
|
||||
for middle_segment in range(begin_segment + 1, end_segment):
|
||||
segment_map[middle_segment] = (0, segment_size - 1)
|
||||
|
||||
segment_map[end_segment] = (0, end_relative_off)
|
||||
|
||||
recipe[r] = segment_map
|
||||
|
||||
return recipe
|
||||
|
||||
|
||||
# PyECLib Exceptions
|
||||
|
||||
# Generic ECDriverException
|
||||
class ECDriverError(Exception):
|
||||
def __init__(self, error):
|
||||
try:
|
||||
self.error_str = str(error)
|
||||
except Exception:
|
||||
self.error_str = 'Error retrieving the error message from %s' \
|
||||
% error.__class__.__name__
|
||||
|
||||
def __str__(self):
|
||||
return self.error_str
|
||||
|
||||
|
||||
# More specific exceptions, mapped to liberasurecode error codes
|
||||
|
||||
# Specified EC backend is not supported by PyECLib/liberasurecode
|
||||
class ECBackendNotSupported(ECDriverError):
|
||||
pass
|
||||
|
||||
|
||||
# Unsupported EC method
|
||||
class ECMethodNotImplemented(ECDriverError):
|
||||
pass
|
||||
|
||||
|
||||
# liberasurecode backend init error
|
||||
class ECBackendInitializationError(ECDriverError):
|
||||
pass
|
||||
|
||||
|
||||
# Specified backend instance is invalid/unavailable
|
||||
class ECBackendInstanceNotAvailable(ECDriverError):
|
||||
pass
|
||||
|
||||
|
||||
# Specified backend instance is busy
|
||||
class ECBackendInstanceInUse(ECDriverError):
|
||||
pass
|
||||
|
||||
|
||||
# Invalid parameter passed to a method
|
||||
class ECInvalidParameter(ECDriverError):
|
||||
pass
|
||||
|
||||
|
||||
# Invalid or incompatible fragment metadata
|
||||
class ECInvalidFragmentMetadata(ECDriverError):
|
||||
pass
|
||||
|
||||
|
||||
# Fragment checksum verification failed
|
||||
class ECBadFragmentChecksum(ECDriverError):
|
||||
pass
|
||||
|
||||
|
||||
# Insufficient fragments specified for decode or reconstruct operation
|
||||
class ECInsufficientFragments(ECDriverError):
|
||||
pass
|
||||
|
||||
|
||||
# Out of memory
|
||||
class ECOutOfMemory(ECDriverError):
|
||||
pass
|
||||
|
||||
|
||||
# PyECLib helper for "available" EC types
|
||||
ALL_EC_TYPES = [
|
||||
'jerasure_rs_vand',
|
||||
'jerasure_rs_cauchy',
|
||||
'flat_xor_hd_3',
|
||||
'flat_xor_hd_4',
|
||||
'isa_l_rs_vand',
|
||||
'shss',
|
||||
'liberasurecode_rs_vand',
|
||||
'isa_l_rs_cauchy',
|
||||
'libphazr',
|
||||
]
|
||||
|
||||
|
||||
def _PyECLibValidECTypes():
|
||||
available_ec_types = []
|
||||
for _type in ALL_EC_TYPES:
|
||||
if check_backend_available(_type):
|
||||
available_ec_types.append(_type)
|
||||
return available_ec_types
|
||||
|
||||
|
||||
VALID_EC_TYPES = _PyECLibValidECTypes()
|
||||
|
||||
|
||||
def _liberasurecode_version():
|
||||
version_int = get_liberasurecode_version()
|
||||
version_hex_str = hex(version_int)
|
||||
version_hex_str = version_hex_str.lstrip('0x')
|
||||
major = str(int(version_hex_str[-6:-4]))
|
||||
minor = str(int(version_hex_str[-4:-2]))
|
||||
rev = str(int(version_hex_str[-2:]))
|
||||
version_str = '.'.join([major, minor, rev])
|
||||
|
||||
# liberasurecode < 1.3.1 should be incompatible but
|
||||
# just warn until packagers build the required version
|
||||
# See https://bugs.launchpad.net/swift/+bug/1639691 in detail
|
||||
required_version = ((1 << 16) + (3 << 8) + 1)
|
||||
if version_int < required_version:
|
||||
logger.warning(
|
||||
'DEPRECATED WARNING: your liberasurecode '
|
||||
'%s will be deprecated in the near future because of the issue '
|
||||
'https://bugs.launchpad.net/swift/+bug/1639691; '
|
||||
'Please upgrade to >=1.3.1 and rebuild pyeclib to suppress '
|
||||
'this message' % version_str)
|
||||
return version_str
|
||||
|
||||
LIBERASURECODE_VERSION = _liberasurecode_version()
|
||||
807
pyeclib/enum.py
807
pyeclib/enum.py
@@ -1,807 +0,0 @@
|
||||
# Original credit: enum34 project https://pypi.python.org/pypi/enum34
|
||||
# Changes for PyECLib (c) Tushar Gohad (tusharsg@gmail.com)
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution. THIS SOFTWARE IS
|
||||
# PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
|
||||
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
# NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""Python Enumerations"""
|
||||
|
||||
import sys as _sys
|
||||
|
||||
__all__ = ['Enum', 'IntEnum', 'unique']
|
||||
|
||||
pyver = float('%s.%s' % _sys.version_info[:2])
|
||||
|
||||
try:
|
||||
any
|
||||
except NameError:
|
||||
def any(iterable):
|
||||
for element in iterable:
|
||||
if element:
|
||||
return True
|
||||
return False
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
OrderedDict = None
|
||||
|
||||
try:
|
||||
basestring
|
||||
except NameError:
|
||||
# In Python 2 basestring is the ancestor of both str and unicode
|
||||
# in Python 3 it's just str, but was missing in 3.1
|
||||
basestring = str
|
||||
|
||||
|
||||
class _RouteClassAttributeToGetattr(object):
|
||||
"""Route attribute access on a class to __getattr__.
|
||||
|
||||
This is a descriptor, used to define attributes that act differently when
|
||||
accessed through an instance and through a class. Instance access remains
|
||||
normal, but access to an attribute through a class will be routed to the
|
||||
class's __getattr__ method; this is done by raising AttributeError.
|
||||
|
||||
"""
|
||||
def __init__(self, fget=None):
|
||||
self.fget = fget
|
||||
|
||||
def __get__(self, instance, ownerclass=None):
|
||||
if instance is None:
|
||||
raise AttributeError()
|
||||
return self.fget(instance)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
raise AttributeError("can't set attribute")
|
||||
|
||||
def __delete__(self, instance):
|
||||
raise AttributeError("can't delete attribute")
|
||||
|
||||
|
||||
def _is_descriptor(obj):
|
||||
"""Returns True if obj is a descriptor, False otherwise."""
|
||||
return (hasattr(obj, '__get__') or
|
||||
hasattr(obj, '__set__') or
|
||||
hasattr(obj, '__delete__'))
|
||||
|
||||
|
||||
def _is_dunder(name):
|
||||
"""Returns True if a __dunder__ name, False otherwise."""
|
||||
return (name[:2] == name[-2:] == '__' and
|
||||
name[2:3] != '_' and
|
||||
name[-3:-2] != '_' and
|
||||
len(name) > 4)
|
||||
|
||||
|
||||
def _is_sunder(name):
|
||||
"""Returns True if a _sunder_ name, False otherwise."""
|
||||
return (name[0] == name[-1] == '_' and
|
||||
name[1:2] != '_' and
|
||||
name[-2:-1] != '_' and
|
||||
len(name) > 2)
|
||||
|
||||
|
||||
def _make_class_unpicklable(cls):
|
||||
"""Make the given class un-picklable."""
|
||||
def _break_on_call_reduce(self, protocol=None):
|
||||
raise TypeError('%r cannot be pickled' % self)
|
||||
cls.__reduce_ex__ = _break_on_call_reduce
|
||||
cls.__module__ = '<unknown>'
|
||||
|
||||
|
||||
class _EnumDict(dict):
|
||||
"""Track enum member order and ensure member names are not reused.
|
||||
|
||||
EnumMeta will use the names found in self._member_names as the
|
||||
enumeration member names.
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
super(_EnumDict, self).__init__()
|
||||
self._member_names = []
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
"""Changes anything not dundered or not a descriptor.
|
||||
|
||||
If a descriptor is added with the same name as an enum member, the name
|
||||
is removed from _member_names (this may leave a hole in the numerical
|
||||
sequence of values).
|
||||
|
||||
If an enum member name is used twice, an error is raised; duplicate
|
||||
values are not checked for.
|
||||
|
||||
Single underscore (sunder) names are reserved.
|
||||
|
||||
Note: in 3.x __order__ is simply discarded as a not necessary piece
|
||||
leftover from 2.x
|
||||
|
||||
"""
|
||||
if pyver >= 3.0 and key == '__order__':
|
||||
return
|
||||
if _is_sunder(key):
|
||||
raise ValueError('_names_ are reserved for future Enum use')
|
||||
elif _is_dunder(key):
|
||||
pass
|
||||
elif key in self._member_names:
|
||||
# descriptor overwriting an enum?
|
||||
raise TypeError('Attempted to reuse key: %r' % key)
|
||||
elif not _is_descriptor(value):
|
||||
if key in self:
|
||||
# enum overwriting a descriptor?
|
||||
raise TypeError('Key already defined as: %r' % self[key])
|
||||
self._member_names.append(key)
|
||||
super(_EnumDict, self).__setitem__(key, value)
|
||||
|
||||
|
||||
# Dummy value for Enum as EnumMeta explicity checks for it, but of course until
|
||||
# EnumMeta finishes running the first time the Enum class doesn't exist. This
|
||||
# is also why there are checks in EnumMeta like `if Enum is not None`
|
||||
Enum = None
|
||||
|
||||
|
||||
class EnumMeta(type):
|
||||
"""Metaclass for Enum"""
|
||||
@classmethod
|
||||
def __prepare__(metacls, cls, bases):
|
||||
return _EnumDict()
|
||||
|
||||
def __new__(metacls, cls, bases, classdict):
|
||||
# an Enum class is final once enumeration items have been defined; it
|
||||
# cannot be mixed with other types (int, float, etc.) if it has an
|
||||
# inherited __new__ unless a new __new__ is defined (or the resulting
|
||||
# class will fail).
|
||||
if type(classdict) is dict:
|
||||
original_dict = classdict
|
||||
classdict = _EnumDict()
|
||||
for k, v in original_dict.items():
|
||||
classdict[k] = v
|
||||
|
||||
member_type, first_enum = metacls._get_mixins_(bases)
|
||||
__new__, save_new, use_args = metacls._find_new_(
|
||||
classdict, member_type, first_enum)
|
||||
# save enum items into separate mapping so they don't get baked into
|
||||
# the new class
|
||||
members = dict((k, classdict[k]) for k in classdict._member_names)
|
||||
for name in classdict._member_names:
|
||||
del classdict[name]
|
||||
|
||||
# py2 support for definition order
|
||||
__order__ = classdict.get('__order__')
|
||||
if __order__ is None:
|
||||
if pyver < 3.0:
|
||||
__order__ = [name for (name, value) in
|
||||
sorted(members.items(), key=lambda item: item[1])]
|
||||
else:
|
||||
__order__ = classdict._member_names
|
||||
else:
|
||||
del classdict['__order__']
|
||||
if pyver < 3.0:
|
||||
__order__ = __order__.replace(',', ' ').split()
|
||||
aliases = [name for name in members if name not in __order__]
|
||||
__order__ += aliases
|
||||
|
||||
# check for illegal enum names (any others?)
|
||||
invalid_names = set(members) & set(['mro'])
|
||||
if invalid_names:
|
||||
raise ValueError('Invalid enum member name(s): %s' % (
|
||||
', '.join(invalid_names), ))
|
||||
|
||||
# create our new Enum type
|
||||
enum_class = super(EnumMeta, metacls).__new__(metacls, cls, bases,
|
||||
classdict)
|
||||
enum_class._member_names_ = [] # names in random order
|
||||
if OrderedDict is not None:
|
||||
enum_class._member_map_ = OrderedDict()
|
||||
else:
|
||||
enum_class._member_map_ = {} # name->value map
|
||||
enum_class._member_type_ = member_type
|
||||
|
||||
# Reverse value->name map for hashable values.
|
||||
enum_class._value2member_map_ = {}
|
||||
|
||||
# instantiate them, checking for duplicates as we go
|
||||
# we instantiate first instead of checking for duplicates first in case
|
||||
# a custom __new__ is doing something funky with the values -- such as
|
||||
# auto-numbering ;)
|
||||
if __new__ is None:
|
||||
__new__ = enum_class.__new__
|
||||
for member_name in __order__:
|
||||
value = members[member_name]
|
||||
if not isinstance(value, tuple):
|
||||
args = (value, )
|
||||
else:
|
||||
args = value
|
||||
if member_type is tuple: # special case for tuple enums
|
||||
args = (args, ) # wrap it one more time
|
||||
if not use_args or not args:
|
||||
enum_member = __new__(enum_class)
|
||||
if not hasattr(enum_member, '_value_'):
|
||||
enum_member._value_ = value
|
||||
else:
|
||||
enum_member = __new__(enum_class, *args)
|
||||
if not hasattr(enum_member, '_value_'):
|
||||
enum_member._value_ = member_type(*args)
|
||||
value = enum_member._value_
|
||||
enum_member._name_ = member_name
|
||||
enum_member.__objclass__ = enum_class
|
||||
enum_member.__init__(*args)
|
||||
# If another member with the same value was already defined, the
|
||||
# new member becomes an alias to the existing one.
|
||||
for name, canonical_member in enum_class._member_map_.items():
|
||||
if canonical_member.value == enum_member._value_:
|
||||
enum_member = canonical_member
|
||||
break
|
||||
else:
|
||||
# Aliases don't appear in member names (only in __members__).
|
||||
enum_class._member_names_.append(member_name)
|
||||
enum_class._member_map_[member_name] = enum_member
|
||||
try:
|
||||
# This may fail if value is not hashable. We can't add the
|
||||
# value to the map, and by-value lookups for this value will be
|
||||
# linear.
|
||||
enum_class._value2member_map_[value] = enum_member
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
# If a custom type is mixed into the Enum, and it does not know how
|
||||
# to pickle itself, pickle.dumps will succeed but pickle.loads will
|
||||
# fail. Rather than have the error show up later and possibly far
|
||||
# from the source, sabotage the pickle protocol for this class so
|
||||
# that pickle.dumps also fails.
|
||||
#
|
||||
# However, if the new class implements its own __reduce_ex__, do not
|
||||
# sabotage -- it's on them to make sure it works correctly. We use
|
||||
# __reduce_ex__ instead of any of the others as it is preferred by
|
||||
# pickle over __reduce__, and it handles all pickle protocols.
|
||||
unpicklable = False
|
||||
if '__reduce_ex__' not in classdict:
|
||||
if member_type is not object:
|
||||
methods = ('__getnewargs_ex__', '__getnewargs__',
|
||||
'__reduce_ex__', '__reduce__')
|
||||
if not any(m in member_type.__dict__ for m in methods):
|
||||
_make_class_unpicklable(enum_class)
|
||||
unpicklable = True
|
||||
|
||||
# double check that repr and friends are not the mixin's or various
|
||||
# things break (such as pickle)
|
||||
for name in ('__repr__', '__str__', '__format__', '__reduce_ex__'):
|
||||
class_method = getattr(enum_class, name)
|
||||
# obj_method = getattr(member_type, name, None)
|
||||
enum_method = getattr(first_enum, name, None)
|
||||
if name not in classdict and class_method is not enum_method:
|
||||
if name == '__reduce_ex__' and unpicklable:
|
||||
continue
|
||||
setattr(enum_class, name, enum_method)
|
||||
|
||||
# method resolution and int's are not playing nice
|
||||
# Python's less than 2.6 use __cmp__
|
||||
|
||||
if pyver < 2.6:
|
||||
|
||||
if issubclass(enum_class, int):
|
||||
setattr(enum_class, '__cmp__', getattr(int, '__cmp__'))
|
||||
|
||||
elif pyver < 3.0:
|
||||
|
||||
if issubclass(enum_class, int):
|
||||
for method in (
|
||||
'__le__',
|
||||
'__lt__',
|
||||
'__gt__',
|
||||
'__ge__',
|
||||
'__eq__',
|
||||
'__ne__',
|
||||
'__hash__',):
|
||||
setattr(enum_class, method, getattr(int, method))
|
||||
|
||||
# replace any other __new__ with our own (as long as Enum is not None,
|
||||
# anyway) -- again, this is to support pickle
|
||||
if Enum is not None:
|
||||
# if the user defined their own __new__, save it before it gets
|
||||
# clobbered in case they subclass later
|
||||
if save_new:
|
||||
setattr(enum_class, '__member_new__',
|
||||
enum_class.__dict__['__new__'])
|
||||
setattr(enum_class, '__new__', Enum.__dict__['__new__'])
|
||||
return enum_class
|
||||
|
||||
def __call__(cls, value, names=None, module=None, type=None):
|
||||
"""Either returns an existing member, or creates a new enum class.
|
||||
|
||||
This method is used both when an enum class is given a value to match
|
||||
to an enumeration member (i.e. Color(3)) and for the functional API
|
||||
(i.e. Color = Enum('Color', names='red green blue')).
|
||||
|
||||
When used for the functional API: `module`, if set, will be stored in
|
||||
the new class' __module__ attribute; `type`, if set, will be mixed in
|
||||
as the first base class.
|
||||
|
||||
Note: if `module` is not set this routine will attempt to discover the
|
||||
calling module by walking the frame stack; if this is unsuccessful
|
||||
the resulting class will not be pickleable.
|
||||
|
||||
"""
|
||||
if names is None: # simple value lookup
|
||||
return cls.__new__(cls, value)
|
||||
# otherwise, functional API: we're creating a new Enum type
|
||||
return cls._create_(value, names, module=module, type=type)
|
||||
|
||||
def __contains__(cls, member):
|
||||
return isinstance(member, cls) and member.name in cls._member_map_
|
||||
|
||||
def __delattr__(cls, attr):
|
||||
# nicer error message when someone tries to delete an attribute
|
||||
# (see issue19025).
|
||||
if attr in cls._member_map_:
|
||||
raise AttributeError(
|
||||
"%s: cannot delete Enum member." % cls.__name__)
|
||||
super(EnumMeta, cls).__delattr__(attr)
|
||||
|
||||
def __dir__(self):
|
||||
return (['__class__', '__doc__', '__members__', '__module__'] +
|
||||
self._member_names_)
|
||||
|
||||
@property
|
||||
def __members__(cls):
|
||||
"""Returns a mapping of member name->value.
|
||||
|
||||
This mapping lists all enum members, including aliases. Note that this
|
||||
is a copy of the internal mapping.
|
||||
|
||||
"""
|
||||
return cls._member_map_.copy()
|
||||
|
||||
def __getattr__(cls, name):
|
||||
"""Return the enum member matching `name`
|
||||
|
||||
We use __getattr__ instead of descriptors or inserting into the enum
|
||||
class' __dict__ in order to support `name` and `value` being both
|
||||
properties for enum members (which live in the class' __dict__) and
|
||||
enum members themselves.
|
||||
|
||||
"""
|
||||
if _is_dunder(name):
|
||||
raise AttributeError(name)
|
||||
try:
|
||||
return cls._member_map_[name]
|
||||
except KeyError:
|
||||
raise AttributeError(name)
|
||||
|
||||
def __getitem__(cls, name):
|
||||
return cls._member_map_[name]
|
||||
|
||||
def __iter__(cls):
|
||||
return (cls._member_map_[name]
|
||||
for name in cls._member_names_)
|
||||
|
||||
def __reversed__(cls):
|
||||
return (cls._member_map_[name]
|
||||
for name in reversed(cls._member_names_))
|
||||
|
||||
def __len__(cls):
|
||||
return len(cls._member_names_)
|
||||
|
||||
def __repr__(cls):
|
||||
return "<enum %r>" % cls.__name__
|
||||
|
||||
def __setattr__(cls, name, value):
|
||||
"""Block attempts to reassign Enum members.
|
||||
|
||||
A simple assignment to the class namespace only changes one of the
|
||||
several possible ways to get an Enum member from the Enum class,
|
||||
resulting in an inconsistent Enumeration.
|
||||
|
||||
"""
|
||||
member_map = cls.__dict__.get('_member_map_', {})
|
||||
if name in member_map:
|
||||
raise AttributeError('Cannot reassign members.')
|
||||
super(EnumMeta, cls).__setattr__(name, value)
|
||||
|
||||
def _create_(cls, class_name, names=None, module=None, type=None):
|
||||
"""Convenience method to create a new Enum class.
|
||||
|
||||
`names` can be:
|
||||
|
||||
* A string containing member names, separated either with spaces or
|
||||
commas. Values are auto-numbered from 1.
|
||||
* An iterable of member names. Values are auto-numbered from 1.
|
||||
* An iterable of (member name, value) pairs.
|
||||
* A mapping of member name -> value.
|
||||
|
||||
"""
|
||||
metacls = cls.__class__
|
||||
if type is None:
|
||||
bases = (cls, )
|
||||
else:
|
||||
bases = (type, cls)
|
||||
classdict = metacls.__prepare__(class_name, bases)
|
||||
__order__ = []
|
||||
|
||||
# special processing needed for names?
|
||||
if isinstance(names, basestring):
|
||||
names = names.replace(',', ' ').split()
|
||||
if isinstance(names, (tuple, list)) and isinstance(names[0],
|
||||
basestring):
|
||||
names = [(e, i + 1) for (i, e) in enumerate(names)]
|
||||
|
||||
# Here, names is either an iterable of (name, value) or a mapping.
|
||||
for item in names:
|
||||
if isinstance(item, basestring):
|
||||
member_name, member_value = item, names[item]
|
||||
else:
|
||||
member_name, member_value = item
|
||||
classdict[member_name] = member_value
|
||||
__order__.append(member_name)
|
||||
# only set __order__ in classdict if name/value was not from a mapping
|
||||
if not isinstance(item, basestring):
|
||||
classdict['__order__'] = ' '.join(__order__)
|
||||
enum_class = metacls.__new__(metacls, class_name, bases, classdict)
|
||||
|
||||
# TODO: replace the frame hack if a blessed way to know the calling
|
||||
# module is ever developed
|
||||
if module is None:
|
||||
try:
|
||||
module = _sys._getframe(2).f_globals['__name__']
|
||||
except (AttributeError, ValueError):
|
||||
pass
|
||||
if module is None:
|
||||
_make_class_unpicklable(enum_class)
|
||||
else:
|
||||
enum_class.__module__ = module
|
||||
|
||||
return enum_class
|
||||
|
||||
@staticmethod
|
||||
def _get_mixins_(bases):
|
||||
"""Returns the type for creating enum members, and the first inherited
|
||||
enum class.
|
||||
|
||||
bases: the tuple of bases that was given to __new__
|
||||
|
||||
"""
|
||||
if not bases or Enum is None:
|
||||
return object, Enum
|
||||
|
||||
# double check that we are not subclassing a class with existing
|
||||
# enumeration members; while we're at it, see if any other data
|
||||
# type has been mixed in so we can use the correct __new__
|
||||
member_type = first_enum = None
|
||||
for base in bases:
|
||||
if (base is not Enum and
|
||||
issubclass(base, Enum) and
|
||||
base._member_names_):
|
||||
raise TypeError("Cannot extend enumerations")
|
||||
# base is now the last base in bases
|
||||
if not issubclass(base, Enum):
|
||||
raise TypeError("new enumerations must be created as "
|
||||
"`ClassName([mixin_type,] enum_type)`")
|
||||
|
||||
# get correct mix-in type (either mix-in type of Enum subclass, or
|
||||
# first base if last base is Enum)
|
||||
if not issubclass(bases[0], Enum):
|
||||
member_type = bases[0] # first data type
|
||||
first_enum = bases[-1] # enum type
|
||||
else:
|
||||
for base in bases[0].__mro__:
|
||||
# most common: (IntEnum, int, Enum, object)
|
||||
# possible: (<Enum 'AutoIntEnum'>, <Enum 'IntEnum'>,
|
||||
# <class 'int'>, <Enum 'Enum'>,
|
||||
# <class 'object'>)
|
||||
if issubclass(base, Enum):
|
||||
if first_enum is None:
|
||||
first_enum = base
|
||||
else:
|
||||
if member_type is None:
|
||||
member_type = base
|
||||
|
||||
return member_type, first_enum
|
||||
|
||||
if pyver < 3.0:
|
||||
@staticmethod
|
||||
def _find_new_(classdict, member_type, first_enum):
|
||||
"""Returns the __new__ to be used for creating the enum members.
|
||||
|
||||
classdict: the class dictionary given to __new__
|
||||
member_type: the data type whose __new__ will be used by default
|
||||
first_enum: enumeration to check for an overriding __new__
|
||||
|
||||
"""
|
||||
# now find the correct __new__, checking to see of one was defined
|
||||
# by the user; also check earlier enum classes in case a __new__
|
||||
# was saved as __member_new__
|
||||
__new__ = classdict.get('__new__', None)
|
||||
if __new__:
|
||||
return None, True, True # __new__, save_new, use_args
|
||||
|
||||
N__new__ = getattr(None, '__new__')
|
||||
O__new__ = getattr(object, '__new__')
|
||||
if Enum is None:
|
||||
E__new__ = N__new__
|
||||
else:
|
||||
E__new__ = Enum.__dict__['__new__']
|
||||
# check all possibles for __member_new__ before falling back to
|
||||
# __new__
|
||||
for method in ('__member_new__', '__new__'):
|
||||
for possible in (member_type, first_enum):
|
||||
try:
|
||||
target = possible.__dict__[method]
|
||||
except (AttributeError, KeyError):
|
||||
target = getattr(possible, method, None)
|
||||
if target not in [
|
||||
None,
|
||||
N__new__,
|
||||
O__new__,
|
||||
E__new__, ]:
|
||||
if method == '__member_new__':
|
||||
classdict['__new__'] = target
|
||||
return None, False, True
|
||||
if isinstance(target, staticmethod):
|
||||
target = target.__get__(member_type)
|
||||
__new__ = target
|
||||
break
|
||||
if __new__ is not None:
|
||||
break
|
||||
else:
|
||||
__new__ = object.__new__
|
||||
|
||||
# if a non-object.__new__ is used then whatever value/tuple was
|
||||
# assigned to the enum member name will be passed to __new__ and
|
||||
# to the new enum member's __init__
|
||||
if __new__ is object.__new__:
|
||||
use_args = False
|
||||
else:
|
||||
use_args = True
|
||||
|
||||
return __new__, False, use_args
|
||||
else:
|
||||
@staticmethod
|
||||
def _find_new_(classdict, member_type, first_enum):
|
||||
"""Returns the __new__ to be used for creating the enum members.
|
||||
|
||||
classdict: the class dictionary given to __new__
|
||||
member_type: the data type whose __new__ will be used by default
|
||||
first_enum: enumeration to check for an overriding __new__
|
||||
|
||||
"""
|
||||
# now find the correct __new__, checking to see of one was defined
|
||||
# by the user; also check earlier enum classes in case a __new__
|
||||
# was saved as __member_new__
|
||||
__new__ = classdict.get('__new__', None)
|
||||
|
||||
# should __new__ be saved as __member_new__ later?
|
||||
save_new = __new__ is not None
|
||||
|
||||
if __new__ is None:
|
||||
# check all possibles for __member_new__ before falling back to
|
||||
# __new__
|
||||
for method in ('__member_new__', '__new__'):
|
||||
for possible in (member_type, first_enum):
|
||||
target = getattr(possible, method, None)
|
||||
if target not in (
|
||||
None,
|
||||
None.__new__,
|
||||
object.__new__,
|
||||
Enum.__new__, ):
|
||||
__new__ = target
|
||||
break
|
||||
if __new__ is not None:
|
||||
break
|
||||
else:
|
||||
__new__ = object.__new__
|
||||
|
||||
# if a non-object.__new__ is used then whatever value/tuple was
|
||||
# assigned to the enum member name will be passed to __new__ and
|
||||
# to the new enum member's __init__
|
||||
if __new__ is object.__new__:
|
||||
use_args = False
|
||||
else:
|
||||
use_args = True
|
||||
|
||||
return __new__, save_new, use_args
|
||||
|
||||
|
||||
########################################################
|
||||
# In order to support Python 2 and 3 with a single
|
||||
# codebase we have to create the Enum methods separately
|
||||
# and then use the `type(name, bases, dict)` method to
|
||||
# create the class.
|
||||
########################################################
|
||||
temp_enum_dict = {}
|
||||
temp_enum_dict['__doc__'] = "Generic enumeration.\n\n "\
|
||||
"Derive from this class to define new enumerations.\n\n"
|
||||
|
||||
|
||||
def __new__(cls, value):
|
||||
# all enum instances are actually created during class construction
|
||||
# without calling this method; this method is called by the metaclass'
|
||||
# __call__ (i.e. Color(3) ), and by pickle
|
||||
if type(value) is cls:
|
||||
# For lookups like Color(Color.red)
|
||||
value = value.value
|
||||
# return value
|
||||
# by-value search for a matching enum member
|
||||
# see if it's in the reverse mapping (for hashable values)
|
||||
try:
|
||||
if value in cls._value2member_map_:
|
||||
return cls._value2member_map_[value]
|
||||
except TypeError:
|
||||
# not there, now do long search -- O(n) behavior
|
||||
for member in cls._member_map_.values():
|
||||
if member.value == value:
|
||||
return member
|
||||
raise ValueError("%s is not a valid %s" % (value, cls.__name__))
|
||||
temp_enum_dict['__new__'] = __new__
|
||||
del __new__
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s.%s: %r>" % (
|
||||
self.__class__.__name__, self._name_, self._value_)
|
||||
temp_enum_dict['__repr__'] = __repr__
|
||||
del __repr__
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return "%s.%s" % (self.__class__.__name__, self._name_)
|
||||
temp_enum_dict['__str__'] = __str__
|
||||
del __str__
|
||||
|
||||
|
||||
def __dir__(self):
|
||||
added_behavior = [m for m in self.__class__.__dict__ if m[0] != '_']
|
||||
return (['__class__', '__doc__', '__module__', 'name', 'value'] +
|
||||
added_behavior)
|
||||
temp_enum_dict['__dir__'] = __dir__
|
||||
del __dir__
|
||||
|
||||
|
||||
def __format__(self, format_spec):
|
||||
# mixed-in Enums should use the mixed-in type's __format__, otherwise
|
||||
# we can get strange results with the Enum name showing up instead of
|
||||
# the value
|
||||
|
||||
# pure Enum branch
|
||||
if self._member_type_ is object:
|
||||
cls = str
|
||||
val = str(self)
|
||||
# mix-in branch
|
||||
else:
|
||||
cls = self._member_type_
|
||||
val = self.value
|
||||
return cls.__format__(val, format_spec)
|
||||
temp_enum_dict['__format__'] = __format__
|
||||
del __format__
|
||||
|
||||
|
||||
####################################
|
||||
# Python's less than 2.6 use __cmp__
|
||||
|
||||
if pyver < 2.6:
|
||||
|
||||
def __cmp__(self, other):
|
||||
if type(other) is self.__class__:
|
||||
if self is other:
|
||||
return 0
|
||||
return -1
|
||||
return NotImplemented
|
||||
raise TypeError("unorderable types: %s() and %s()" %
|
||||
(self.__class__.__name__, other.__class__.__name__))
|
||||
temp_enum_dict['__cmp__'] = __cmp__
|
||||
del __cmp__
|
||||
|
||||
else:
|
||||
|
||||
def __le__(self, other):
|
||||
raise TypeError("unorderable types: %s() <= %s()" %
|
||||
(self.__class__.__name__, other.__class__.__name__))
|
||||
temp_enum_dict['__le__'] = __le__
|
||||
del __le__
|
||||
|
||||
def __lt__(self, other):
|
||||
raise TypeError("unorderable types: %s() < %s()" %
|
||||
(self.__class__.__name__, other.__class__.__name__))
|
||||
temp_enum_dict['__lt__'] = __lt__
|
||||
del __lt__
|
||||
|
||||
def __ge__(self, other):
|
||||
raise TypeError("unorderable types: %s() >= %s()" %
|
||||
(self.__class__.__name__, other.__class__.__name__))
|
||||
temp_enum_dict['__ge__'] = __ge__
|
||||
del __ge__
|
||||
|
||||
def __gt__(self, other):
|
||||
raise TypeError("unorderable types: %s() > %s()" %
|
||||
(self.__class__.__name__, other.__class__.__name__))
|
||||
temp_enum_dict['__gt__'] = __gt__
|
||||
del __gt__
|
||||
|
||||
|
||||
def __eq__(self, other):
|
||||
if type(other) is self.__class__:
|
||||
return self is other
|
||||
return NotImplemented
|
||||
temp_enum_dict['__eq__'] = __eq__
|
||||
del __eq__
|
||||
|
||||
|
||||
def __ne__(self, other):
|
||||
if type(other) is self.__class__:
|
||||
return self is not other
|
||||
return NotImplemented
|
||||
temp_enum_dict['__ne__'] = __ne__
|
||||
del __ne__
|
||||
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self._name_)
|
||||
temp_enum_dict['__hash__'] = __hash__
|
||||
del __hash__
|
||||
|
||||
|
||||
def __reduce_ex__(self, proto):
|
||||
return self.__class__, (self._value_, )
|
||||
temp_enum_dict['__reduce_ex__'] = __reduce_ex__
|
||||
del __reduce_ex__
|
||||
|
||||
# _RouteClassAttributeToGetattr is used to provide access to the `name`
|
||||
# and `value` properties of enum members while keeping some measure of
|
||||
# protection from modification, while still allowing for an enumeration
|
||||
# to have members named `name` and `value`. This works because enumeration
|
||||
# members are not set directly on the enum class -- __getattr__ is
|
||||
# used to look them up.
|
||||
|
||||
|
||||
@_RouteClassAttributeToGetattr
|
||||
def name(self):
|
||||
return self._name_
|
||||
temp_enum_dict['name'] = name
|
||||
del name
|
||||
|
||||
|
||||
@_RouteClassAttributeToGetattr
|
||||
def value(self):
|
||||
return self._value_
|
||||
temp_enum_dict['value'] = value
|
||||
del value
|
||||
|
||||
Enum = EnumMeta('Enum', (object, ), temp_enum_dict)
|
||||
del temp_enum_dict
|
||||
|
||||
# Enum has now been created
|
||||
###########################
|
||||
|
||||
|
||||
class IntEnum(int, Enum):
|
||||
"""Enum where members are also (and must be) ints"""
|
||||
|
||||
|
||||
def unique(enumeration):
|
||||
"""Class decorator that ensures only unique members exist
|
||||
in an enumeration."""
|
||||
duplicates = []
|
||||
for name, member in enumeration.__members__.items():
|
||||
if name != member.name:
|
||||
duplicates.append((name, member.name))
|
||||
if duplicates:
|
||||
duplicate_names = ', '.join(
|
||||
["%s -> %s" % (alias, name) for (alias, name) in duplicates])
|
||||
raise ValueError('duplicate names found in %r: %s' %
|
||||
(enumeration, duplicate_names))
|
||||
return enumeration
|
||||
@@ -1,75 +0,0 @@
|
||||
# Copyright (c) 2013, 2014, Kevin Greenan (kmgreen2@gmail.com)
|
||||
# Copyright (c) 2014, Tushar Gohad (tusharsg@gmail.com)
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution. THIS SOFTWARE IS
|
||||
# PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
|
||||
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
# NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
|
||||
def positive_int_value(param):
|
||||
# Returns value as a positive int or raises ValueError otherwise
|
||||
try:
|
||||
value = int(param)
|
||||
assert value > 0
|
||||
except (TypeError, ValueError, AssertionError):
|
||||
# Handle: TypeError for 'None', ValueError for non-int strings
|
||||
# and AssertionError for values <= 0
|
||||
raise ValueError('Must be an integer > 0, not "%s".' % param)
|
||||
return value
|
||||
|
||||
|
||||
def import_class(import_str):
|
||||
"""
|
||||
Returns a class from a string that specifies a module and/or class
|
||||
|
||||
:param import_str: import path, e.g. 'httplib.HTTPConnection'
|
||||
:returns imported object
|
||||
:raises: ImportedError if the class does not exist or the path is invalid
|
||||
"""
|
||||
(mod_str, separator, class_str) = import_str.rpartition('.')
|
||||
try:
|
||||
__import__(mod_str)
|
||||
return getattr(sys.modules[mod_str], class_str)
|
||||
except (ValueError, AttributeError):
|
||||
raise ImportError('Class %s cannot be found (%)' %
|
||||
(class_str,
|
||||
traceback.format_exception(*sys.exc_info())))
|
||||
|
||||
|
||||
def create_instance(import_str, *args, **kwargs):
|
||||
"""
|
||||
Returns instance of class which imported by import path.
|
||||
|
||||
:param import_str: import path of class
|
||||
:param \*args: indexed arguments for new instance
|
||||
:param \*\*kwargs: keyword arguments for new instance
|
||||
:returns: instance of imported class which instantiated with
|
||||
arguments *args and **kwargs
|
||||
"""
|
||||
try:
|
||||
object_class = import_class(import_str)
|
||||
except Exception:
|
||||
raise
|
||||
instance = object_class(*args, **kwargs)
|
||||
|
||||
return instance
|
||||
196
setup.py
196
setup.py
@@ -1,196 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2013-2015, Kevin Greenan (kmgreen2@gmail.com)
|
||||
# Copyright (c) 2013-2015, Tushar Gohad (tusharsg@gmail.com)
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution. THIS SOFTWARE IS
|
||||
# PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
|
||||
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
# NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
|
||||
from ctypes import *
|
||||
from ctypes.util import *
|
||||
from distutils.command.build import build as _build
|
||||
from distutils.command.clean import clean as _clean
|
||||
from distutils.sysconfig import EXEC_PREFIX as _exec_prefix
|
||||
from distutils.sysconfig import get_python_lib
|
||||
from distutils.sysconfig import get_python_inc
|
||||
|
||||
try:
|
||||
from setuptools import setup
|
||||
except ImportError:
|
||||
from distribute_setup import use_setuptools
|
||||
use_setuptools()
|
||||
from setuptools import setup
|
||||
|
||||
from setuptools import Extension
|
||||
from setuptools.command.install import install as _install
|
||||
|
||||
platform_str = platform.platform()
|
||||
default_python_incdir = get_python_inc()
|
||||
|
||||
|
||||
# this is to be used only for library existence/version checks,
|
||||
# not for rpath handling
|
||||
def _find_library(name):
|
||||
target_lib = find_library(name)
|
||||
if platform_str.find("Darwin") > -1:
|
||||
target_lib = os.path.abspath(target_lib)
|
||||
if os.path.islink(target_lib):
|
||||
p = os.readlink(target_lib)
|
||||
if os.path.isabs(p):
|
||||
target_lib = p
|
||||
else:
|
||||
target_lib = os.path.join(os.path.dirname(target_lib), p)
|
||||
# return absolute path to the library if found
|
||||
return target_lib
|
||||
|
||||
|
||||
class build(_build):
|
||||
|
||||
def check_liberasure(self):
|
||||
library_basename = "liberasurecode"
|
||||
library_version = "1"
|
||||
library = library_basename + "-" + library_version
|
||||
library_url = "https://github.com/openstack/liberasurecode"
|
||||
|
||||
found_path = _find_library("erasurecode")
|
||||
if found_path:
|
||||
if found_path.endswith(library_version) or \
|
||||
found_path.find(library_version + ".") > -1:
|
||||
# call 1.x.x the only compatible version for now
|
||||
return
|
||||
|
||||
if platform_str.find("Darwin") > -1:
|
||||
liberasure_file = \
|
||||
library_basename + "." + library_version + ".dylib"
|
||||
else:
|
||||
liberasure_file = \
|
||||
library_basename + ".so." + library_version
|
||||
|
||||
print("**************************************************************")
|
||||
print("*** ")
|
||||
print("*** Can not locate %s" % (liberasure_file))
|
||||
print("*** ")
|
||||
print("*** Install - ")
|
||||
print("*** Manual: %s" % library_url)
|
||||
print("*** Fedora/Red Hat variants: liberasurecode-devel")
|
||||
print("*** Debian/Ubuntu variants: liberasurecode-dev")
|
||||
print("*** ")
|
||||
print("**************************************************************")
|
||||
|
||||
sys.exit(-1)
|
||||
|
||||
def run(self):
|
||||
self.check_liberasure()
|
||||
_build.run(self)
|
||||
|
||||
|
||||
class clean(_clean):
|
||||
|
||||
def run(self):
|
||||
_clean.run(self)
|
||||
|
||||
|
||||
class install(_install):
|
||||
|
||||
def run(self):
|
||||
|
||||
install_cmd = self.distribution.get_command_obj('install')
|
||||
install_lib = self.distribution.get_command_obj('install_lib')
|
||||
for cmd in (install_lib, install_cmd):
|
||||
cmd.ensure_finalized()
|
||||
|
||||
# ensure that the paths are absolute so we don't get lost
|
||||
opts = {'exec_prefix': install_cmd.exec_prefix,
|
||||
'root': install_cmd.root}
|
||||
for optname, value in list(opts.items()):
|
||||
if value is not None:
|
||||
opts[optname] = os.path.abspath(value)
|
||||
|
||||
installroot = install_lib.install_dir
|
||||
_install.run(self)
|
||||
|
||||
# Another Mac-ism... If the libraries are installed
|
||||
# in a strange place, DYLD_LIRBARY_PATH needs to be
|
||||
# updated.
|
||||
if platform_str.find("Darwin") > -1:
|
||||
ldpath_str = "DYLD_LIBRARY_PATH"
|
||||
else:
|
||||
ldpath_str = "LD_LIBRARY_PATH"
|
||||
print("***************************************************")
|
||||
print("** ")
|
||||
print("** PyECLib libraries have been installed to: ")
|
||||
print("** %s" % installroot)
|
||||
print("** ")
|
||||
print("** Any user using this library must update: ")
|
||||
print("** %s" % ldpath_str)
|
||||
print("** ")
|
||||
print("** Run 'ldconfig' or place this line: ")
|
||||
print("** export %s=%s" % (ldpath_str, "%s"
|
||||
% installroot))
|
||||
print("** ")
|
||||
print("** into .bashrc, .profile, or the appropriate shell")
|
||||
print("** start-up script! Also look at ldconfig(8) man ")
|
||||
print("** page for a more static LD configuration ")
|
||||
print("** ")
|
||||
print("***************************************************")
|
||||
|
||||
|
||||
module = Extension('pyeclib_c',
|
||||
define_macros=[('MAJOR VERSION', '1'),
|
||||
('MINOR VERSION', '5')],
|
||||
include_dirs=[default_python_incdir,
|
||||
'src/c/pyeclib_c',
|
||||
'/usr/include',
|
||||
'/usr/include/liberasurecode',
|
||||
'%s/include/liberasurecode' % sys.prefix,
|
||||
'%s/include' % sys.prefix],
|
||||
libraries=['erasurecode'],
|
||||
# The extra arguments are for debugging
|
||||
# extra_compile_args=['-g', '-O0'],
|
||||
sources=['src/c/pyeclib_c/pyeclib_c.c'])
|
||||
|
||||
setup(name='pyeclib',
|
||||
version='1.5.0',
|
||||
author='Kevin Greenan',
|
||||
author_email='kmgreen2@gmail.com',
|
||||
maintainer='Kevin Greenan and Tushar Gohad',
|
||||
maintainer_email='kmgreen2@gmail.com, tusharsg@gmail.com',
|
||||
url='http://git.openstack.org/cgit/openstack/pyeclib/',
|
||||
bugtrack_url='https://bugs.launchpad.net/pyeclib',
|
||||
description='This library provides a simple Python interface for \
|
||||
implementing erasure codes. To obtain the best possible \
|
||||
performance, the underlying erasure code algorithms are \
|
||||
written in C.',
|
||||
platforms='Linux',
|
||||
license='BSD',
|
||||
ext_modules=[module],
|
||||
packages=['pyeclib'],
|
||||
package_dir={'pyeclib': 'pyeclib'},
|
||||
cmdclass={'build': build, 'install': install, 'clean': clean},
|
||||
py_modules=['pyeclib.ec_iface', 'pyeclib.core'],
|
||||
command_options={
|
||||
'build_sphinx': {
|
||||
'build_dir': ('setup.py', 'doc/build')}},
|
||||
test_suite='test')
|
||||
@@ -1,160 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2014, Kevin Greenan (kmgreen2@gmail.com)
|
||||
* Copyright (c) 2014, Tushar Gohad (tusharsg@gmail.com)
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
* list of conditions and the following disclaimer in the documentation and/or
|
||||
* other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY
|
||||
* THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
* EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __CAPSULETHUNK_H
|
||||
#define __CAPSULETHUNK_H
|
||||
|
||||
#if ( (PY_VERSION_HEX < 0x02070000) \
|
||||
|| ((PY_VERSION_HEX >= 0x03000000) \
|
||||
&& (PY_VERSION_HEX < 0x03010000)) )
|
||||
|
||||
#define __PyCapsule_GetField(capsule, field, default_value) \
|
||||
( PyCapsule_CheckExact(capsule) \
|
||||
? (((PyCObject *)capsule)->field) \
|
||||
: (default_value) \
|
||||
) \
|
||||
|
||||
#define __PyCapsule_SetField(capsule, field, value) \
|
||||
( PyCapsule_CheckExact(capsule) \
|
||||
? (((PyCObject *)capsule)->field = value), 1 \
|
||||
: 0 \
|
||||
) \
|
||||
|
||||
|
||||
#define PyCapsule_Type PyCObject_Type
|
||||
|
||||
#define PyCapsule_CheckExact(capsule) (PyCObject_Check(capsule))
|
||||
#define PyCapsule_IsValid(capsule, name) (PyCObject_Check(capsule))
|
||||
|
||||
|
||||
#define PyCapsule_New(pointer, name, destructor) \
|
||||
(PyCObject_FromVoidPtr(pointer, destructor))
|
||||
|
||||
|
||||
#define PyCapsule_GetPointer(capsule, name) \
|
||||
(PyCObject_AsVoidPtr(capsule))
|
||||
|
||||
/* Don't call PyCObject_SetPointer here, it fails if there's a destructor */
|
||||
#define PyCapsule_SetPointer(capsule, pointer) \
|
||||
__PyCapsule_SetField(capsule, cobject, pointer)
|
||||
|
||||
|
||||
#define PyCapsule_GetDestructor(capsule) \
|
||||
__PyCapsule_GetField(capsule, destructor)
|
||||
|
||||
#define PyCapsule_SetDestructor(capsule, dtor) \
|
||||
__PyCapsule_SetField(capsule, destructor, dtor)
|
||||
|
||||
|
||||
/*
|
||||
* Sorry, there's simply no place
|
||||
* to store a Capsule "name" in a CObject.
|
||||
*/
|
||||
#define PyCapsule_GetName(capsule) NULL
|
||||
|
||||
static int
|
||||
PyCapsule_SetName(PyObject *capsule, const char *unused)
|
||||
{
|
||||
unused = unused;
|
||||
PyErr_SetString(PyExc_NotImplementedError,
|
||||
"can't use PyCapsule_SetName with CObjects");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#define PyCapsule_GetContext(capsule) \
|
||||
__PyCapsule_GetField(capsule, descr)
|
||||
|
||||
#define PyCapsule_SetContext(capsule, context) \
|
||||
__PyCapsule_SetField(capsule, descr, context)
|
||||
|
||||
|
||||
static void *
|
||||
PyCapsule_Import(const char *name, int no_block)
|
||||
{
|
||||
PyObject *object = NULL;
|
||||
void *return_value = NULL;
|
||||
char *trace;
|
||||
size_t name_length = (strlen(name) + 1) * sizeof(char);
|
||||
char *name_dup = (char *)PyMem_MALLOC(name_length);
|
||||
|
||||
if (!name_dup) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(name_dup, name, name_length);
|
||||
|
||||
trace = name_dup;
|
||||
while (trace) {
|
||||
char *dot = strchr(trace, '.');
|
||||
if (dot) {
|
||||
*dot++ = '\0';
|
||||
}
|
||||
|
||||
if (object == NULL) {
|
||||
if (no_block) {
|
||||
object = PyImport_ImportModuleNoBlock(trace);
|
||||
} else {
|
||||
object = PyImport_ImportModule(trace);
|
||||
if (!object) {
|
||||
PyErr_Format(PyExc_ImportError,
|
||||
"PyCapsule_Import could not "
|
||||
"import module \"%s\"", trace);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PyObject *object2 = PyObject_GetAttrString(object, trace);
|
||||
Py_DECREF(object);
|
||||
object = object2;
|
||||
}
|
||||
if (!object) {
|
||||
goto EXIT;
|
||||
}
|
||||
|
||||
trace = dot;
|
||||
}
|
||||
|
||||
if (PyCObject_Check(object)) {
|
||||
PyCObject *cobject = (PyCObject *)object;
|
||||
return_value = cobject->cobject;
|
||||
} else {
|
||||
PyErr_Format(PyExc_AttributeError,
|
||||
"PyCapsule_Import \"%s\" is not valid",
|
||||
name);
|
||||
}
|
||||
|
||||
EXIT:
|
||||
Py_XDECREF(object);
|
||||
if (name_dup) {
|
||||
PyMem_FREE(name_dup);
|
||||
}
|
||||
return return_value;
|
||||
}
|
||||
|
||||
#endif /* #if PY_VERSION_HEX < 0x02070000 */
|
||||
|
||||
#endif /* __CAPSULETHUNK_H */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2014, Kevin Greenan (kmgreen2@gmail.com)
|
||||
* Copyright (c) 2014, Tushar Gohad (tusharsg@gmail.com)
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
* list of conditions and the following disclaimer in the documentation and/or
|
||||
* other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY
|
||||
* THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
* EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __PYEC_LIB_C_H_
|
||||
#define __PYEC_LIB_C_H_
|
||||
|
||||
/* For exact-width integer types */
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct pyeclib_s
|
||||
{
|
||||
int ec_desc;
|
||||
struct ec_args ec_args;
|
||||
} pyeclib_t;
|
||||
|
||||
|
||||
#define PYECC_HANDLE_NAME "pyeclib_handle"
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
coverage
|
||||
nose
|
||||
six>=1.9.0
|
||||
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3 # BSD
|
||||
@@ -1,94 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright (c) 2013, Kevin Greenan (kmgreen2@gmail.com)
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice, this
|
||||
# list of conditions and the following disclaimer in the documentation and/or
|
||||
# other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY
|
||||
# THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
# EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
FILE_DIR=./test_files
|
||||
DECODED_DIR=./decoded_files
|
||||
FRAGMENT_DIR=./test_fragments
|
||||
FILES=`echo ${FILE_DIR}`
|
||||
TOOLS_DIR=../tools
|
||||
|
||||
if [ ! -d ${DECODED_DIR} ]; then
|
||||
mkdir ${DECODED_DIR}
|
||||
fi
|
||||
|
||||
if [ ! -d ${FRAGMENT_DIR} ]; then
|
||||
mkdir ${FRAGMENT_DIR}
|
||||
fi
|
||||
|
||||
TYPES="
|
||||
jerasure_rs_vand
|
||||
jerasure_rs_cauchy
|
||||
flat_xor_hd_3
|
||||
flat_xor_hd_4
|
||||
isa_l_rs_vand
|
||||
liberasurecode_rs_vand
|
||||
"
|
||||
NUM_DATAS="10 11 12"
|
||||
RS_NUM_PARITIES="2 3 4"
|
||||
XOR_NUM_PARITIES="6"
|
||||
|
||||
# Weird stuff happens when we try to rebuild and
|
||||
# are beyond the FT of the code... Make it fail
|
||||
# gracefully
|
||||
|
||||
for TYPE in ${TYPES}; do
|
||||
for NUM_DATA in ${NUM_DATAS}; do
|
||||
rm ${DECODED_DIR}/*
|
||||
rm ${FRAGMENT_DIR}/*
|
||||
NUM_PARITIES=${RS_NUM_PARITIES}
|
||||
if [[ ${TYPE} == "flat_xor_hd"* ]]; then
|
||||
NUM_PARITIES=${XOR_NUM_PARITIES}
|
||||
fi
|
||||
for NUM_PARITY in ${NUM_PARITIES}; do
|
||||
let NUM_TOTAL=$(( NUM_DATA + NUM_PARITY))
|
||||
FAULT_TOL=${NUM_PARITY}
|
||||
if [[ ${TYPE} == "flat_xor_hd"* ]]; then
|
||||
FAULT_TOL="2"
|
||||
fi
|
||||
for file in `cd ${FILES}; echo *; cd ..`; do
|
||||
python ${TOOLS_DIR}/pyeclib_encode.py ${NUM_DATA} ${NUM_PARITY} ${TYPE} ${FILE_DIR} ${file} ${FRAGMENT_DIR}
|
||||
done
|
||||
|
||||
for file in `cd ${FILES}; echo *; cd ..`; do
|
||||
fragments=( `echo ${FRAGMENT_DIR}/${file}.*` )
|
||||
let i=0
|
||||
while (( $i < ${FAULT_TOL} )); do
|
||||
index=$(( RANDOM % NUM_TOTAL ))
|
||||
fragments[${index}]=""
|
||||
let i=$i+1
|
||||
done
|
||||
python ${TOOLS_DIR}/pyeclib_decode.py ${NUM_DATA} ${NUM_PARITY} ${TYPE} ${fragments[*]} ${DECODED_DIR}/${file}
|
||||
diff ${FILE_DIR}/${file} ${DECODED_DIR}/${file}.decoded
|
||||
if [[ $? != 0 ]]; then
|
||||
echo "${FILE_DIR}/${file} != ${DECODED_DIR}/${file}.decoded"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
done
|
||||
done
|
||||
done
|
||||
|
||||
rm ${DECODED_DIR}/*
|
||||
rm ${FRAGMENT_DIR}/*
|
||||
@@ -1,92 +0,0 @@
|
||||
# Copyright (c) 2013, Kevin Greenan (kmgreen2@gmail.com)
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution. THIS SOFTWARE IS
|
||||
# PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
|
||||
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
# NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
run_under_valgrind = False
|
||||
test_cmd_prefix = ""
|
||||
log_filename_prefix = ""
|
||||
|
||||
|
||||
class CoreTests():
|
||||
|
||||
def __init__(self, *args):
|
||||
self.pyeclib_core_test = "test_pyeclib_c.py"
|
||||
self.pyeclib_iface_test = "test_pyeclib_api.py"
|
||||
|
||||
def setUp(self):
|
||||
# Determine which directory we're in
|
||||
dirs = os.getcwd().split('/')
|
||||
if dirs[-1] == 'test':
|
||||
self.pyeclib_test_dir = "."
|
||||
else:
|
||||
self.pyeclib_test_dir = "./test"
|
||||
|
||||
# Create the array of tests to run
|
||||
self.py_test_dirs = [
|
||||
(self.pyeclib_test_dir, self.pyeclib_core_test),
|
||||
(self.pyeclib_test_dir, self.pyeclib_iface_test)
|
||||
]
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def invoke_core_tests(self):
|
||||
cur_dir = os.getcwd()
|
||||
print("\n")
|
||||
for (dir, test) in self.py_test_dirs:
|
||||
sys.stdout.write("Running test %s ... " % test)
|
||||
sys.stdout.flush()
|
||||
os.chdir(dir)
|
||||
if os.path.isfile(test):
|
||||
pythonpath = "PYTHONPATH=%s:%s" % \
|
||||
(cur_dir, os.path.dirname(cur_dir))
|
||||
ret = os.system(
|
||||
"%s %s python %s >%s/%s.%s.out 2>&1" %
|
||||
(pythonpath, test_cmd_prefix, test, cur_dir,
|
||||
log_filename_prefix, test))
|
||||
|
||||
assert(0 == ret)
|
||||
os.system("rm -f *.pyc")
|
||||
os.chdir(cur_dir)
|
||||
print('ok')
|
||||
else:
|
||||
print('failed')
|
||||
|
||||
|
||||
# Invoke this script as "python test_core_valgrind.py"
|
||||
# for the "valgrind" variant
|
||||
# (test_core_valgrind.py is a symlink to test_core.py)
|
||||
if __name__ == "__main__":
|
||||
if '_valgrind' in sys.argv[0]:
|
||||
if (0 != os.system("which valgrind")):
|
||||
print("You don't appear to have 'valgrind' installed")
|
||||
sys.exit(-1)
|
||||
run_under_valgrind = True
|
||||
test_cmd_prefix = "valgrind --leak-check=full "
|
||||
log_filename_prefix = "valgrind"
|
||||
coretests = CoreTests()
|
||||
coretests.setUp()
|
||||
coretests.invoke_core_tests()
|
||||
coretests.tearDown()
|
||||
@@ -1 +0,0 @@
|
||||
run_core_tests_manual.py
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,812 +0,0 @@
|
||||
# Copyright (c) 2013, Kevin Greenan (kmgreen2@gmail.com)
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution. THIS SOFTWARE IS
|
||||
# PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
|
||||
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
# NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import os
|
||||
import random
|
||||
from string import ascii_letters, ascii_uppercase, digits
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from distutils.version import StrictVersion
|
||||
from itertools import combinations
|
||||
|
||||
import pyeclib.ec_iface
|
||||
from pyeclib.ec_iface import ECBackendNotSupported
|
||||
from pyeclib.ec_iface import ECDriver
|
||||
from pyeclib.ec_iface import ECDriverError
|
||||
from pyeclib.ec_iface import ECInsufficientFragments
|
||||
from pyeclib.ec_iface import ECInvalidFragmentMetadata
|
||||
from pyeclib.ec_iface import ECInvalidParameter
|
||||
from pyeclib.ec_iface import PyECLib_EC_Types
|
||||
from pyeclib.ec_iface import ALL_EC_TYPES
|
||||
from pyeclib.ec_iface import VALID_EC_TYPES
|
||||
from pyeclib.ec_iface import LIBERASURECODE_VERSION
|
||||
import resource
|
||||
|
||||
|
||||
if sys.version < '3':
|
||||
def b2i(b):
|
||||
return ord(b)
|
||||
else:
|
||||
def b2i(b):
|
||||
return b
|
||||
|
||||
|
||||
class TestNullDriver(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.null_driver = ECDriver(
|
||||
library_import_str="pyeclib.core.ECNullDriver", k=8, m=2)
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def test_null_driver(self):
|
||||
self.null_driver.encode('')
|
||||
self.null_driver.decode([])
|
||||
|
||||
|
||||
class TestStripeDriver(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.stripe_driver = ECDriver(
|
||||
library_import_str="pyeclib.core.ECStripingDriver", k=8, m=0)
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
|
||||
class TestPyECLibDriver(unittest.TestCase):
|
||||
|
||||
def __init__(self, *args):
|
||||
# Create the temp files needed for testing
|
||||
self.file_sizes = ["100-K"]
|
||||
self.files = {}
|
||||
self.num_iterations = 100
|
||||
self._create_tmp_files()
|
||||
|
||||
unittest.TestCase.__init__(self, *args)
|
||||
|
||||
def _create_tmp_files(self):
|
||||
"""
|
||||
Create the temporary files needed for testing. Use the tempfile
|
||||
package so that the files will be automatically removed during
|
||||
garbage collection.
|
||||
"""
|
||||
for size_str in self.file_sizes:
|
||||
# Determine the size of the file to create
|
||||
size_desc = size_str.split("-")
|
||||
size = int(size_desc[0])
|
||||
if size_desc[1] == 'M':
|
||||
size *= 1000000
|
||||
elif size_desc[1] == 'K':
|
||||
size *= 1000
|
||||
|
||||
# Create the dictionary of files to test with
|
||||
buf = ''.join(random.choice(ascii_letters) for i in range(size))
|
||||
if sys.version_info >= (3,):
|
||||
buf = buf.encode('ascii')
|
||||
tmp_file = tempfile.NamedTemporaryFile()
|
||||
tmp_file.write(buf)
|
||||
self.files[size_str] = tmp_file
|
||||
|
||||
def setUp(self):
|
||||
# Ensure that the file offset is set to the head of the file
|
||||
for _, tmp_file in self.files.items():
|
||||
tmp_file.seek(0, 0)
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def test_invalid_km_args(self):
|
||||
for ec_type in VALID_EC_TYPES:
|
||||
# missing k
|
||||
with self.assertRaises(ECDriverError) as err_context:
|
||||
ECDriver(ec_type=ec_type, m=1)
|
||||
|
||||
self.assertEqual(str(err_context.exception),
|
||||
"Invalid Argument: k is required")
|
||||
|
||||
# missing m
|
||||
with self.assertRaises(ECDriverError) as err_context:
|
||||
ECDriver(ec_type=ec_type, k=1)
|
||||
|
||||
self.assertEqual(str(err_context.exception),
|
||||
"Invalid Argument: m is required")
|
||||
|
||||
with self.assertRaises(ECDriverError) as err_context:
|
||||
# k is smaller than 1
|
||||
ECDriver(ec_type=ec_type, k=-100, m=1)
|
||||
self.assertEqual(str(err_context.exception),
|
||||
"Invalid number of data fragments (k)")
|
||||
|
||||
with self.assertRaises(ECDriverError) as err_context:
|
||||
# m is smaller than 1
|
||||
ECDriver(ec_type=ec_type, k=1, m=-100)
|
||||
self.assertEqual(str(err_context.exception),
|
||||
"Invalid number of parity fragments (m)")
|
||||
|
||||
def test_valid_ec_types(self):
|
||||
# Build list of available types and compare to VALID_EC_TYPES
|
||||
available_ec_types = []
|
||||
for _type in ALL_EC_TYPES:
|
||||
try:
|
||||
if _type is 'shss':
|
||||
_k = 10
|
||||
_m = 4
|
||||
elif _type is 'libphazr':
|
||||
_k = 4
|
||||
_m = 4
|
||||
else:
|
||||
_k = 10
|
||||
_m = 5
|
||||
ECDriver(k=_k, m=_m, ec_type=_type, validate=True)
|
||||
available_ec_types.append(_type)
|
||||
except Exception:
|
||||
# ignore any errors, assume backend not available
|
||||
pass
|
||||
self.assertEqual(available_ec_types, VALID_EC_TYPES)
|
||||
|
||||
def test_valid_algo(self):
|
||||
print("")
|
||||
for _type in ALL_EC_TYPES:
|
||||
# Check if this algo works
|
||||
if _type not in VALID_EC_TYPES:
|
||||
print("Skipping test for %s backend" % _type)
|
||||
continue
|
||||
try:
|
||||
if _type is 'shss':
|
||||
_instance = ECDriver(k=10, m=4, ec_type=_type)
|
||||
elif _type is 'libphazr':
|
||||
_instance = ECDriver(k=4, m=4, ec_type=_type)
|
||||
else:
|
||||
_instance = ECDriver(k=10, m=5, ec_type=_type)
|
||||
except ECDriverError:
|
||||
self.fail("%s algorithm not supported" % _type)
|
||||
|
||||
self.assertRaises(ECBackendNotSupported, ECDriver, k=10, m=5,
|
||||
ec_type="invalid_algo")
|
||||
|
||||
def get_pyeclib_testspec(self, csum="none"):
|
||||
pyeclib_drivers = []
|
||||
_type1 = 'jerasure_rs_vand'
|
||||
if _type1 in VALID_EC_TYPES:
|
||||
pyeclib_drivers.append(ECDriver(k=12, m=2, ec_type=_type1,
|
||||
chksum_type=csum))
|
||||
pyeclib_drivers.append(ECDriver(k=11, m=2, ec_type=_type1,
|
||||
chksum_type=csum))
|
||||
pyeclib_drivers.append(ECDriver(k=10, m=2, ec_type=_type1,
|
||||
chksum_type=csum))
|
||||
pyeclib_drivers.append(ECDriver(k=8, m=4, ec_type=_type1,
|
||||
chksum_type=csum))
|
||||
_type2 = 'liberasurecode_rs_vand'
|
||||
if _type2 in VALID_EC_TYPES:
|
||||
pyeclib_drivers.append(ECDriver(k=12, m=2, ec_type=_type2,
|
||||
chksum_type=csum))
|
||||
pyeclib_drivers.append(ECDriver(k=11, m=2, ec_type=_type2,
|
||||
chksum_type=csum))
|
||||
pyeclib_drivers.append(ECDriver(k=10, m=2, ec_type=_type2,
|
||||
chksum_type=csum))
|
||||
pyeclib_drivers.append(ECDriver(k=8, m=4, ec_type=_type2,
|
||||
chksum_type=csum))
|
||||
_type3_1 = 'flat_xor_hd'
|
||||
if _type3_1 in VALID_EC_TYPES:
|
||||
pyeclib_drivers.append(ECDriver(k=12, m=6, ec_type=_type3_1,
|
||||
chksum_type=csum))
|
||||
pyeclib_drivers.append(ECDriver(k=10, m=5, ec_type=_type3_1,
|
||||
chksum_type=csum))
|
||||
_type3_2 = 'flat_xor_hd_4'
|
||||
if _type3_2 in VALID_EC_TYPES:
|
||||
pyeclib_drivers.append(ECDriver(k=12, m=6, ec_type=_type3_2,
|
||||
chksum_type=csum))
|
||||
pyeclib_drivers.append(ECDriver(k=10, m=5, ec_type=_type3_2,
|
||||
chksum_type=csum))
|
||||
_type4 = 'shss'
|
||||
if _type4 in VALID_EC_TYPES:
|
||||
pyeclib_drivers.append(ECDriver(k=10, m=4, ec_type=_type4,
|
||||
chksum_type=csum))
|
||||
pyeclib_drivers.append(ECDriver(k=20, m=4, ec_type=_type4,
|
||||
chksum_type=csum))
|
||||
pyeclib_drivers.append(ECDriver(k=11, m=7, ec_type=_type4,
|
||||
chksum_type=csum))
|
||||
|
||||
_type5 = 'isa_l_rs_vand'
|
||||
if _type5 in VALID_EC_TYPES:
|
||||
pyeclib_drivers.append(ECDriver(k=12, m=2, ec_type=_type5,
|
||||
chksum_type=csum))
|
||||
pyeclib_drivers.append(ECDriver(k=11, m=2, ec_type=_type5,
|
||||
chksum_type=csum))
|
||||
pyeclib_drivers.append(ECDriver(k=10, m=2, ec_type=_type5,
|
||||
chksum_type=csum))
|
||||
pyeclib_drivers.append(ECDriver(k=8, m=4, ec_type=_type5,
|
||||
chksum_type=csum))
|
||||
_type6 = 'isa_l_rs_cauchy'
|
||||
if _type6 in VALID_EC_TYPES:
|
||||
pyeclib_drivers.append(ECDriver(k=12, m=2, ec_type=_type6,
|
||||
chksum_type=csum))
|
||||
pyeclib_drivers.append(ECDriver(k=11, m=2, ec_type=_type6,
|
||||
chksum_type=csum))
|
||||
pyeclib_drivers.append(ECDriver(k=10, m=2, ec_type=_type6,
|
||||
chksum_type=csum))
|
||||
pyeclib_drivers.append(ECDriver(k=8, m=4, ec_type=_type6,
|
||||
chksum_type=csum))
|
||||
pyeclib_drivers.append(ECDriver(k=11, m=7, ec_type=_type6,
|
||||
chksum_type=csum))
|
||||
|
||||
_type7 = 'libphazr'
|
||||
if _type7 in VALID_EC_TYPES:
|
||||
pyeclib_drivers.append(ECDriver(k=4, m=4, ec_type=_type7,
|
||||
chksum_type=csum))
|
||||
return pyeclib_drivers
|
||||
|
||||
def test_small_encode(self):
|
||||
pyeclib_drivers = self.get_pyeclib_testspec()
|
||||
encode_strs = [b"a", b"hello", b"hellohyhi", b"yo"]
|
||||
|
||||
for pyeclib_driver in pyeclib_drivers:
|
||||
for encode_str in encode_strs:
|
||||
encoded_fragments = pyeclib_driver.encode(encode_str)
|
||||
decoded_str = pyeclib_driver.decode(encoded_fragments)
|
||||
|
||||
self.assertTrue(decoded_str == encode_str)
|
||||
|
||||
def test_encode_invalid_params(self):
|
||||
pyeclib_drivers = self.get_pyeclib_testspec()
|
||||
encode_args = [u'\U0001F0A1', 3, object(), None, {}, []]
|
||||
|
||||
for pyeclib_driver in pyeclib_drivers:
|
||||
for encode_str in encode_args:
|
||||
with self.assertRaises(ECInvalidParameter):
|
||||
pyeclib_driver.encode(encode_str)
|
||||
|
||||
def test_attribute_error_in_the_error_handling(self):
|
||||
pyeclib_drivers = self.get_pyeclib_testspec()
|
||||
self.assertGreater(len(pyeclib_drivers), 0)
|
||||
pyeclib_driver = pyeclib_drivers[0]
|
||||
|
||||
del pyeclib.ec_iface.ECInvalidParameter
|
||||
try:
|
||||
with self.assertRaises(AttributeError): # !!
|
||||
pyeclib_driver.encode(3)
|
||||
finally:
|
||||
pyeclib.ec_iface.ECInvalidParameter = ECInvalidParameter
|
||||
|
||||
def test_import_error_in_the_error_handling(self):
|
||||
pyeclib_drivers = self.get_pyeclib_testspec()
|
||||
self.assertGreater(len(pyeclib_drivers), 0)
|
||||
pyeclib_driver = pyeclib_drivers[0]
|
||||
|
||||
from six.moves import builtins
|
||||
real_import = builtins.__import__
|
||||
|
||||
def fake_import(*a, **kw):
|
||||
raise ImportError
|
||||
|
||||
builtins.__import__ = fake_import
|
||||
try:
|
||||
with self.assertRaises(ImportError): # !!
|
||||
pyeclib_driver.encode(3)
|
||||
finally:
|
||||
builtins.__import__ = real_import
|
||||
|
||||
def test_decode_reconstruct_with_fragment_iterator(self):
|
||||
pyeclib_drivers = self.get_pyeclib_testspec()
|
||||
encode_strs = [b"a", b"hello", b"hellohyhi", b"yo"]
|
||||
|
||||
for pyeclib_driver in pyeclib_drivers:
|
||||
for encode_str in encode_strs:
|
||||
encoded_fragments = pyeclib_driver.encode(encode_str)
|
||||
|
||||
idxs_to_remove = random.sample(range(
|
||||
pyeclib_driver.k + pyeclib_driver.m), 2)
|
||||
available_fragments = encoded_fragments[:]
|
||||
for i in sorted(idxs_to_remove, reverse=True):
|
||||
available_fragments.pop(i)
|
||||
|
||||
frag_iter = iter(available_fragments)
|
||||
decoded_str = pyeclib_driver.decode(frag_iter)
|
||||
self.assertEqual(decoded_str, encode_str)
|
||||
|
||||
# Since the iterator is exhausted, we can't decode again
|
||||
with self.assertRaises(ECDriverError) as exc_mgr:
|
||||
pyeclib_driver.decode(frag_iter)
|
||||
self.assertEqual(
|
||||
'Invalid fragment payload in ECPyECLibDriver.decode',
|
||||
str(exc_mgr.exception))
|
||||
|
||||
frag_iter = iter(available_fragments)
|
||||
reconstructed_fragments = pyeclib_driver.reconstruct(
|
||||
frag_iter, idxs_to_remove)
|
||||
self.assertEqual(len(reconstructed_fragments),
|
||||
len(idxs_to_remove))
|
||||
for i, data in zip(idxs_to_remove, reconstructed_fragments):
|
||||
self.assertEqual(data, encoded_fragments[i])
|
||||
|
||||
# Since the iterator is exhausted, we can't decode again
|
||||
with self.assertRaises(ECDriverError) as exc_mgr:
|
||||
pyeclib_driver.reconstruct(frag_iter, idxs_to_remove)
|
||||
self.assertEqual(
|
||||
'Invalid fragment payload in ECPyECLibDriver.reconstruct',
|
||||
str(exc_mgr.exception))
|
||||
|
||||
def check_metadata_formatted(self, k, m, ec_type, chksum_type):
|
||||
|
||||
if ec_type not in VALID_EC_TYPES:
|
||||
return
|
||||
|
||||
filesize = 1024 * 1024 * 3
|
||||
file_str = ''.join(random.choice(ascii_letters)
|
||||
for i in range(filesize))
|
||||
file_bytes = file_str.encode('utf-8')
|
||||
|
||||
pyeclib_driver = ECDriver(k=k, m=m, ec_type=ec_type,
|
||||
chksum_type=chksum_type)
|
||||
|
||||
fragments = pyeclib_driver.encode(file_bytes)
|
||||
|
||||
f = 0
|
||||
for fragment in fragments:
|
||||
metadata = pyeclib_driver.get_metadata(fragment, 1)
|
||||
if 'index' in metadata:
|
||||
self.assertEqual(metadata['index'], f)
|
||||
else:
|
||||
self.assertTrue(False)
|
||||
|
||||
if 'chksum_mismatch' in metadata:
|
||||
self.assertEqual(metadata['chksum_mismatch'], 0)
|
||||
else:
|
||||
self.assertTrue(False)
|
||||
|
||||
if 'backend_id' in metadata:
|
||||
self.assertEqual(metadata['backend_id'], ec_type)
|
||||
else:
|
||||
self.assertTrue(False)
|
||||
|
||||
if 'orig_data_size' in metadata:
|
||||
self.assertEqual(metadata['orig_data_size'], 3145728)
|
||||
else:
|
||||
self.assertTrue(False)
|
||||
|
||||
if 'chksum_type' in metadata:
|
||||
self.assertEqual(metadata['chksum_type'], 'crc32')
|
||||
else:
|
||||
self.assertTrue(False)
|
||||
|
||||
if 'backend_version' not in metadata:
|
||||
self.assertTrue(False)
|
||||
|
||||
if 'chksum' not in metadata:
|
||||
self.assertTrue(False)
|
||||
|
||||
if 'size' not in metadata:
|
||||
self.assertTrue(False)
|
||||
|
||||
f += 1
|
||||
|
||||
def test_get_metadata_formatted(self):
|
||||
self.check_metadata_formatted(12, 2, "jerasure_rs_vand",
|
||||
"inline_crc32")
|
||||
self.check_metadata_formatted(12, 2, "liberasurecode_rs_vand",
|
||||
"inline_crc32")
|
||||
self.check_metadata_formatted(8, 4, "liberasurecode_rs_vand",
|
||||
"inline_crc32")
|
||||
|
||||
def test_verify_fragment_inline_chksum_fail(self):
|
||||
pyeclib_drivers = self.get_pyeclib_testspec("inline_crc32")
|
||||
filesize = 1024 * 1024 * 3
|
||||
file_str = ''.join(random.choice(ascii_letters)
|
||||
for i in range(filesize))
|
||||
file_bytes = file_str.encode('utf-8')
|
||||
|
||||
for pyeclib_driver in pyeclib_drivers:
|
||||
fragments = pyeclib_driver.encode(file_bytes)
|
||||
|
||||
fragment_metadata_list = []
|
||||
|
||||
first_fragment_to_corrupt = random.randint(0, len(fragments))
|
||||
num_to_corrupt = 2
|
||||
fragments_to_corrupt = [
|
||||
(first_fragment_to_corrupt + i) % len(fragments)
|
||||
for i in range(num_to_corrupt + 1)]
|
||||
fragments_to_corrupt.sort()
|
||||
|
||||
i = 0
|
||||
for fragment in fragments:
|
||||
if i in fragments_to_corrupt:
|
||||
corrupted_fragment = fragment[:100] +\
|
||||
(str(chr((b2i(fragment[100]) + 0x1)
|
||||
% 128))).encode('utf-8') + fragment[101:]
|
||||
fragment_metadata_list.append(
|
||||
pyeclib_driver.get_metadata(corrupted_fragment))
|
||||
else:
|
||||
fragment_metadata_list.append(
|
||||
pyeclib_driver.get_metadata(fragment))
|
||||
i += 1
|
||||
|
||||
expected_ret_value = {"status": -206,
|
||||
"reason": "Bad checksum",
|
||||
"bad_fragments": fragments_to_corrupt}
|
||||
self.assertEqual(
|
||||
pyeclib_driver.verify_stripe_metadata(fragment_metadata_list),
|
||||
expected_ret_value)
|
||||
|
||||
def test_verify_fragment_inline_chksum_succeed(self):
|
||||
pyeclib_drivers = self.get_pyeclib_testspec("inline_crc32")
|
||||
|
||||
filesize = 1024 * 1024 * 3
|
||||
file_str = ''.join(random.choice(ascii_letters)
|
||||
for i in range(filesize))
|
||||
file_bytes = file_str.encode('utf-8')
|
||||
|
||||
for pyeclib_driver in pyeclib_drivers:
|
||||
fragments = pyeclib_driver.encode(file_bytes)
|
||||
|
||||
fragment_metadata_list = []
|
||||
|
||||
for fragment in fragments:
|
||||
fragment_metadata_list.append(
|
||||
pyeclib_driver.get_metadata(fragment))
|
||||
|
||||
expected_ret_value = {"status": 0}
|
||||
|
||||
self.assertTrue(pyeclib_driver.verify_stripe_metadata(
|
||||
fragment_metadata_list) == expected_ret_value)
|
||||
|
||||
def test_get_segment_byterange_info(self):
|
||||
pyeclib_drivers = self.get_pyeclib_testspec()
|
||||
if not pyeclib_drivers:
|
||||
return
|
||||
|
||||
file_size = 1024 * 1024
|
||||
segment_size = 3 * 1024
|
||||
|
||||
ranges = [
|
||||
(0, 1),
|
||||
(1, 12),
|
||||
(10, 1000),
|
||||
(0, segment_size - 1),
|
||||
(1, segment_size + 1),
|
||||
(segment_size - 1, 2 * segment_size)]
|
||||
|
||||
expected_results = {}
|
||||
|
||||
expected_results[(0, 1)] = {0: (0, 1)}
|
||||
expected_results[(1, 12)] = {0: (1, 12)}
|
||||
expected_results[(10, 1000)] = {0: (10, 1000)}
|
||||
expected_results[(0, segment_size - 1)] = {0: (0, segment_size - 1)}
|
||||
expected_results[(1, segment_size + 1)
|
||||
] = {0: (1, segment_size - 1), 1: (0, 1)}
|
||||
expected_results[
|
||||
(segment_size - 1, 2 * segment_size)] = {
|
||||
0: (segment_size - 1, segment_size - 1),
|
||||
1: (0, segment_size - 1),
|
||||
2: (0, 0)}
|
||||
|
||||
results = pyeclib_drivers[0].get_segment_info_byterange(
|
||||
ranges, file_size, segment_size)
|
||||
|
||||
for exp_result_key in expected_results:
|
||||
self.assertIn(exp_result_key, results)
|
||||
self.assertTrue(
|
||||
len(results[exp_result_key]) ==
|
||||
len(expected_results[exp_result_key]))
|
||||
exp_result_map = expected_results[exp_result_key]
|
||||
for segment_key in exp_result_map:
|
||||
self.assertIn(segment_key, results[exp_result_key])
|
||||
self.assertTrue(
|
||||
results[exp_result_key][segment_key] ==
|
||||
expected_results[exp_result_key][segment_key])
|
||||
|
||||
def test_get_segment_info(self):
|
||||
pyeclib_drivers = self.get_pyeclib_testspec()
|
||||
|
||||
file_sizes = [
|
||||
1024 * 1024,
|
||||
2 * 1024 * 1024,
|
||||
10 * 1024 * 1024,
|
||||
10 * 1024 * 1024 + 7]
|
||||
segment_sizes = [3 * 1024, 1024 * 1024]
|
||||
segment_strings = {}
|
||||
|
||||
#
|
||||
# Generate some test segments for each segment size.
|
||||
# Use 2 * segment size, because last segment may be
|
||||
# greater than segment_size
|
||||
#
|
||||
char_set = ascii_uppercase + digits
|
||||
for segment_size in segment_sizes:
|
||||
segment_strings[segment_size] = ''.join(
|
||||
random.choice(char_set) for i in range(segment_size * 2))
|
||||
|
||||
for pyeclib_driver in pyeclib_drivers:
|
||||
for file_size in file_sizes:
|
||||
for segment_size in segment_sizes:
|
||||
#
|
||||
# Compute the segment info
|
||||
#
|
||||
segment_info = pyeclib_driver.get_segment_info(
|
||||
file_size,
|
||||
segment_size)
|
||||
|
||||
num_segments = segment_info['num_segments']
|
||||
segment_size = segment_info['segment_size']
|
||||
fragment_size = segment_info['fragment_size']
|
||||
last_segment_size = segment_info['last_segment_size']
|
||||
last_fragment_size = segment_info['last_fragment_size']
|
||||
|
||||
computed_file_size = (
|
||||
(num_segments - 1) * segment_size) + last_segment_size
|
||||
|
||||
#
|
||||
# Verify that the segment sizes add up
|
||||
#
|
||||
self.assertTrue(computed_file_size == file_size)
|
||||
|
||||
encoded_fragments = pyeclib_driver.encode(
|
||||
(segment_strings[segment_size][
|
||||
:segment_size]).encode('utf-8'))
|
||||
|
||||
#
|
||||
# Verify the fragment size
|
||||
#
|
||||
self.assertTrue(fragment_size == len(encoded_fragments[0]))
|
||||
|
||||
if last_segment_size > 0:
|
||||
encoded_fragments = pyeclib_driver.encode(
|
||||
(segment_strings[segment_size][
|
||||
:last_segment_size]).encode('utf-8'))
|
||||
|
||||
#
|
||||
# Verify the last fragment size, if there is one
|
||||
#
|
||||
self.assertTrue(
|
||||
last_fragment_size == len(
|
||||
encoded_fragments[0]))
|
||||
|
||||
def test_greedy_decode_reconstruct_combination(self):
|
||||
# the testing spec defined at get_pyeclib_testspec() method
|
||||
# and if you want to test either other parameters or backends,
|
||||
# you can add the spec you want to test there.
|
||||
pyeclib_drivers = self.get_pyeclib_testspec()
|
||||
orig_data = os.urandom(1024 ** 2)
|
||||
for pyeclib_driver in pyeclib_drivers:
|
||||
encoded = pyeclib_driver.encode(orig_data)
|
||||
# make all fragment like (index, frag_data) format to feed
|
||||
# to combinations
|
||||
frags = [(i, frag) for i, frag in enumerate(encoded)]
|
||||
num_frags = pyeclib_driver.k + pyeclib_driver.m
|
||||
|
||||
if pyeclib_driver.ec_type == PyECLib_EC_Types.flat_xor_hd:
|
||||
# flat_xord_hd is guaranteed to work with 2 or 3 failures
|
||||
tolerable_failures = pyeclib_driver.hd - 1
|
||||
else:
|
||||
# ... while others can tolerate more
|
||||
tolerable_failures = pyeclib_driver.m
|
||||
|
||||
for check_frags_tuples in combinations(
|
||||
frags, num_frags - tolerable_failures):
|
||||
# extract check_frags_tuples from [(index, data bytes), ...]
|
||||
# to [index, index, ...] and [data bytes, data bytes, ...]
|
||||
indexes, check_frags = zip(*check_frags_tuples)
|
||||
decoded = pyeclib_driver.decode(check_frags)
|
||||
self.assertEqual(
|
||||
orig_data, decoded,
|
||||
"assertion fail in decode %s from:%s" %
|
||||
(pyeclib_driver, indexes))
|
||||
holes = [index for index in range(num_frags) if
|
||||
index not in indexes]
|
||||
for hole in holes:
|
||||
reconed = pyeclib_driver.reconstruct(
|
||||
check_frags, [hole])[0]
|
||||
self.assertEqual(
|
||||
frags[hole][1], reconed,
|
||||
"assertion fail in reconstruct %s target:%s "
|
||||
"from:%s" % (pyeclib_driver, hole, indexes))
|
||||
|
||||
def test_rs(self):
|
||||
pyeclib_drivers = self.get_pyeclib_testspec()
|
||||
|
||||
for pyeclib_driver in pyeclib_drivers:
|
||||
for file_size in self.file_sizes:
|
||||
tmp_file = self.files[file_size]
|
||||
tmp_file.seek(0)
|
||||
whole_file_bytes = tmp_file.read()
|
||||
|
||||
encode_input = whole_file_bytes
|
||||
orig_fragments = pyeclib_driver.encode(encode_input)
|
||||
|
||||
for iter in range(self.num_iterations):
|
||||
num_missing = 2
|
||||
idxs_to_remove = random.sample(range(
|
||||
pyeclib_driver.k + pyeclib_driver.m), num_missing)
|
||||
fragments = orig_fragments[:]
|
||||
|
||||
# Reverse sort the list, so we can always
|
||||
# remove from the original index
|
||||
idxs_to_remove.sort(reverse=True)
|
||||
for idx in idxs_to_remove:
|
||||
fragments.pop(idx)
|
||||
|
||||
#
|
||||
# Test decoder
|
||||
#
|
||||
decoded_string = pyeclib_driver.decode(fragments)
|
||||
|
||||
self.assertTrue(encode_input == decoded_string)
|
||||
|
||||
#
|
||||
# Test reconstructor
|
||||
#
|
||||
reconstructed_fragments = pyeclib_driver.reconstruct(
|
||||
fragments,
|
||||
idxs_to_remove)
|
||||
self.assertEqual(len(reconstructed_fragments),
|
||||
len(idxs_to_remove))
|
||||
for idx, frag_data in zip(idxs_to_remove,
|
||||
reconstructed_fragments):
|
||||
self.assertEqual(
|
||||
frag_data, orig_fragments[idx],
|
||||
'Failed to reconstruct fragment %d!' % idx)
|
||||
|
||||
#
|
||||
# Test decode with integrity checks
|
||||
#
|
||||
first_fragment_to_corrupt = random.randint(
|
||||
0, len(fragments))
|
||||
num_to_corrupt = min(len(fragments), pyeclib_driver.m + 1)
|
||||
fragments_to_corrupt = [
|
||||
(first_fragment_to_corrupt + i) % len(fragments)
|
||||
for i in range(num_to_corrupt)]
|
||||
|
||||
if StrictVersion(LIBERASURECODE_VERSION) < \
|
||||
StrictVersion('1.2.0'):
|
||||
# if liberasurecode is older than the version supports
|
||||
# fragment integrity check, skip following test
|
||||
continue
|
||||
i = 0
|
||||
for fragment in fragments:
|
||||
if i in fragments_to_corrupt:
|
||||
corrupted_fragment = (
|
||||
"0" * len(fragment)).encode('utf-8')
|
||||
fragments[i] = corrupted_fragment
|
||||
i += 1
|
||||
|
||||
self.assertRaises(ECInvalidFragmentMetadata,
|
||||
pyeclib_driver.decode,
|
||||
fragments, force_metadata_checks=True)
|
||||
|
||||
def get_available_backend(self, k, m, ec_type, chksum_type="inline_crc32"):
|
||||
if ec_type[:11] == "flat_xor_hd":
|
||||
return ECDriver(k=k, m=m, ec_type="flat_xor_hd",
|
||||
chksum_type=chksum_type)
|
||||
elif ec_type in VALID_EC_TYPES:
|
||||
return ECDriver(k=k, m=m, ec_type=ec_type, chksum_type=chksum_type)
|
||||
else:
|
||||
return None
|
||||
|
||||
def test_liberasurecode_insufficient_frags_error(self):
|
||||
file_size = self.file_sizes[0]
|
||||
tmp_file = self.files[file_size]
|
||||
tmp_file.seek(0)
|
||||
whole_file_bytes = tmp_file.read()
|
||||
for ec_type in ['flat_xor_hd_3', 'liberasurecode_rs_vand']:
|
||||
if ec_type in VALID_EC_TYPES:
|
||||
pyeclib_driver = self.get_available_backend(
|
||||
k=10, m=5, ec_type=ec_type)
|
||||
fragments = pyeclib_driver.encode(whole_file_bytes)
|
||||
self.assertRaises(ECInsufficientFragments,
|
||||
pyeclib_driver.reconstruct,
|
||||
[fragments[0]], [1, 2, 3, 4, 5, 6])
|
||||
|
||||
def test_min_parity_fragments_needed(self):
|
||||
pyeclib_drivers = []
|
||||
for ec_type in ['flat_xor_hd_3', 'liberasurecode_rs_vand']:
|
||||
if ec_type in VALID_EC_TYPES:
|
||||
pyeclib_drivers.append(ECDriver(k=10, m=5, ec_type=ec_type))
|
||||
self.assertTrue(
|
||||
pyeclib_drivers[0].min_parity_fragments_needed() == 1)
|
||||
|
||||
def test_pyeclib_driver_repr_expression(self):
|
||||
pyeclib_drivers = self.get_pyeclib_testspec()
|
||||
for driver in pyeclib_drivers:
|
||||
if driver.ec_type.name == 'flat_xor_hd':
|
||||
name = 'flat_xor_hd_%s' % driver.hd
|
||||
else:
|
||||
name = driver.ec_type.name
|
||||
|
||||
self.assertEqual(
|
||||
"ECDriver(ec_type='%s', k=%s, m=%s)" %
|
||||
(name, driver.k, driver.m), repr(driver))
|
||||
|
||||
def test_get_segment_info_memory_usage(self):
|
||||
for ec_driver in self.get_pyeclib_testspec():
|
||||
self._test_get_segment_info_memory_usage(ec_driver)
|
||||
|
||||
def _test_get_segment_info_memory_usage(self, ec_driver):
|
||||
# 1. Preapre the expected memory allocation
|
||||
ec_driver.get_segment_info(1024 * 1024, 1024 * 1024)
|
||||
loop_range = range(1000)
|
||||
|
||||
# 2. Get current memory usage
|
||||
usage = resource.getrusage(resource.RUSAGE_SELF)[2]
|
||||
|
||||
# 3. Loop to call get_segment_info
|
||||
for x in loop_range:
|
||||
ec_driver.get_segment_info(1024 * 1024, 1024 * 1024)
|
||||
|
||||
# 4. memory usage shoudln't be increased
|
||||
self.assertEqual(usage, resource.getrusage(resource.RUSAGE_SELF)[2],
|
||||
'Memory usage is increased unexpectedly %s - %s' %
|
||||
(usage, resource.getrusage(resource.RUSAGE_SELF)[2]))
|
||||
|
||||
def test_get_metadata_memory_usage(self):
|
||||
for ec_driver in self.get_pyeclib_testspec():
|
||||
self._test_get_metadata_memory_usage(ec_driver)
|
||||
|
||||
def _test_get_metadata_memory_usage(self, ec_driver):
|
||||
# 1. Prepare the expected memory allocation
|
||||
encoded = ec_driver.encode(b'aaa')
|
||||
ec_driver.get_metadata(encoded[0], formatted=True)
|
||||
loop_range = range(400000)
|
||||
|
||||
# 2. Get current memory usage
|
||||
baseline_usage = resource.getrusage(resource.RUSAGE_SELF)[2]
|
||||
|
||||
# 3. Loop to call get_metadata
|
||||
for x in loop_range:
|
||||
ec_driver.get_metadata(encoded[0], formatted=True)
|
||||
|
||||
# 4. memory usage shouldn't increase
|
||||
new_usage = resource.getrusage(resource.RUSAGE_SELF)[2]
|
||||
self.assertEqual(baseline_usage, new_usage,
|
||||
'Memory usage is increased unexpectedly %s -> %s' %
|
||||
(baseline_usage, new_usage))
|
||||
|
||||
|
||||
class TestBackendsEnabled(unittest.TestCase):
|
||||
'''Based on TestPyECLibDriver.test_valid_algo above, but these tests
|
||||
should *always* either pass or skip.'''
|
||||
class __metaclass__(type):
|
||||
def __new__(meta, cls_name, cls_bases, cls_dict):
|
||||
for ec_type in ALL_EC_TYPES:
|
||||
def dummy(self, ec_type=ec_type):
|
||||
if ec_type not in VALID_EC_TYPES:
|
||||
raise unittest.SkipTest
|
||||
if ec_type == 'shss':
|
||||
k = 10
|
||||
m = 4
|
||||
elif ec_type == 'libphazr':
|
||||
k = 4
|
||||
m = 4
|
||||
else:
|
||||
k = 10
|
||||
m = 5
|
||||
ECDriver(k=k, m=m, ec_type=ec_type)
|
||||
dummy.__name__ = 'test_%s_available' % ec_type
|
||||
cls_dict[dummy.__name__] = dummy
|
||||
return type.__new__(meta, cls_name, cls_bases, cls_dict)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -1,473 +0,0 @@
|
||||
# Copyright (c) 2013, Kevin Greenan (kmgreen2@gmail.com)
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution. THIS SOFTWARE IS
|
||||
# PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
|
||||
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
# NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import random
|
||||
from string import ascii_letters
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import unittest
|
||||
|
||||
import pyeclib_c
|
||||
from pyeclib.ec_iface import PyECLib_EC_Types
|
||||
from pyeclib.ec_iface import VALID_EC_TYPES
|
||||
|
||||
|
||||
class Timer:
|
||||
|
||||
def __init__(self):
|
||||
self.start_time = 0
|
||||
self.end_time = 0
|
||||
|
||||
def start(self):
|
||||
self.start_time = time.time()
|
||||
|
||||
def stop(self):
|
||||
self.end_time = time.time()
|
||||
|
||||
def curr_delta(self):
|
||||
return self.end_time - self.start_time
|
||||
|
||||
def stop_and_return(self):
|
||||
self.end_time = time.time()
|
||||
return self.curr_delta()
|
||||
|
||||
|
||||
def require_backend(backend):
|
||||
return unittest.skipIf(backend not in VALID_EC_TYPES,
|
||||
"%s backend is not available" % backend)
|
||||
|
||||
|
||||
class TestPyECLib(unittest.TestCase):
|
||||
|
||||
def __init__(self, *args):
|
||||
self.num_datas = [12, 12, 12]
|
||||
self.num_parities = [2, 3, 4]
|
||||
self.iterations = 100
|
||||
|
||||
# EC algorithm and config parameters
|
||||
self.rs_types = [(PyECLib_EC_Types.jerasure_rs_vand),
|
||||
(PyECLib_EC_Types.jerasure_rs_cauchy),
|
||||
(PyECLib_EC_Types.isa_l_rs_vand),
|
||||
(PyECLib_EC_Types.liberasurecode_rs_vand),
|
||||
(PyECLib_EC_Types.isa_l_rs_cauchy)]
|
||||
self.xor_types = [(PyECLib_EC_Types.flat_xor_hd, 12, 6, 4),
|
||||
(PyECLib_EC_Types.flat_xor_hd, 10, 5, 4),
|
||||
(PyECLib_EC_Types.flat_xor_hd, 10, 5, 3)]
|
||||
self.shss = [(PyECLib_EC_Types.shss, 6, 3),
|
||||
(PyECLib_EC_Types.shss, 10, 4),
|
||||
(PyECLib_EC_Types.shss, 20, 4),
|
||||
(PyECLib_EC_Types.shss, 11, 7)]
|
||||
self.libphazr = [(PyECLib_EC_Types.libphazr, 4, 4)]
|
||||
|
||||
# Input temp files for testing
|
||||
self.sizes = ["101-K", "202-K", "303-K"]
|
||||
self.files = {}
|
||||
self._create_tmp_files()
|
||||
|
||||
unittest.TestCase.__init__(self, *args)
|
||||
|
||||
def _create_tmp_files(self):
|
||||
"""
|
||||
Create the temporary files needed for testing. Use the tempfile
|
||||
package so that the files will be automatically removed during
|
||||
garbage collection.
|
||||
"""
|
||||
for size_str in self.sizes:
|
||||
# Determine the size of the file to create
|
||||
size_desc = size_str.split("-")
|
||||
size = int(size_desc[0])
|
||||
if size_desc[1] == 'M':
|
||||
size *= 1000000
|
||||
elif size_desc[1] == 'K':
|
||||
size *= 1000
|
||||
|
||||
# Create the dictionary of files to test with
|
||||
buf = ''.join(random.choice(ascii_letters) for i in range(size))
|
||||
if sys.version_info >= (3,):
|
||||
buf = buf.encode('ascii')
|
||||
tmp_file = tempfile.NamedTemporaryFile('w+b')
|
||||
tmp_file.write(buf)
|
||||
self.files[size_str] = tmp_file
|
||||
|
||||
def get_tmp_file(self, name):
|
||||
"""
|
||||
Acquire a temp file from the dictionary of pre-built, random files
|
||||
with the seek position to the head of the file.
|
||||
"""
|
||||
tmp_file = self.files.get(name, None)
|
||||
|
||||
if tmp_file:
|
||||
tmp_file.seek(0, 0)
|
||||
|
||||
return tmp_file
|
||||
|
||||
def setUp(self):
|
||||
# Ensure that the file offset is set to the head of the file
|
||||
for _, tmp_file in self.files.items():
|
||||
tmp_file.seek(0, 0)
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def iter_available_types(self, ec_types):
|
||||
found_one = False
|
||||
for ec_type in ec_types:
|
||||
if ec_type.name not in VALID_EC_TYPES:
|
||||
continue
|
||||
found_one = True
|
||||
yield ec_type
|
||||
if not found_one:
|
||||
type_list = ', '.join(t.name for t in ec_types)
|
||||
raise unittest.SkipTest('No backend available in types: %r' %
|
||||
type_list)
|
||||
|
||||
def time_encode(self, num_data, num_parity, ec_type, hd,
|
||||
file_size, iterations):
|
||||
"""
|
||||
:return average encode time
|
||||
"""
|
||||
timer = Timer()
|
||||
handle = pyeclib_c.init(num_data, num_parity, ec_type, hd)
|
||||
whole_file_bytes = self.get_tmp_file(file_size).read()
|
||||
|
||||
timer.start()
|
||||
for l in range(iterations):
|
||||
pyeclib_c.encode(handle, whole_file_bytes)
|
||||
tsum = timer.stop_and_return()
|
||||
|
||||
return tsum / iterations
|
||||
|
||||
def time_decode(self,
|
||||
num_data, num_parity, ec_type, hd,
|
||||
file_size, iterations):
|
||||
"""
|
||||
:return 2-tuple, (success, average decode time)
|
||||
"""
|
||||
timer = Timer()
|
||||
tsum = 0
|
||||
handle = pyeclib_c.init(num_data, num_parity, ec_type, hd)
|
||||
whole_file_bytes = self.get_tmp_file(file_size).read()
|
||||
success = True
|
||||
|
||||
fragments = pyeclib_c.encode(handle, whole_file_bytes)
|
||||
orig_fragments = fragments[:]
|
||||
|
||||
for i in range(iterations):
|
||||
num_missing = hd - 1
|
||||
for j in range(num_missing):
|
||||
num_frags_left = len(fragments)
|
||||
idx = random.randint(0, num_frags_left - 1)
|
||||
fragments.pop(idx)
|
||||
|
||||
timer.start()
|
||||
decoded_file_bytes = pyeclib_c.decode(handle,
|
||||
fragments,
|
||||
len(fragments[0]))
|
||||
tsum += timer.stop_and_return()
|
||||
|
||||
fragments = orig_fragments[:]
|
||||
|
||||
if whole_file_bytes != decoded_file_bytes:
|
||||
success = False
|
||||
|
||||
return success, tsum / iterations
|
||||
|
||||
def time_range_decode(self,
|
||||
num_data, num_parity, ec_type, hd,
|
||||
file_size, iterations):
|
||||
"""
|
||||
:return 2-tuple, (success, average decode time)
|
||||
"""
|
||||
timer = Timer()
|
||||
tsum = 0
|
||||
handle = pyeclib_c.init(num_data, num_parity, ec_type, hd)
|
||||
whole_file_bytes = self.get_tmp_file(file_size).read()
|
||||
success = True
|
||||
|
||||
begins = [int(random.randint(0, len(whole_file_bytes) - 1))
|
||||
for i in range(3)]
|
||||
ends = [int(random.randint(begins[i], len(whole_file_bytes)))
|
||||
for i in range(3)]
|
||||
|
||||
ranges = list(zip(begins, ends))
|
||||
|
||||
fragments = pyeclib_c.encode(handle, whole_file_bytes)
|
||||
orig_fragments = fragments[:]
|
||||
|
||||
for i in range(iterations):
|
||||
num_missing = hd - 1
|
||||
for j in range(num_missing):
|
||||
num_frags_left = len(fragments)
|
||||
idx = random.randint(0, num_frags_left - 1)
|
||||
fragments.pop(idx)
|
||||
|
||||
timer.start()
|
||||
decoded_file_ranges = pyeclib_c.decode(handle,
|
||||
fragments,
|
||||
len(fragments[0]),
|
||||
ranges)
|
||||
tsum += timer.stop_and_return()
|
||||
|
||||
fragments = orig_fragments[:]
|
||||
|
||||
range_offset = 0
|
||||
for r in ranges:
|
||||
if whole_file_bytes[
|
||||
r[0]: r[1] + 1] != decoded_file_ranges[range_offset]:
|
||||
success = False
|
||||
range_offset += 1
|
||||
|
||||
return success, tsum / iterations
|
||||
|
||||
def time_reconstruct(self,
|
||||
num_data, num_parity, ec_type, hd,
|
||||
file_size, iterations):
|
||||
"""
|
||||
:return 2-tuple, (success, average reconstruct time)
|
||||
"""
|
||||
timer = Timer()
|
||||
tsum = 0
|
||||
handle = pyeclib_c.init(num_data, num_parity, ec_type, hd)
|
||||
whole_file_bytes = self.get_tmp_file(file_size).read()
|
||||
success = True
|
||||
|
||||
orig_fragments = pyeclib_c.encode(handle, whole_file_bytes)
|
||||
|
||||
for i in range(iterations):
|
||||
fragments = orig_fragments[:]
|
||||
num_missing = 1
|
||||
missing_idxs = []
|
||||
for j in range(num_missing):
|
||||
num_frags_left = len(fragments)
|
||||
idx = random.randint(0, num_frags_left - 1)
|
||||
while idx in missing_idxs:
|
||||
idx = random.randint(0, num_frags_left - 1)
|
||||
missing_idxs.append(idx)
|
||||
fragments.pop(idx)
|
||||
|
||||
timer.start()
|
||||
reconstructed_fragment = pyeclib_c.reconstruct(handle,
|
||||
fragments,
|
||||
len(fragments[0]),
|
||||
missing_idxs[0])
|
||||
tsum += timer.stop_and_return()
|
||||
|
||||
if orig_fragments[missing_idxs[0]] != reconstructed_fragment:
|
||||
success = False
|
||||
# Output the fragments for debugging
|
||||
with open("orig_fragments", "wb") as fd_orig:
|
||||
fd_orig.write(orig_fragments[missing_idxs[0]])
|
||||
with open("decoded_fragments", "wb") as fd_decoded:
|
||||
fd_decoded.write(reconstructed_fragment)
|
||||
print("Fragment %d was not reconstructed!!!" % missing_idxs[0])
|
||||
sys.exit(2)
|
||||
|
||||
return success, tsum / iterations
|
||||
|
||||
def get_throughput(self, avg_time, size_str):
|
||||
size_desc = size_str.split("-")
|
||||
size = float(size_desc[0])
|
||||
|
||||
if avg_time == 0:
|
||||
return '? (test finished too fast to calculate throughput)'
|
||||
|
||||
if size_desc[1] == 'M':
|
||||
throughput = size / avg_time
|
||||
elif size_desc[1] == 'K':
|
||||
throughput = (size / 1000.0) / avg_time
|
||||
|
||||
return format(throughput, '.10g')
|
||||
|
||||
@require_backend("flat_xor_hd_3")
|
||||
def test_xor_code(self):
|
||||
for (ec_type, k, m, hd) in self.xor_types:
|
||||
print("\nRunning tests for flat_xor_hd k=%d, m=%d, hd=%d" %
|
||||
(k, m, hd))
|
||||
|
||||
for size_str in self.sizes:
|
||||
avg_time = self.time_encode(k, m, ec_type.value, hd,
|
||||
size_str,
|
||||
self.iterations)
|
||||
print("Encode (%s): %s" %
|
||||
(size_str, self.get_throughput(avg_time, size_str)))
|
||||
|
||||
for size_str in self.sizes:
|
||||
success, avg_time = self.time_decode(k, m, ec_type.value, hd,
|
||||
size_str,
|
||||
self.iterations)
|
||||
self.assertTrue(success)
|
||||
print("Decode (%s): %s" %
|
||||
(size_str, self.get_throughput(avg_time, size_str)))
|
||||
|
||||
for size_str in self.sizes:
|
||||
success, avg_time = self.time_reconstruct(
|
||||
k, m, ec_type.value, hd, size_str, self.iterations)
|
||||
self.assertTrue(success)
|
||||
print("Reconstruct (%s): %s" %
|
||||
(size_str, self.get_throughput(avg_time, size_str)))
|
||||
|
||||
@require_backend("shss")
|
||||
def test_shss(self):
|
||||
for (ec_type, k, m) in self.shss:
|
||||
print(("\nRunning tests for %s k=%d, m=%d" % (ec_type, k, m)))
|
||||
|
||||
success = self._test_get_required_fragments(k, m, ec_type)
|
||||
self.assertTrue(success)
|
||||
|
||||
for size_str in self.sizes:
|
||||
avg_time = self.time_encode(k, m, ec_type.value, 0,
|
||||
size_str,
|
||||
self.iterations)
|
||||
print("Encode (%s): %s" %
|
||||
(size_str, self.get_throughput(avg_time, size_str)))
|
||||
|
||||
for size_str in self.sizes:
|
||||
success, avg_time = self.time_decode(k, m, ec_type.value, 0,
|
||||
size_str,
|
||||
self.iterations)
|
||||
self.assertTrue(success)
|
||||
print("Decode (%s): %s" %
|
||||
(size_str, self.get_throughput(avg_time, size_str)))
|
||||
|
||||
for size_str in self.sizes:
|
||||
success, avg_time = self.time_reconstruct(
|
||||
k, m, ec_type.value, 0, size_str, self.iterations)
|
||||
self.assertTrue(success)
|
||||
print("Reconstruct (%s): %s" %
|
||||
(size_str, self.get_throughput(avg_time, size_str)))
|
||||
|
||||
@require_backend("libphazr")
|
||||
def test_libphazr(self):
|
||||
for (ec_type, k, m) in self.libphazr:
|
||||
print(("\nRunning tests for %s k=%d, m=%d" % (ec_type, k, m)))
|
||||
|
||||
success = self._test_get_required_fragments(k, m, ec_type)
|
||||
self.assertTrue(success)
|
||||
|
||||
for size_str in self.sizes:
|
||||
avg_time = self.time_encode(k, m, ec_type.value, 0,
|
||||
size_str,
|
||||
self.iterations)
|
||||
print("Encode (%s): %s" %
|
||||
(size_str, self.get_throughput(avg_time, size_str)))
|
||||
|
||||
for size_str in self.sizes:
|
||||
success, avg_time = self.time_decode(k, m, ec_type.value, 0,
|
||||
size_str,
|
||||
self.iterations)
|
||||
self.assertTrue(success)
|
||||
print("Decode (%s): %s" %
|
||||
(size_str, self.get_throughput(avg_time, size_str)))
|
||||
|
||||
for size_str in self.sizes:
|
||||
success, avg_time = self.time_reconstruct(
|
||||
k, m, ec_type.value, 0, size_str, self.iterations)
|
||||
self.assertTrue(success)
|
||||
print("Reconstruct (%s): %s" %
|
||||
(size_str, self.get_throughput(avg_time, size_str)))
|
||||
|
||||
def _test_get_required_fragments(self, num_data, num_parity, ec_type):
|
||||
"""
|
||||
:return boolean, True if all tests passed
|
||||
"""
|
||||
handle = pyeclib_c.init(num_data, num_parity, ec_type.value)
|
||||
success = True
|
||||
|
||||
#
|
||||
# MDS codes need any k fragments
|
||||
#
|
||||
if ec_type in ["jerasure_rs_vand", "jerasure_rs_cauchy",
|
||||
"liberasurecode_rs_vand"]:
|
||||
expected_fragments = [i for i in range(num_data + num_parity)]
|
||||
missing_fragments = []
|
||||
|
||||
#
|
||||
# Remove between 1 and num_parity
|
||||
#
|
||||
for i in range(random.randint(0, num_parity - 1)):
|
||||
missing_fragment = random.sample(expected_fragments, 1)[0]
|
||||
missing_fragments.append(missing_fragment)
|
||||
expected_fragments.remove(missing_fragment)
|
||||
|
||||
expected_fragments = expected_fragments[:num_data]
|
||||
required_fragments = pyeclib_c.get_required_fragments(
|
||||
handle,
|
||||
missing_fragments, [])
|
||||
|
||||
if expected_fragments != required_fragments:
|
||||
success = False
|
||||
print("Unexpected required fragments list "
|
||||
"(exp != req): %s != %s" % (
|
||||
expected_fragments, required_fragments))
|
||||
|
||||
return success
|
||||
|
||||
def test_codes(self):
|
||||
for ec_type in self.iter_available_types(self.rs_types):
|
||||
for i in range(len(self.num_datas)):
|
||||
success = self._test_get_required_fragments(
|
||||
self.num_datas[i], self.num_parities[i], ec_type)
|
||||
self.assertTrue(success)
|
||||
|
||||
for i in range(len(self.num_datas)):
|
||||
for size_str in self.sizes:
|
||||
avg_time = self.time_encode(
|
||||
self.num_datas[i], self.num_parities[i], ec_type.value,
|
||||
self.num_parities[i] + 1, size_str, self.iterations)
|
||||
|
||||
print("Encode (%s): %s" %
|
||||
(size_str, self.get_throughput(avg_time, size_str)))
|
||||
|
||||
for i in range(len(self.num_datas)):
|
||||
for size_str in self.sizes:
|
||||
success, avg_time = self.time_decode(
|
||||
self.num_datas[i], self.num_parities[i], ec_type.value,
|
||||
self.num_parities[i] + 1, size_str, self.iterations)
|
||||
|
||||
self.assertTrue(success)
|
||||
print("Decode (%s): %s" %
|
||||
(size_str, self.get_throughput(avg_time, size_str)))
|
||||
|
||||
for i in range(len(self.num_datas)):
|
||||
for size_str in self.sizes:
|
||||
success, avg_time = self.time_range_decode(
|
||||
self.num_datas[i], self.num_parities[i], ec_type.value,
|
||||
self.num_parities[i] + 1, size_str, self.iterations)
|
||||
|
||||
self.assertTrue(success)
|
||||
print("Range Decode (%s): %s" %
|
||||
(size_str, self.get_throughput(avg_time, size_str)))
|
||||
|
||||
for i in range(len(self.num_datas)):
|
||||
for size_str in self.sizes:
|
||||
success, avg_time = self.time_reconstruct(
|
||||
self.num_datas[i], self.num_parities[i], ec_type.value,
|
||||
self.num_parities[i] + 1, size_str, self.iterations)
|
||||
self.assertTrue(success)
|
||||
print("Reconstruct (%s): %s" %
|
||||
(size_str, self.get_throughput(avg_time, size_str)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,266 +0,0 @@
|
||||
# Copyright (c) 2013, Kevin Greenan (kmgreen2@gmail.com)
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution. THIS SOFTWARE IS
|
||||
# PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
|
||||
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
# NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
# PyEClib Companion tool
|
||||
# Goal: When defining an EC pool, help cluster admin make an informed choice
|
||||
# between available EC implementations. Generate sample swift.conf + swift-
|
||||
# ring-builder hints.
|
||||
#
|
||||
# Suggested features:
|
||||
#
|
||||
# - List the "EC types" supported - EC algorithms
|
||||
# - List implementations of each EC type available on the platform
|
||||
# (dumb-software-only, software with SIMD acceleration,
|
||||
# specialized hardware, etc).
|
||||
# - Benchmark each algorithm with possible implementation and display
|
||||
# performance numbers.
|
||||
# - Generate sample EC policy entry (for inclusion in swift.conf) for the
|
||||
# best performing algorithm + implementation. (And optionally provide swift-
|
||||
# ring-builder hints).
|
||||
#
|
||||
# Suggested EC policy entry format:
|
||||
#
|
||||
# ======== swift.conf ============
|
||||
# [storage-policy:10]
|
||||
# type = erasure_coding
|
||||
# name = ec_jerasure_rs_cauchy_12_2
|
||||
# ec_type = jerasure_rs_cauchy
|
||||
# ec_k = 12
|
||||
# ec_m = 2
|
||||
# ============================
|
||||
#
|
||||
# (ec_type values are one of those available within PyEClib)
|
||||
|
||||
#
|
||||
# User input: Num data, num parity, average file size
|
||||
# Output: Ordered list of options and their corresponding conf entries
|
||||
# (limit 10)
|
||||
#
|
||||
|
||||
from pyeclib.ec_iface import ECDriver
|
||||
import random
|
||||
import string
|
||||
import sys
|
||||
import argparse
|
||||
import time
|
||||
import math
|
||||
|
||||
|
||||
class Timer:
|
||||
|
||||
def __init__(self):
|
||||
self.start_time = 0
|
||||
self.end_time = 0
|
||||
|
||||
def reset(self):
|
||||
self.start_time = 0
|
||||
self.end_time = 0
|
||||
|
||||
def start(self):
|
||||
self.start_time = time.time()
|
||||
|
||||
def stop(self):
|
||||
self.end_time = time.time()
|
||||
|
||||
def curr_delta(self):
|
||||
return self.end_time - self.start_time
|
||||
|
||||
def stop_and_return(self):
|
||||
self.end_time = time.time()
|
||||
return self.curr_delta()
|
||||
|
||||
|
||||
def nCr(n, r):
|
||||
f = math.factorial
|
||||
return f(n) / f(r) / f(n - r)
|
||||
|
||||
|
||||
class ECScheme:
|
||||
|
||||
def __init__(self, k, m, ec_type):
|
||||
self.k = k
|
||||
self.m = m
|
||||
self.ec_type = ec_type
|
||||
|
||||
def __str__(self):
|
||||
return "k=%d m=%d ec_type=%s" % (self.k, self.m, self.ec_type)
|
||||
|
||||
valid_flat_xor_hd_3 = [(6, 6), (7, 6), (8, 6), (9, 6),
|
||||
(10, 6), (11, 6), (12, 6), (13, 6),
|
||||
(14, 6), (15, 6)]
|
||||
|
||||
valid_flat_xor_hd_4 = [(6, 6), (7, 6), (8, 6), (9, 6),
|
||||
(10, 6), (11, 6), (12, 6), (13, 6),
|
||||
(14, 6), (15, 6), (16, 6), (17, 6),
|
||||
(18, 6), (19, 6), (20, 6)]
|
||||
|
||||
|
||||
def get_viable_schemes(
|
||||
max_num_frags, minimum_rate, avg_stripe_size, fault_tolerance):
|
||||
|
||||
list_of_schemes = []
|
||||
|
||||
#
|
||||
# Get min_k from (minimum_rate * max_num_frags)
|
||||
#
|
||||
min_k = int(math.ceil(minimum_rate * max_num_frags))
|
||||
|
||||
#
|
||||
# Get min_m from the fault tolerance
|
||||
#
|
||||
min_m = fault_tolerance
|
||||
|
||||
#
|
||||
# Is not information theoretically possible
|
||||
#
|
||||
if (min_k + min_m) > max_num_frags:
|
||||
return list_of_schemes
|
||||
|
||||
#
|
||||
# Iterate over EC(k, max_num_frags-k) k \in [min_k, n-min_m]
|
||||
#
|
||||
for k in range(min_k, max_num_frags - min_m + 1):
|
||||
list_of_schemes.append(
|
||||
ECScheme(k, max_num_frags - k, "jerasure_rs_vand"))
|
||||
|
||||
list_of_schemes.append(
|
||||
ECScheme(k, max_num_frags - k, "jerasure_rs_cauchy"))
|
||||
|
||||
#
|
||||
# The XOR codes are a little tricker
|
||||
# (only check if fault_tolerance = 2 or 3)
|
||||
#
|
||||
# Constraint for 2: k <= (m choose 2)
|
||||
# Constraint for 3: k <= (m choose 3)
|
||||
#
|
||||
# The '3' flat_xor_hd_3 (and '4' in flat_xor_hd_4) refers to the Hamming
|
||||
# distance, which means the code guarantees the reconstruction of any
|
||||
# 2 lost fragments (or 3 in the case of flat_xor_hd_4).
|
||||
#
|
||||
# So, only consider the XOR code if the fault_tolerance matches and
|
||||
# the additional constraint is met
|
||||
#
|
||||
if fault_tolerance == 2:
|
||||
max_k = nCr(max_num_frags - k, 2)
|
||||
if k <= max_k and (k, max_num_frags - k) in valid_flat_xor_hd_3:
|
||||
list_of_schemes.append(
|
||||
ECScheme(k, max_num_frags - k, "flat_xor_hd_3"))
|
||||
|
||||
if fault_tolerance == 3:
|
||||
max_k = nCr(max_num_frags - k, 3)
|
||||
if k <= max_k and (k, max_num_frags - k) in valid_flat_xor_hd_4:
|
||||
list_of_schemes.append(
|
||||
ECScheme(k, max_num_frags - k, "flat_xor_hd_4"))
|
||||
|
||||
return list_of_schemes
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='PyECLib tool to evaluate viable EC options, benchmark them '
|
||||
'and report results with the appropriate conf entries.')
|
||||
parser.add_argument(
|
||||
'-n',
|
||||
type=int,
|
||||
help='max number of fragments',
|
||||
required=True)
|
||||
parser.add_argument('-f', type=int, help='fault tolerance', required=True)
|
||||
parser.add_argument(
|
||||
'-r',
|
||||
type=float,
|
||||
help='minimum coding rate (num_data / num_data+num_parity)',
|
||||
required=True)
|
||||
parser.add_argument('-s', type=int, help='average stripe size', required=True)
|
||||
parser.add_argument(
|
||||
'-l',
|
||||
type=int,
|
||||
help='set limit on number of entries returned (default = 10)',
|
||||
default=10,
|
||||
)
|
||||
|
||||
args = parser.parse_args(sys.argv[1:])
|
||||
|
||||
MB = 1024 * 1024
|
||||
|
||||
# Generate a buffer of size 's'
|
||||
if args.s > 10 * MB:
|
||||
print("s must be smaller than 10 MB.")
|
||||
sys.exit(1)
|
||||
|
||||
# Instantiate the timer
|
||||
timer = Timer()
|
||||
|
||||
return_limit = args.l
|
||||
|
||||
schemes = get_viable_schemes(args.n, args.r, args.s, args.f)
|
||||
|
||||
# Results will be List[(ec_type, throughput)]
|
||||
results = []
|
||||
|
||||
# Num iterations
|
||||
num_iterations = 10
|
||||
|
||||
for scheme in schemes:
|
||||
print(scheme)
|
||||
|
||||
# Generate a new string for each test
|
||||
file_str = ''.join(
|
||||
random.choice(
|
||||
string.ascii_uppercase + string.digits) for x in range(args.s))
|
||||
|
||||
try:
|
||||
ec_driver = ECDriver(k=scheme.k, m=scheme.m, ec_type=scheme.ec_type)
|
||||
except Exception as e:
|
||||
print("Scheme %s is not defined (%s)." % (scheme, e))
|
||||
continue
|
||||
|
||||
timer.start()
|
||||
|
||||
for i in range(num_iterations):
|
||||
ec_driver.encode(file_str)
|
||||
|
||||
duration = timer.stop_and_return()
|
||||
|
||||
results.append((scheme, duration))
|
||||
|
||||
timer.reset()
|
||||
|
||||
print(results)
|
||||
results.sort(lambda x, y: (int)((1000 * x[1]) - (1000 * y[1])))
|
||||
|
||||
for i in range(len(results)):
|
||||
if i > return_limit:
|
||||
break
|
||||
|
||||
print("\n\nPerf Rank #%d:" % i)
|
||||
print(" ======== To Use this Policy, Copy and Paste Text (not including "
|
||||
"this header and footer) to Swift Conf ========")
|
||||
print(" type = erasure_coding")
|
||||
print(" name = %s_%d_%d" % (results[i][0].ec_type,
|
||||
results[i][0].k, results[i][0].m))
|
||||
print(" ec_type = %s" % results[i][0].ec_type)
|
||||
print(" ec_k = %s" % results[i][0].k)
|
||||
print(" ec_m = %s" % results[i][0].m)
|
||||
print(" ================================================================"
|
||||
"==============================================")
|
||||
results[i]
|
||||
@@ -1,56 +0,0 @@
|
||||
# Copyright (c) 2013, Kevin Greenan (kmgreen2@gmail.com)
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution. THIS SOFTWARE IS
|
||||
# PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
|
||||
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
# NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from pyeclib.ec_iface import ECDriver
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='Decoder for PyECLib.')
|
||||
parser.add_argument('k', type=int, help='number of data elements')
|
||||
parser.add_argument('m', type=int, help='number of parity elements')
|
||||
parser.add_argument('ec_type', help='EC algorithm used')
|
||||
parser.add_argument('fragments', metavar='fragment', nargs='+',
|
||||
help='fragments to decode')
|
||||
parser.add_argument('filename', help='output file')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
print("k = %d, m = %d" % (args.k, args.m))
|
||||
print("ec_type = %s" % args.ec_type)
|
||||
print("fragments = %s" % args.fragments)
|
||||
print("filename = %s" % args.filename)
|
||||
|
||||
ec_driver = ECDriver(k=args.k, m=args.m, ec_type=args.ec_type)
|
||||
|
||||
fragment_list = []
|
||||
|
||||
# read fragments
|
||||
for fragment in args.fragments:
|
||||
with open(("%s" % fragment), "rb") as fp:
|
||||
fragment_list.append(fp.read())
|
||||
|
||||
# decode
|
||||
decoded_file = ec_driver.decode(fragment_list)
|
||||
|
||||
# write
|
||||
with open("%s.decoded" % args.filename, "wb") as fp:
|
||||
fp.write(decoded_file)
|
||||
@@ -1,55 +0,0 @@
|
||||
# Copyright (c) 2013, Kevin Greenan (kmgreen2@gmail.com)
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution. THIS SOFTWARE IS
|
||||
# PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
|
||||
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
# NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from pyeclib.ec_iface import ECDriver
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='Encoder for PyECLib.')
|
||||
parser.add_argument('k', type=int, help='number of data elements')
|
||||
parser.add_argument('m', type=int, help='number of parity elements')
|
||||
parser.add_argument('ec_type', help='EC algorithm used')
|
||||
parser.add_argument('file_dir', help='directory with the file')
|
||||
parser.add_argument('filename', help='file to encode')
|
||||
parser.add_argument('fragment_dir', help='directory to drop encoded fragments')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
print("k = %d, m = %d" % (args.k, args.m))
|
||||
print("ec_type = %s" % args.ec_type)
|
||||
print("filename = %s" % args.filename)
|
||||
|
||||
ec_driver = ECDriver(k=args.k, m=args.m, ec_type=args.ec_type)
|
||||
|
||||
# read
|
||||
with open(("%s/%s" % (args.file_dir, args.filename)), "rb") as fp:
|
||||
whole_file_str = fp.read()
|
||||
|
||||
# encode
|
||||
fragments = ec_driver.encode(whole_file_str)
|
||||
|
||||
# store
|
||||
i = 0
|
||||
for fragment in fragments:
|
||||
with open("%s/%s.%d" % (args.fragment_dir, args.filename, i), "wb") as fp:
|
||||
fp.write(fragment)
|
||||
i += 1
|
||||
@@ -1,47 +0,0 @@
|
||||
# Copyright (c) 2013, Kevin Greenan (kmgreen2@gmail.com)
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution. THIS SOFTWARE IS
|
||||
# PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
|
||||
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
# NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import pyeclib
|
||||
from pyeclib.ec_iface import ECDriver
|
||||
import random
|
||||
import string
|
||||
import sys
|
||||
import os
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='PyECLib tool to determine fragment indexes needed to '
|
||||
'recover missing fragments.')
|
||||
parser.add_argument('k', type=int, help='number of data elements')
|
||||
parser.add_argument('m', type=int, help='number of parity elements')
|
||||
parser.add_argument('ec_type', help='EC algorithm used')
|
||||
parser.add_argument('missing_fragments', type=int, metavar='missing_fragment',
|
||||
nargs='+', help='missing_fragments')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
ec_driver = ECDriver(k=args.k, m=args.m, ec_type=args.ec_type)
|
||||
|
||||
fragments_needed = ec_driver.fragments_needed(args.missing_fragments)
|
||||
|
||||
print(fragments_needed)
|
||||
20
tox.ini
20
tox.ini
@@ -1,20 +0,0 @@
|
||||
[tox]
|
||||
envlist = py27,py35,pep8
|
||||
|
||||
[testenv]
|
||||
deps =
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
commands=
|
||||
nosetests --no-path-adjustment -v test/
|
||||
|
||||
[testenv:pep8]
|
||||
deps=
|
||||
pep8
|
||||
commands=
|
||||
pep8 pyeclib/ setup.py test/
|
||||
|
||||
[testenv:venv]
|
||||
commands = {posargs}
|
||||
|
||||
[testenv:docs]
|
||||
commands = python setup.py build_sphinx
|
||||
Reference in New Issue
Block a user