Beispiel #1
0
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']
Beispiel #2
0
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)
Beispiel #4
0
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
Beispiel #5
0
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
Beispiel #6
0
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
Beispiel #7
0
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']
Beispiel #8
0
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