class ECStorageMethod(StorageMethod): def __init__(self, name, ec_segment_size, ec_type, ec_nb_data, ec_nb_parity): super(ECStorageMethod, self).__init__(name=name, ec=True) try: self._ec_nb_data = int(ec_nb_data) except (TypeError, ValueError): raise exc.InvalidStorageMethod('Invalid %r ec_nb_data' % ec_nb_data) try: self._ec_nb_parity = int(ec_nb_parity) except (TypeError, ValueError): raise exc.InvalidStorageMethod('Invalid %r ec_nb_parity' % ec_nb_parity) self._ec_segment_size = ec_segment_size self._ec_type = ec_type self.driver = ECDriver(k=ec_nb_data, m=ec_nb_parity, ec_type=ec_type_to_pyeclib_type[ec_type]) self._ec_quorum_size = \ self._ec_nb_data + self.driver.min_parity_fragments_needed() @property def quorum(self): return self._ec_quorum_size @classmethod def build(cls, params): ec_nb_data = params.pop('k') ec_nb_parity = params.pop('m') ec_type = params.pop('algo') return cls('ec', ec_segment_size=EC_SEGMENT_SIZE, ec_type=ec_type, ec_nb_data=ec_nb_data, ec_nb_parity=ec_nb_parity) @property def ec_type(self): return self._ec_type @property def ec_nb_data(self): return self._ec_nb_data @property def ec_nb_parity(self): return self._ec_nb_parity @property def ec_segment_size(self): return self._ec_segment_size @property def ec_fragment_size(self): return self.driver.get_segment_info( self.ec_segment_size, self.ec_segment_size)['fragment_size']
def test_ec_fragment_size_cached(self): policy = ECStoragePolicy( 0, 'ec2-1', ec_type=DEFAULT_TEST_EC_TYPE, ec_ndata=2, ec_nparity=1, object_ring=FakeRing(replicas=3), ec_segment_size=DEFAULT_EC_OBJECT_SEGMENT_SIZE, is_default=True) ec_driver = ECDriver(ec_type=DEFAULT_TEST_EC_TYPE, k=2, m=1) expected_fragment_size = ec_driver.get_segment_info( DEFAULT_EC_OBJECT_SEGMENT_SIZE, DEFAULT_EC_OBJECT_SEGMENT_SIZE)['fragment_size'] with mock.patch.object( policy.pyeclib_driver, 'get_segment_info') as fake: fake.return_value = { 'fragment_size': expected_fragment_size} for x in range(10): self.assertEqual(expected_fragment_size, policy.fragment_size) # pyeclib_driver.get_segment_info is called only once self.assertEqual(1, fake.call_count)
class ECStoragePolicy(BaseStoragePolicy): """ Represents a storage policy of type 'erasure_coding'. Not meant to be instantiated directly; use :func:`~swift.common.storage_policy.reload_storage_policies` to load POLICIES from ``swift.conf``. """ def __init__(self, idx, name='', aliases='', is_default=False, is_deprecated=False, object_ring=None, ec_segment_size=DEFAULT_EC_OBJECT_SEGMENT_SIZE, ec_type=None, ec_ndata=None, ec_nparity=None): super(ECStoragePolicy, self).__init__( idx=idx, name=name, aliases=aliases, is_default=is_default, is_deprecated=is_deprecated, object_ring=object_ring) # Validate erasure_coding policy specific members # ec_type is one of the EC implementations supported by PyEClib if ec_type is None: raise PolicyError('Missing ec_type') if ec_type not in VALID_EC_TYPES: raise PolicyError('Wrong ec_type %s for policy %s, should be one' ' of "%s"' % (ec_type, self.name, ', '.join(VALID_EC_TYPES))) self._ec_type = ec_type # Define _ec_ndata as the number of EC data fragments # Accessible as the property "ec_ndata" try: value = int(ec_ndata) if value <= 0: raise ValueError self._ec_ndata = value except (TypeError, ValueError): raise PolicyError('Invalid ec_num_data_fragments %r' % ec_ndata, index=self.idx) # Define _ec_nparity as the number of EC parity fragments # Accessible as the property "ec_nparity" try: value = int(ec_nparity) if value <= 0: raise ValueError self._ec_nparity = value except (TypeError, ValueError): raise PolicyError('Invalid ec_num_parity_fragments %r' % ec_nparity, index=self.idx) # Define _ec_segment_size as the encode segment unit size # Accessible as the property "ec_segment_size" try: value = int(ec_segment_size) if value <= 0: raise ValueError self._ec_segment_size = value except (TypeError, ValueError): raise PolicyError('Invalid ec_object_segment_size %r' % ec_segment_size, index=self.idx) # Initialize PyECLib EC backend try: self.pyeclib_driver = \ ECDriver(k=self._ec_ndata, m=self._ec_nparity, ec_type=self._ec_type) except ECDriverError as e: raise PolicyError("Error creating EC policy (%s)" % e, index=self.idx) # quorum size in the EC case depends on the choice of EC scheme. self._ec_quorum_size = \ self._ec_ndata + self.pyeclib_driver.min_parity_fragments_needed() @property def ec_type(self): return self._ec_type @property def ec_ndata(self): return self._ec_ndata @property def ec_nparity(self): return self._ec_nparity @property def ec_segment_size(self): return self._ec_segment_size @property def fragment_size(self): """ Maximum length of a fragment, including header. NB: a fragment archive is a sequence of 0 or more max-length fragments followed by one possibly-shorter fragment. """ # Technically pyeclib's get_segment_info signature calls for # (data_len, segment_size) but on a ranged GET we don't know the # ec-content-length header before we need to compute where in the # object we should request to align with the fragment size. So we # tell pyeclib a lie - from it's perspective, as long as data_len >= # segment_size it'll give us the answer we want. From our # perspective, because we only use this answer to calculate the # *minimum* size we should read from an object body even if data_len < # segment_size we'll still only read *the whole one and only last # fragment* and pass than into pyeclib who will know what to do with # it just as it always does when the last fragment is < fragment_size. return self.pyeclib_driver.get_segment_info( self.ec_segment_size, self.ec_segment_size)['fragment_size'] @property def ec_scheme_description(self): """ This short hand form of the important parts of the ec schema is stored in Object System Metadata on the EC Fragment Archives for debugging. """ return "%s %d+%d" % (self._ec_type, self._ec_ndata, self._ec_nparity) def __repr__(self): return ("%s, EC config(ec_type=%s, ec_segment_size=%d, " "ec_ndata=%d, ec_nparity=%d)") % \ (super(ECStoragePolicy, self).__repr__(), self.ec_type, self.ec_segment_size, self.ec_ndata, self.ec_nparity) @classmethod def _config_options_map(cls): options = super(ECStoragePolicy, cls)._config_options_map() options.update({ 'ec_type': 'ec_type', 'ec_object_segment_size': 'ec_segment_size', 'ec_num_data_fragments': 'ec_ndata', 'ec_num_parity_fragments': 'ec_nparity', }) return options def get_info(self, config=False): info = super(ECStoragePolicy, self).get_info(config=config) if not config: info.pop('ec_object_segment_size') info.pop('ec_num_data_fragments') info.pop('ec_num_parity_fragments') info.pop('ec_type') return info def _validate_ring(self): """ EC specific validation Replica count check - we need _at_least_ (#data + #parity) replicas configured. Also if the replica count is larger than exactly that number there's a non-zero risk of error for code that is considering the number of nodes in the primary list from the ring. """ if not self.object_ring: raise PolicyError('Ring is not loaded') nodes_configured = self.object_ring.replica_count if nodes_configured != (self.ec_ndata + self.ec_nparity): raise RingValidationError( 'EC ring for policy %s needs to be configured with ' 'exactly %d nodes. Got %d.' % ( self.name, self.ec_ndata + self.ec_nparity, nodes_configured)) @property def quorum(self): """ Number of successful backend requests needed for the proxy to consider the client request successful. The quorum size for EC policies defines the minimum number of data + parity elements required to be able to guarantee the desired fault tolerance, which is the number of data elements supplemented by the minimum number of parity elements required by the chosen erasure coding scheme. For example, for Reed-Solomon, the minimum number parity elements required is 1, and thus the quorum_size requirement is ec_ndata + 1. Given the number of parity elements required is not the same for every erasure coding scheme, consult PyECLib for min_parity_fragments_needed() """ return self._ec_quorum_size
class ECStoragePolicy(BaseStoragePolicy): """ Represents a storage policy of type 'erasure_coding'. Not meant to be instantiated directly; use :func:`~swift.common.storage_policy.reload_storage_policies` to load POLICIES from ``swift.conf``. """ def __init__(self, idx, name='', aliases='', is_default=False, is_deprecated=False, object_ring=None, ec_segment_size=DEFAULT_EC_OBJECT_SEGMENT_SIZE, ec_type=None, ec_ndata=None, ec_nparity=None, ec_duplication_factor=1): super(ECStoragePolicy, self).__init__(idx=idx, name=name, aliases=aliases, is_default=is_default, is_deprecated=is_deprecated, object_ring=object_ring) # Validate erasure_coding policy specific members # ec_type is one of the EC implementations supported by PyEClib if ec_type is None: raise PolicyError('Missing ec_type') if ec_type not in VALID_EC_TYPES: raise PolicyError('Wrong ec_type %s for policy %s, should be one' ' of "%s"' % (ec_type, self.name, ', '.join(VALID_EC_TYPES))) self._ec_type = ec_type # Define _ec_ndata as the number of EC data fragments # Accessible as the property "ec_ndata" try: value = int(ec_ndata) if value <= 0: raise ValueError self._ec_ndata = value except (TypeError, ValueError): raise PolicyError('Invalid ec_num_data_fragments %r' % ec_ndata, index=self.idx) # Define _ec_nparity as the number of EC parity fragments # Accessible as the property "ec_nparity" try: value = int(ec_nparity) if value <= 0: raise ValueError self._ec_nparity = value except (TypeError, ValueError): raise PolicyError('Invalid ec_num_parity_fragments %r' % ec_nparity, index=self.idx) # Define _ec_segment_size as the encode segment unit size # Accessible as the property "ec_segment_size" try: value = int(ec_segment_size) if value <= 0: raise ValueError self._ec_segment_size = value except (TypeError, ValueError): raise PolicyError('Invalid ec_object_segment_size %r' % ec_segment_size, index=self.idx) if self._ec_type == 'isa_l_rs_vand' and self._ec_nparity >= 5: logger = logging.getLogger("swift.common.storage_policy") if not logger.handlers: # If nothing else, log to stderr logger.addHandler(logging.StreamHandler(sys.__stderr__)) logger.warning( 'Storage policy %s uses an EC configuration known to harm ' 'data durability. Any data in this policy should be migrated. ' 'See https://bugs.launchpad.net/swift/+bug/1639691 for ' 'more information.' % self.name) if not is_deprecated: raise PolicyError( 'Storage policy %s uses an EC configuration known to harm ' 'data durability. This policy MUST be deprecated.' % self.name) # Initialize PyECLib EC backend try: self.pyeclib_driver = \ ECDriver(k=self._ec_ndata, m=self._ec_nparity, ec_type=self._ec_type) except ECDriverError as e: raise PolicyError("Error creating EC policy (%s)" % e, index=self.idx) # quorum size in the EC case depends on the choice of EC scheme. self._ec_quorum_size = \ self._ec_ndata + self.pyeclib_driver.min_parity_fragments_needed() self._fragment_size = None self._ec_duplication_factor = \ config_positive_int_value(ec_duplication_factor) @property def ec_type(self): return self._ec_type @property def ec_ndata(self): return self._ec_ndata @property def ec_nparity(self): return self._ec_nparity @property def ec_n_unique_fragments(self): return self._ec_ndata + self._ec_nparity @property def ec_segment_size(self): return self._ec_segment_size @property def fragment_size(self): """ Maximum length of a fragment, including header. NB: a fragment archive is a sequence of 0 or more max-length fragments followed by one possibly-shorter fragment. """ # Technically pyeclib's get_segment_info signature calls for # (data_len, segment_size) but on a ranged GET we don't know the # ec-content-length header before we need to compute where in the # object we should request to align with the fragment size. So we # tell pyeclib a lie - from it's perspective, as long as data_len >= # segment_size it'll give us the answer we want. From our # perspective, because we only use this answer to calculate the # *minimum* size we should read from an object body even if data_len < # segment_size we'll still only read *the whole one and only last # fragment* and pass than into pyeclib who will know what to do with # it just as it always does when the last fragment is < fragment_size. if self._fragment_size is None: self._fragment_size = self.pyeclib_driver.get_segment_info( self.ec_segment_size, self.ec_segment_size)['fragment_size'] return self._fragment_size @property def ec_scheme_description(self): """ This short hand form of the important parts of the ec schema is stored in Object System Metadata on the EC Fragment Archives for debugging. """ return "%s %d+%d" % (self._ec_type, self._ec_ndata, self._ec_nparity) @property def ec_duplication_factor(self): return self._ec_duplication_factor def __repr__(self): extra_info = '' if self.ec_duplication_factor != 1: extra_info = ', ec_duplication_factor=%d' % \ self.ec_duplication_factor return ("%s, EC config(ec_type=%s, ec_segment_size=%d, " "ec_ndata=%d, ec_nparity=%d%s)") % \ (super(ECStoragePolicy, self).__repr__(), self.ec_type, self.ec_segment_size, self.ec_ndata, self.ec_nparity, extra_info) @classmethod def _config_options_map(cls): options = super(ECStoragePolicy, cls)._config_options_map() options.update({ 'ec_type': 'ec_type', 'ec_object_segment_size': 'ec_segment_size', 'ec_num_data_fragments': 'ec_ndata', 'ec_num_parity_fragments': 'ec_nparity', 'ec_duplication_factor': 'ec_duplication_factor', }) return options def get_info(self, config=False): info = super(ECStoragePolicy, self).get_info(config=config) if not config: info.pop('ec_object_segment_size') info.pop('ec_num_data_fragments') info.pop('ec_num_parity_fragments') info.pop('ec_type') info.pop('ec_duplication_factor') return info @property def quorum(self): """ Number of successful backend requests needed for the proxy to consider the client PUT request successful. The quorum size for EC policies defines the minimum number of data + parity elements required to be able to guarantee the desired fault tolerance, which is the number of data elements supplemented by the minimum number of parity elements required by the chosen erasure coding scheme. For example, for Reed-Solomon, the minimum number parity elements required is 1, and thus the quorum_size requirement is ec_ndata + 1. Given the number of parity elements required is not the same for every erasure coding scheme, consult PyECLib for min_parity_fragments_needed() """ return self._ec_quorum_size * self.ec_duplication_factor def load_ring(self, swift_dir): """ Load the ring for this policy immediately. :param swift_dir: path to rings """ if self.object_ring: return def validate_ring_data(ring_data): """ EC specific validation Replica count check - we need _at_least_ (#data + #parity) replicas configured. Also if the replica count is larger than exactly that number there's a non-zero risk of error for code that is considering the number of nodes in the primary list from the ring. """ configured_fragment_count = len(ring_data._replica2part2dev_id) required_fragment_count = \ (self.ec_n_unique_fragments) * self.ec_duplication_factor if configured_fragment_count != required_fragment_count: raise RingLoadError( 'EC ring for policy %s needs to be configured with ' 'exactly %d replicas. Got %d.' % (self.name, required_fragment_count, configured_fragment_count)) self.object_ring = Ring(swift_dir, ring_name=self.ring_name, validation_hook=validate_ring_data) def get_backend_index(self, node_index): """ Backend index for PyECLib :param node_index: integer of node index :return: integer of actual fragment index. if param is not an integer, return None instead """ try: node_index = int(node_index) except ValueError: return None return node_index % self.ec_n_unique_fragments
class ECStoragePolicy(BaseStoragePolicy): """ Represents a storage policy of type 'erasure_coding'. Not meant to be instantiated directly; use :func:`~swift.common.storage_policy.reload_storage_policies` to load POLICIES from ``swift.conf``. """ def __init__(self, idx, name='', is_default=False, is_deprecated=False, object_ring=None, ec_segment_size=DEFAULT_EC_OBJECT_SEGMENT_SIZE, ec_type=None, ec_ndata=None, ec_nparity=None): super(ECStoragePolicy, self).__init__(idx, name, is_default, is_deprecated, object_ring) # Validate erasure_coding policy specific members # ec_type is one of the EC implementations supported by PyEClib if ec_type is None: raise PolicyError('Missing ec_type') if ec_type not in VALID_EC_TYPES: raise PolicyError('Wrong ec_type %s for policy %s, should be one' ' of "%s"' % (ec_type, self.name, ', '.join(VALID_EC_TYPES))) self._ec_type = ec_type # Define _ec_ndata as the number of EC data fragments # Accessible as the property "ec_ndata" try: value = int(ec_ndata) if value <= 0: raise ValueError self._ec_ndata = value except (TypeError, ValueError): raise PolicyError('Invalid ec_num_data_fragments %r' % ec_ndata, index=self.idx) # Define _ec_nparity as the number of EC parity fragments # Accessible as the property "ec_nparity" try: value = int(ec_nparity) if value <= 0: raise ValueError self._ec_nparity = value except (TypeError, ValueError): raise PolicyError('Invalid ec_num_parity_fragments %r' % ec_nparity, index=self.idx) # Define _ec_segment_size as the encode segment unit size # Accessible as the property "ec_segment_size" try: value = int(ec_segment_size) if value <= 0: raise ValueError self._ec_segment_size = value except (TypeError, ValueError): raise PolicyError('Invalid ec_object_segment_size %r' % ec_segment_size, index=self.idx) # Initialize PyECLib EC backend try: self.pyeclib_driver = \ ECDriver(k=self._ec_ndata, m=self._ec_nparity, ec_type=self._ec_type) except ECDriverError as e: raise PolicyError("Error creating EC policy (%s)" % e, index=self.idx) # quorum size in the EC case depends on the choice of EC scheme. self._ec_quorum_size = \ self._ec_ndata + self.pyeclib_driver.min_parity_fragments_needed() @property def ec_type(self): return self._ec_type @property def ec_ndata(self): return self._ec_ndata @property def ec_nparity(self): return self._ec_nparity @property def ec_segment_size(self): return self._ec_segment_size @property def fragment_size(self): """ Maximum length of a fragment, including header. NB: a fragment archive is a sequence of 0 or more max-length fragments followed by one possibly-shorter fragment. """ # Technically pyeclib's get_segment_info signature calls for # (data_len, segment_size) but on a ranged GET we don't know the # ec-content-length header before we need to compute where in the # object we should request to align with the fragment size. So we # tell pyeclib a lie - from it's perspective, as long as data_len >= # segment_size it'll give us the answer we want. From our # perspective, because we only use this answer to calculate the # *minimum* size we should read from an object body even if data_len < # segment_size we'll still only read *the whole one and only last # fragment* and pass than into pyeclib who will know what to do with # it just as it always does when the last fragment is < fragment_size. return self.pyeclib_driver.get_segment_info( self.ec_segment_size, self.ec_segment_size)['fragment_size'] @property def ec_scheme_description(self): """ This short hand form of the important parts of the ec schema is stored in Object System Metadata on the EC Fragment Archives for debugging. """ return "%s %d+%d" % (self._ec_type, self._ec_ndata, self._ec_nparity) def __repr__(self): return ("%s, EC config(ec_type=%s, ec_segment_size=%d, " "ec_ndata=%d, ec_nparity=%d)") % ( super(ECStoragePolicy, self).__repr__(), self.ec_type, self.ec_segment_size, self.ec_ndata, self.ec_nparity) @classmethod def _config_options_map(cls): options = super(ECStoragePolicy, cls)._config_options_map() options.update({ 'ec_type': 'ec_type', 'ec_object_segment_size': 'ec_segment_size', 'ec_num_data_fragments': 'ec_ndata', 'ec_num_parity_fragments': 'ec_nparity', }) return options def get_info(self, config=False): info = super(ECStoragePolicy, self).get_info(config=config) if not config: info.pop('ec_object_segment_size') info.pop('ec_num_data_fragments') info.pop('ec_num_parity_fragments') info.pop('ec_type') return info def _validate_ring(self): """ EC specific validation Replica count check - we need _at_least_ (#data + #parity) replicas configured. Also if the replica count is larger than exactly that number there's a non-zero risk of error for code that is considering the number of nodes in the primary list from the ring. """ if not self.object_ring: raise PolicyError('Ring is not loaded') nodes_configured = self.object_ring.replica_count if nodes_configured != (self.ec_ndata + self.ec_nparity): raise RingValidationError( 'EC ring for policy %s needs to be configured with ' 'exactly %d nodes. Got %d.' % (self.name, self.ec_ndata + self.ec_nparity, nodes_configured)) @property def quorum(self): """ Number of successful backend requests needed for the proxy to consider the client request successful. The quorum size for EC policies defines the minimum number of data + parity elements required to be able to guarantee the desired fault tolerance, which is the number of data elements supplemented by the minimum number of parity elements required by the chosen erasure coding scheme. For example, for Reed-Solomon, the minimum number parity elements required is 1, and thus the quorum_size requirement is ec_ndata + 1. Given the number of parity elements required is not the same for every erasure coding scheme, consult PyECLib for min_parity_fragments_needed() """ return self._ec_quorum_size
class ECStorageMethod(StorageMethod): def __init__(self, name, ec_segment_size, ec_type, ec_nb_data, ec_nb_parity): super(ECStorageMethod, self).__init__(name=name, ec=True) try: self._ec_nb_data = int(ec_nb_data) except (TypeError, ValueError): raise exceptions.InvalidStorageMethod('Invalid %r ec_nb_data' % ec_nb_data) try: self._ec_nb_parity = int(ec_nb_parity) except (TypeError, ValueError): raise exceptions.InvalidStorageMethod('Invalid %r ec_nb_parity' % ec_nb_parity) self._ec_segment_size = ec_segment_size self._ec_type = ec_type try: self.driver = ECDriver(k=ec_nb_data, m=ec_nb_parity, ec_type=ec_type) except ECDriverError as exc: msg = "'%s' (%s: %s) Check erasure code packages." % ( ec_type, exc.__class__.__name__, exc) reraise(exceptions.InvalidStorageMethod, exceptions.InvalidStorageMethod(msg), sys.exc_info()[2]) self._ec_quorum_size = \ self._ec_nb_data + self.driver.min_parity_fragments_needed() @classmethod def build(cls, params): ec_nb_data = params.pop('k') ec_nb_parity = params.pop('m') ec_type = params.pop('algo') return cls('ec', ec_segment_size=EC_SEGMENT_SIZE, ec_type=ec_type, ec_nb_data=ec_nb_data, ec_nb_parity=ec_nb_parity) @property def quorum(self): return self._ec_quorum_size @property def expected_chunks(self): return self._ec_nb_data + self._ec_nb_parity @property def min_chunks_to_read(self): return self._ec_nb_data @property def ec_type(self): return self._ec_type @property def ec_nb_data(self): return self._ec_nb_data @property def ec_nb_parity(self): return self._ec_nb_parity @property def ec_segment_size(self): return self._ec_segment_size @property def ec_fragment_size(self): return self.driver.get_segment_info( self.ec_segment_size, self.ec_segment_size)['fragment_size']
class ECStoragePolicy(BaseStoragePolicy): """ Represents a storage policy of type 'erasure_coding'. Not meant to be instantiated directly; use :func:`~swift.common.storage_policy.reload_storage_policies` to load POLICIES from ``swift.conf``. """ def __init__(self, idx, name='', aliases='', is_default=False, is_deprecated=False, object_ring=None, diskfile_module='egg:swift#erasure_coding.fs', ec_segment_size=DEFAULT_EC_OBJECT_SEGMENT_SIZE, ec_type=None, ec_ndata=None, ec_nparity=None, ec_duplication_factor=1): super(ECStoragePolicy, self).__init__( idx=idx, name=name, aliases=aliases, is_default=is_default, is_deprecated=is_deprecated, object_ring=object_ring, diskfile_module=diskfile_module) # Validate erasure_coding policy specific members # ec_type is one of the EC implementations supported by PyEClib if ec_type is None: raise PolicyError('Missing ec_type') if ec_type not in VALID_EC_TYPES: raise PolicyError('Wrong ec_type %s for policy %s, should be one' ' of "%s"' % (ec_type, self.name, ', '.join(VALID_EC_TYPES))) self._ec_type = ec_type # Define _ec_ndata as the number of EC data fragments # Accessible as the property "ec_ndata" try: value = int(ec_ndata) if value <= 0: raise ValueError self._ec_ndata = value except (TypeError, ValueError): raise PolicyError('Invalid ec_num_data_fragments %r' % ec_ndata, index=self.idx) # Define _ec_nparity as the number of EC parity fragments # Accessible as the property "ec_nparity" try: value = int(ec_nparity) if value <= 0: raise ValueError self._ec_nparity = value except (TypeError, ValueError): raise PolicyError('Invalid ec_num_parity_fragments %r' % ec_nparity, index=self.idx) # Define _ec_segment_size as the encode segment unit size # Accessible as the property "ec_segment_size" try: value = int(ec_segment_size) if value <= 0: raise ValueError self._ec_segment_size = value except (TypeError, ValueError): raise PolicyError('Invalid ec_object_segment_size %r' % ec_segment_size, index=self.idx) if self._ec_type == 'isa_l_rs_vand' and self._ec_nparity >= 5: logger = logging.getLogger("swift.common.storage_policy") if not logger.handlers: # If nothing else, log to stderr logger.addHandler(logging.StreamHandler(sys.__stderr__)) logger.warning( 'Storage policy %s uses an EC configuration known to harm ' 'data durability. Any data in this policy should be migrated. ' 'See https://bugs.launchpad.net/swift/+bug/1639691 for ' 'more information.' % self.name) if not is_deprecated: raise PolicyError( 'Storage policy %s uses an EC configuration known to harm ' 'data durability. This policy MUST be deprecated.' % self.name) # Initialize PyECLib EC backend try: self.pyeclib_driver = \ ECDriver(k=self._ec_ndata, m=self._ec_nparity, ec_type=self._ec_type) except ECDriverError as e: raise PolicyError("Error creating EC policy (%s)" % e, index=self.idx) # quorum size in the EC case depends on the choice of EC scheme. self._ec_quorum_size = \ self._ec_ndata + self.pyeclib_driver.min_parity_fragments_needed() self._fragment_size = None self._ec_duplication_factor = \ config_positive_int_value(ec_duplication_factor) @property def ec_type(self): return self._ec_type @property def ec_ndata(self): return self._ec_ndata @property def ec_nparity(self): return self._ec_nparity @property def ec_n_unique_fragments(self): return self._ec_ndata + self._ec_nparity @property def ec_segment_size(self): return self._ec_segment_size @property def fragment_size(self): """ Maximum length of a fragment, including header. NB: a fragment archive is a sequence of 0 or more max-length fragments followed by one possibly-shorter fragment. """ # Technically pyeclib's get_segment_info signature calls for # (data_len, segment_size) but on a ranged GET we don't know the # ec-content-length header before we need to compute where in the # object we should request to align with the fragment size. So we # tell pyeclib a lie - from it's perspective, as long as data_len >= # segment_size it'll give us the answer we want. From our # perspective, because we only use this answer to calculate the # *minimum* size we should read from an object body even if data_len < # segment_size we'll still only read *the whole one and only last # fragment* and pass than into pyeclib who will know what to do with # it just as it always does when the last fragment is < fragment_size. if self._fragment_size is None: self._fragment_size = self.pyeclib_driver.get_segment_info( self.ec_segment_size, self.ec_segment_size)['fragment_size'] return self._fragment_size @property def ec_scheme_description(self): """ This short hand form of the important parts of the ec schema is stored in Object System Metadata on the EC Fragment Archives for debugging. """ return "%s %d+%d" % (self._ec_type, self._ec_ndata, self._ec_nparity) @property def ec_duplication_factor(self): return self._ec_duplication_factor def __repr__(self): extra_info = '' if self.ec_duplication_factor != 1: extra_info = ', ec_duplication_factor=%d' % \ self.ec_duplication_factor return ("%s, EC config(ec_type=%s, ec_segment_size=%d, " "ec_ndata=%d, ec_nparity=%d%s)") % \ (super(ECStoragePolicy, self).__repr__(), self.ec_type, self.ec_segment_size, self.ec_ndata, self.ec_nparity, extra_info) @classmethod def _config_options_map(cls): options = super(ECStoragePolicy, cls)._config_options_map() options.update({ 'ec_type': 'ec_type', 'ec_object_segment_size': 'ec_segment_size', 'ec_num_data_fragments': 'ec_ndata', 'ec_num_parity_fragments': 'ec_nparity', 'ec_duplication_factor': 'ec_duplication_factor', }) return options def get_info(self, config=False): info = super(ECStoragePolicy, self).get_info(config=config) if not config: info.pop('ec_object_segment_size') info.pop('ec_num_data_fragments') info.pop('ec_num_parity_fragments') info.pop('ec_type') info.pop('ec_duplication_factor') return info @property def quorum(self): """ Number of successful backend requests needed for the proxy to consider the client PUT request successful. The quorum size for EC policies defines the minimum number of data + parity elements required to be able to guarantee the desired fault tolerance, which is the number of data elements supplemented by the minimum number of parity elements required by the chosen erasure coding scheme. For example, for Reed-Solomon, the minimum number parity elements required is 1, and thus the quorum_size requirement is ec_ndata + 1. Given the number of parity elements required is not the same for every erasure coding scheme, consult PyECLib for min_parity_fragments_needed() """ return self._ec_quorum_size * self.ec_duplication_factor def load_ring(self, swift_dir): """ Load the ring for this policy immediately. :param swift_dir: path to rings """ if self.object_ring: return def validate_ring_data(ring_data): """ EC specific validation Replica count check - we need _at_least_ (#data + #parity) replicas configured. Also if the replica count is larger than exactly that number there's a non-zero risk of error for code that is considering the number of nodes in the primary list from the ring. """ configured_fragment_count = ring_data.replica_count required_fragment_count = \ (self.ec_n_unique_fragments) * self.ec_duplication_factor if configured_fragment_count != required_fragment_count: raise RingLoadError( 'EC ring for policy %s needs to be configured with ' 'exactly %d replicas. Got %s.' % ( self.name, required_fragment_count, configured_fragment_count)) self.object_ring = Ring( swift_dir, ring_name=self.ring_name, validation_hook=validate_ring_data) def get_backend_index(self, node_index): """ Backend index for PyECLib :param node_index: integer of node index :return: integer of actual fragment index. if param is not an integer, return None instead """ try: node_index = int(node_index) except ValueError: return None return node_index % self.ec_n_unique_fragments