The community of developers working on ZFS continues to grow, as does
the diversity of companies betting big on ZFS.  We wanted a forum for
these developers to coordinate their efforts and exchange ideas.  The
ZFS working group was formed to coordinate these development efforts.
The working group encourages new membership.  In order to maintain the
group's focus on ZFS development, candidates should demonstrate
significant and ongoing contribution to ZFS.

The first product of the working group is the design for a ZFS on-disk
versioning method that will allow for distributed development of ZFS
on-disk format changes without further explicit coordination. This
method eliminates the problem of two developers both allocating
version number 31 to mean their own feature.

This "feature flags" versioning allows unknown versions to be
identified, and in many cases the ZFS pool or filesystem can be
accessed read-only even in the presence of unknown on-disk features.
My proposal covers versioning of the SPA/zpool, ZPL/zfs, send stream,
and allocation of compression and checksum identifiers (enum values).

We plan to implement the feature flags this summer, and aim to
integrate it into Illumos.  I welcome feedback on my proposal, and I'd
especially like to hear from people doing ZFS development -- what are
you working on?  Does this meet your needs?  If we implement it, will
you use it?

Thanks,
--matt
ZFS Feature Flags proposal, version 1.0, May 25th 2011

===============================
ON-DISK FORMAT CHANGES
===============================

for SPA/zpool versioning:
        new pool version = SPA_VERSION_FEATURES = 1000
        ZAP objects in MOS, pointed to by DMU_POOL_DIRECTORY_OBJECT = 1
                "features_for_read" -> { feature name -> nonzero if in use }
                "features_for_write" -> { feature name -> nonzero if in use }
                "feature_descriptions" -> { feature name -> description }
        Note that a pool can't be opened "write-only", so the
        features_for_read are always required.  A given feature should
        be stored in either features_for_read or features_for_write, not
        both.
        Note that if a feature is "promoted" from a company-private
        feature to part of a larger distribution (eg. illumos), this can
        be handled in a variety of ways, all of which can be handled
        with code added at that time, without changing the on-disk
        format.

for ZPL/zfs versioning:
        new zpl version = ZPL_VERSION_FEATURES = 1000
        same 3 ZAP objects as above, but pointed to by MASTER_NODE_OBJ = 1
                "features_for_read" -> { feature name -> nonzero if in use }
                "features_for_write" -> { feature name -> nonzero if in use }
                "feature_descriptions" -> { feature name -> description }
        Note that the namespace for ZPL features is separate from SPA
        features (like version numbers), so the same feature name can be
        used for both (eg. for related SPA and ZPL features), but
        compatibility-wise this is not treated specially.

for compression:
        must be at pool version SPA_VERSION_FEATURES
        ZAP object in MOS, pointed to by POOL_DIR_OBJ:
                "compression_algos" -> { algo name -> enum value }
        Existing enum values (0-14) must stay the same, but new
        algorithms may have different enum values in different pools.
        Note that this simply defines the enum value.  If a new algorithm
        is in use, there must also be a corresponding feature in
        features_for_read with a nonzero value.  For simplicity, all
        algorithms, including legacy algorithms with fixed values (lzjb,
        gzip, etc) should be stored here (pending evaluation of
        prototype code -- this may be more trouble than it's worth).

for checksum:
        must be at pool version SPA_VERSION_FEATURES
        ZAP object in MOS, pointed to by POOL_DIR_OBJ:
                "checksum_algos" -> { algo name -> enum value }
        All notes for compression_algos apply here too.

Must also store copy of what's needed to read the MOS in label nvlist:
        "features_for_read" -> { feature name -> nonzero if in use }
        "compression_algos" -> { algo name -> enum value }
        "checksum_algos" -> { algo name -> enum value }

        ZPL information is never needed.
        It's fine to store complete copies of these objects in the label.
        However, space in the label is limited.  It's only *required* to
        store information needed to read the MOS so we can get to the
        definitive version of this information.  Eg, introduce new
        compression algo, but it is never used in the MOS, don't need to
        add it to the label.  Legacy algos with fixed values may be
        omitted from the label nvlist (eg. lzjb, fletcher4).

        The values in the nvlist features_for_read map may be different
        from the values in the MOS features_for_read.  However, they
        must be the same when interpreted as a boolean (ie, the nvlist
        value != 0 iff the MOS value != 0).  This is so that the nvlist
        map need not be updated whenever the "reference count" on a
        feature changes, only when it changes to/from zero.

for send stream:
        new feature flag DRR_FLAG_FEATURES = 1<<16
        BEGIN record has nvlist payload
        nvlist has:
                "features" -> { feature name -> unspecified }
                "types" -> { type name -> enum value }
        types are record types, existing ones are reserved.  New types
        should have a corresponding feature, so presence of an unknown
        type is not an error.  If an unknown type is used, records of
        that type can be safely ignored.  So if a new record type can
        not be safely ignored, a corresponding new feature must be added.

all name formats (feature name, algo name, type name):
        <reverse-dns>:<short-name> eg. com.delphix:raidz4

all ALL_CAPS_STRING_DEFINITIONS will be #defined to the lowercase string, eg:
#define FEATURES_FOR_READ "features_for_read"


===============================
BEHAVIOR CHANGES
===============================

zpool upgrade
        zpool upgrade (no arguments)
                If the pool is at SPA_VERSION_FEATURES, but this
                software supports features which are not listed in the
                features_for_* MOS objects, the pool should be listed as
                available to upgrade.  It's recommended that the short
                name of the available features be listed.
        
        zpool upgrade -v
                After the list of static versions, each supported
                feature should be listed.
                
        zpool upgrade -a | <pool>
                The pool or pools will have their features_for_* MOS
                objects updated to list all features supported by this
                software.  Ideally, the value of the newly-added ZAP
                entries will be 0, indicating that the feature is
                enabled but not yet in use.
        
        zpool upgrade -V <version> -a | <pool>
                The <version> may specify a feature, rather than a
                version number, if the version is already at
                SPA_VERSION_FEATURES.  The feature may be specified by
                its short or full name.  The pool or pools will have
                their features_for_* MOS object updated to list the
                specified feature, and any other features required by
                the specified one.

pool open ("zpool import" and implicit import from zpool.cache)
        If pool is at SPA_VERSION_FEATURES, we must check for feature
        compatibility.  First we will look through entries in the label
        nvlist's features_for_read.  If there is a feature listed there
        which we don't understand, and it has a nonzero value, then we
        can not open the pool.

        Each vendor may decide how much information they want to print
        about the unsupported feature.  It may be a catch all "Pool
        could not be opened because it uses an unsupported feature.", or
        it may be the most verbose message, "Pool could not be opened
        because it uses the following unsupported features: <long
        feature name> <feature description> ...".  Or features from
        known vs foreign vendors may be treated differently (eg. print
        this vendors features description, but not unknown vendors').
        Note that if a feature in the label is not supported, we can't
        access the feature description, so at best we can print the full
        feature name.

        After checking the label's features_for_read, we know we can
        read the MOS, so we will continue opening it and then check the
        MOS's features_for_read.  Note that we will need to load the
        label's checksum_algos and compression_algos before reading any
        blocks.  This should be implemented as:

        If the pool is bring opened for writing, then features_for_write
        must also be checked.  (Note, currently grub and zdb open the
        pool read-only, and the kernel module opens the pool for
        writing.  In the future it would be great to allow the kernel
        module to open the pool read-only.)

zfs upgrade
        Treat this similarly to zpool upgrade, using the filesystem's
        MASTER_NODE's features_for_* objects.

filesystem mount
        Treat this similarly to pool open, using the filesystem's
        MASTER_NODE's features_for_* objects.

zfs receive
        If any unknown features are in the stream's BEGIN record's
        nvlist's "features" entry, then the stream can not be received.

===============================
IMPLEMENTATION NOTES
===============================

        Legacy checksum algorithms are to be stored as follows:
        "com.sun:label" -> 3
        "com.sun:gang_header" -> 4
        "com.sun:zilog" -> 5
        "com.sun:fletcher2" -> 6
        "com.sun:fletcher4" -> 7
        "com.sun:sha256" -> 8
        "com.sun:zilog2" -> 9

        Legacy compression algorithms are to be stored as follows:
        "com.sun:lzjb" -> 3
        "com.sun:empty" -> 4
        "com.sun:gzip-1" -> 5
        "com.sun:gzip-2" -> 6
        "com.sun:gzip-3" -> 7
        "com.sun:gzip-4" -> 8
        "com.sun:gzip-5" -> 9
        "com.sun:gzip-6" -> 10
        "com.sun:gzip-7" -> 11
        "com.sun:gzip-8" -> 12
        "com.sun:gzip-9" -> 13
        "com.sun:zle" -> 14

        Legacy send record types are to be stored as follows:
        "com.sun:begin" -> 0
        "com.sun:object" -> 1
        "com.sun:freeobjects" -> 2
        "com.sun:write" -> 3
        "com.sun:free" -> 4
        "com.sun:end" -> 5
        "com.sun:write_byref" -> 6
        "com.sun:spill" -> 7

        The indirection tables for checksum algorithm, compression
        algorithm, and send stream record type can be implemented as
        follows:

        enum zio_checksum {
                ZIO_CHECKSUM_INHERIT = 0,
                ZIO_CHECKSUM_ON,
                ZIO_CHECKSUM_OFF,
                ZIO_CHECKSUM_LABEL,
                ZIO_CHECKSUM_GANG_HEADER,
                ZIO_CHECKSUM_ZILOG,
                ZIO_CHECKSUM_FLETCHER_2,
                ZIO_CHECKSUM_FLETCHER_4,
                ZIO_CHECKSUM_SHA256,
                ZIO_CHECKSUM_ZILOG2,
                ...
                ZIO_CHECKSUM_FUNCTIONS
        };

        const char *zio_checksum_names[] = {
                /* Order must match enum zio_checksum! */
                "inherit",
                "on",
                "off",
                "com.sun:label",
                "com.sun:gang_header"
                "com.sun:zilog"
                "com.sun:fletcher2"
                "com.sun:fletcher4"
                "com.sun:sha256"
                "com.sun:zilog2"
                ...
        };

        /*
         * inherit, on, and off are not stored on disk, so
         * pre-initialize them here.  Note that 8 bits are used for the
         * checksum algorithm in the blkptr_t, so there are 256 posible
         * values.
         */
        uint8_t checksum_to_index[ZIO_CHECKSUM_FUNCTIONS] = {0, 1, 2};
        enum zio_checksum index_to_checksum[256] = {0, 1, 2};

        void add_checksum_algo(const char *algo_name, uint8_t value) {
                enum zio_checksum i;
                for (i = 0; i < ZIO_CHECKSUM_FUNCTIONS; i++) {
                        if (strcmp(algo_name, zio_checksum_names[i]) == 0) {
                                checksum_to_index[i] = value;
                                index_to_checksum[value] = i;
                        }
                }
                /* Ignore any unknown algorithms. */
        }

        #define BP_GET_CHECKSUM(bp) index_to_checksum[BF64_GET(...)]
        #define BP_SET_CHECKSUM(bp, x) BF64_SET(..., checksum_to_index[x])


_______________________________________________
zfs-discuss mailing list
zfs-discuss@opensolaris.org
http://mail.opensolaris.org/mailman/listinfo/zfs-discuss

Reply via email to