diff --git a/src/client/dfs/dfs_internal.h b/src/client/dfs/dfs_internal.h index c1af0a8a5c5..6892e15ecbe 100644 --- a/src/client/dfs/dfs_internal.h +++ b/src/client/dfs/dfs_internal.h @@ -498,6 +498,9 @@ file_stat_by_oid(dfs_t *dfs, daos_obj_id_t head_oid, daos_obj_id_t tail_oid, boo int file_truncate_zero(dfs_obj_t *obj, daos_handle_t th); int +dfs_default_file_pl(dfs_t *dfs, daos_size_t chunk_size, daos_oclass_id_t *head_cid, + daos_oclass_id_t *tail_cid, daos_size_t *split_off, bool *has_tail); +int get_num_entries(daos_handle_t oh, daos_handle_t th, uint32_t *nr, bool check_empty); int update_stbuf_times(struct dfs_entry entry, daos_epoch_t max_epoch, struct stat *stbuf, diff --git a/src/client/dfs/mnt.c b/src/client/dfs/mnt.c index 20b006a35de..5c24ab354b5 100644 --- a/src/client/dfs/mnt.c +++ b/src/client/dfs/mnt.c @@ -975,6 +975,8 @@ dfs_query(dfs_t *dfs, dfs_attr_t *attr) return EINVAL; memcpy(attr, &dfs->attr, sizeof(dfs_attr_t)); + /* Progressive layout is only reported for the default file class; clear until confirmed. */ + attr->da_file_pl_nr = 0; if (!dfs->attr.da_dir_oclass_id) { rc = daos_obj_get_oclass(dfs->coh, DAOS_OT_MULTI_HASHED, 0, 0, @@ -985,13 +987,42 @@ dfs_query(dfs_t *dfs, dfs_attr_t *attr) } } + /* + * A non-zero da_file_oclass_id means the container has an explicit default file class, so + * the default layout is NOT progressive. Only when it is unset (0) do we resolve the + * default byte-array class and the progressive-layout head/tail segment(s) it would + * produce: da_file_oclass_id becomes the compact head class and da_file_pl_segs[] the wider + * tail segment(s); da_file_pl_nr stays 0 when PL does not apply. + */ if (!dfs->attr.da_file_oclass_id) { + daos_oclass_id_t head_cid = OC_UNKNOWN; + daos_oclass_id_t tail_cid = OC_UNKNOWN; + daos_size_t split_off = 0; + daos_size_t chunk_size; + bool has_tail = false; + rc = daos_obj_get_oclass(dfs->coh, DAOS_OT_ARRAY_BYTE, 0, 0, &attr->da_file_oclass_id); if (rc) { D_ERROR("daos_obj_get_oclass() failed " DF_RC "\n", DP_RC(rc)); return daos_der2errno(rc); } + + chunk_size = + dfs->attr.da_chunk_size ? dfs->attr.da_chunk_size : DFS_DEFAULT_CHUNK_SIZE; + rc = dfs_default_file_pl(dfs, chunk_size, &head_cid, &tail_cid, &split_off, + &has_tail); + if (rc) { + D_ERROR("failed to resolve default file layout: %d\n", rc); + return rc; + } + if (has_tail) { + attr->da_file_oclass_id = head_cid; + attr->da_file_pl_nr = 1; + attr->da_file_pl_segs[0].pls_oclass_id = tail_cid; + attr->da_file_pl_segs[0].pls_split_off = split_off; + attr->da_file_pl_segs[0].pls_oid = DAOS_OBJ_NIL; + } } return 0; diff --git a/src/client/dfs/obj.c b/src/client/dfs/obj.c index 30826a4d8b2..76af67e8a2f 100644 --- a/src/client/dfs/obj.c +++ b/src/client/dfs/obj.c @@ -152,9 +152,9 @@ file_split_off(uint32_t target_nr, uint64_t total_scm, uint64_t total_nvme, /* Resolve file head/tail oclasses, applying PL only when no explicit file class was selected. */ static int -file_oclasses(dfs_t *dfs, dfs_obj_t *parent, daos_oclass_id_t cid, daos_size_t chunk_size, - daos_oclass_id_t *head_cid, daos_oclass_id_t *tail_cid, daos_size_t *split_off, - bool *has_tail) +file_oclasses(dfs_t *dfs, daos_oclass_id_t parent_oclass, daos_oclass_id_t cid, + daos_size_t chunk_size, daos_oclass_id_t *head_cid, daos_oclass_id_t *tail_cid, + daos_size_t *split_off, bool *has_tail) { struct daos_oclass_attr *tail_attr; daos_size_t local_split_off = 0; @@ -168,10 +168,10 @@ file_oclasses(dfs_t *dfs, dfs_obj_t *parent, daos_oclass_id_t cid, daos_size_t c if (cid == 0) { /* Respect explicit file-class choices before considering progressive layout * defaults. */ - if (parent->d.oclass == 0) + if (parent_oclass == 0) cid = dfs->attr.da_file_oclass_id; else - cid = parent->d.oclass; + cid = parent_oclass; } *head_cid = cid; @@ -236,6 +236,22 @@ file_oclasses(dfs_t *dfs, dfs_obj_t *parent, daos_oclass_id_t cid, daos_size_t c return 0; } +/* + * Report the head/tail object classes and split offset a default-class regular file would receive + * under progressive layout in this container (no explicit or parent class). Used by dfs_query() to + * expose the effective default layout. On return \a has_tail is false (and \a tail_cid/\a split_off + * are 0) when progressive layout does not apply to default files. + */ +int +dfs_default_file_pl(dfs_t *dfs, daos_size_t chunk_size, daos_oclass_id_t *head_cid, + daos_oclass_id_t *tail_cid, daos_size_t *split_off, bool *has_tail) +{ + if (dfs == NULL) + return EINVAL; + + return file_oclasses(dfs, 0, 0, chunk_size, head_cid, tail_cid, split_off, has_tail); +} + static int check_access(uid_t c_uid, gid_t c_gid, uid_t uid, gid_t gid, mode_t mode, int mask) { @@ -293,14 +309,20 @@ dfs_obj_get_info(dfs_t *dfs, dfs_obj_t *obj, dfs_obj_info_t *info) return EINVAL; info->doi_oid = obj->oid; - info->doi_tail_oid = DAOS_OBJ_NIL; - info->doi_split_off = 0; + info->doi_pl_nr = 0; switch (obj->mode & S_IFMT) { case S_IFDIR: { /** the oclass of the directory object itself */ info->doi_oclass_id = daos_obj_id2class(obj->oid); + if (obj->d.chunk_size) + info->doi_chunk_size = obj->d.chunk_size; + else if (dfs->attr.da_chunk_size) + info->doi_chunk_size = dfs->attr.da_chunk_size; + else + info->doi_chunk_size = DFS_DEFAULT_CHUNK_SIZE; + /** what is the default oclass files and dirs will be created with in this dir */ if (obj->d.oclass) { /** if parent oclass is EC, dir would be chosen as container default */ @@ -330,23 +352,45 @@ dfs_obj_get_info(dfs_t *dfs, dfs_obj_t *obj, dfs_obj_info_t *info) return daos_der2errno(rc); } - if (dfs->attr.da_file_oclass_id) + if (dfs->attr.da_file_oclass_id) { info->doi_file_oclass_id = dfs->attr.da_file_oclass_id; - else + } else { + daos_oclass_id_t pl_head = 0; + daos_oclass_id_t pl_tail = 0; + daos_size_t pl_split = 0; + bool pl_has_tail = false; + + /** container uses the DAOS-default file class */ rc = daos_obj_get_oclass(dfs->coh, DAOS_OT_ARRAY_BYTE, 0, 0, &info->doi_file_oclass_id); - if (rc) { - D_ERROR("daos_obj_get_oclass() failed " DF_RC "\n", DP_RC(rc)); - return daos_der2errno(rc); + if (rc) { + D_ERROR("daos_obj_get_oclass() failed " DF_RC "\n", + DP_RC(rc)); + return daos_der2errno(rc); + } + + /* + * Files created here use the default class, so they may be + * progressive: report the head/tail classes and split offset a file + * created in this directory would receive. doi_file_oclass_id + * becomes the compact head class and doi_pl_segs[] the wider tail + * segment(s); left as no PL when the pool is too small or the + * layout predates PL. + */ + rc = file_oclasses(dfs, 0, 0, info->doi_chunk_size, &pl_head, + &pl_tail, &pl_split, &pl_has_tail); + if (rc) + return rc; + if (pl_has_tail) { + info->doi_file_oclass_id = pl_head; + info->doi_pl_nr = 1; + info->doi_pl_segs[0].pls_oclass_id = pl_tail; + info->doi_pl_segs[0].pls_split_off = pl_split; + info->doi_pl_segs[0].pls_oid = DAOS_OBJ_NIL; + } } } - if (obj->d.chunk_size) - info->doi_chunk_size = obj->d.chunk_size; - else if (dfs->attr.da_chunk_size) - info->doi_chunk_size = dfs->attr.da_chunk_size; - else - info->doi_chunk_size = DFS_DEFAULT_CHUNK_SIZE; break; } case S_IFREG: { @@ -358,8 +402,10 @@ dfs_obj_get_info(dfs_t *dfs, dfs_obj_t *obj, dfs_obj_info_t *info) info->doi_oclass_id = daos_obj_id2class(obj->oid); if (obj->f.has_tail) { - info->doi_tail_oid = obj->f.tail_oid; - info->doi_split_off = obj->f.split_off; + info->doi_pl_nr = 1; + info->doi_pl_segs[0].pls_oclass_id = daos_obj_id2class(obj->f.tail_oid); + info->doi_pl_segs[0].pls_split_off = obj->f.split_off; + info->doi_pl_segs[0].pls_oid = obj->f.tail_oid; } break; } @@ -403,7 +449,7 @@ open_file(dfs_t *dfs, dfs_obj_t *parent, int flags, daos_oclass_id_t cid, daos_s chunk_size = parent->d.chunk_size; } - rc = file_oclasses(dfs, parent, cid, chunk_size, &cid, &tail_cid, + rc = file_oclasses(dfs, parent->d.oclass, cid, chunk_size, &cid, &tail_cid, &file->f.split_off, &file->f.has_tail); if (rc != 0) return rc; diff --git a/src/control/cmd/daos/filesystem.go b/src/control/cmd/daos/filesystem.go index 107a920221f..6da261876c0 100644 --- a/src/control/cmd/daos/filesystem.go +++ b/src/control/cmd/daos/filesystem.go @@ -1,5 +1,6 @@ // // (C) Copyright 2021-2024 Intel Corporation. +// (C) Copyright 2026 Hewlett Packard Enterprise Development LP // (C) Copyright 2025 Google LLC // // SPDX-License-Identifier: BSD-2-Clause-Patent @@ -286,23 +287,63 @@ func (cmd *fsGetAttrCmd) Execute(_ []string) error { var fileoclassName [16]C.char var oid C.daos_obj_id_t = attrs.doi_oid var oidStr string = fmt.Sprintf("%d.%d", oid.hi, oid.lo) - if C.mode_is_dir(cmode) { + isDir := bool(C.mode_is_dir(cmode)) + + // Collect the progressive-layout tail segment(s) beyond the head (empty when not PL). For a + // file each carries its own tail OID; for a directory template the segment OIDs are nil. + type plTail struct { + oidStr string + oclass string + splitOff uint64 + } + var tails []plTail + for i := 0; i < int(attrs.doi_pl_nr); i++ { + seg := attrs.doi_pl_segs[i] + var segClass [16]C.char + C.daos_oclass_id2name(seg.pls_oclass_id, &segClass[0]) + t := plTail{ + oclass: C.GoString(&segClass[0]), + splitOff: uint64(seg.pls_split_off), + } + if seg.pls_oid.hi != 0 || seg.pls_oid.lo != 0 { + t.oidStr = fmt.Sprintf("%d.%d", seg.pls_oid.hi, seg.pls_oid.lo) + } + tails = append(tails, t) + } + isPL := len(tails) > 0 + + if isDir { C.daos_oclass_id2name(attrs.doi_dir_oclass_id, &diroclassName[0]) C.daos_oclass_id2name(attrs.doi_file_oclass_id, &fileoclassName[0]) } if cmd.JSONOutputEnabled() { - if C.mode_is_dir(cmode) { + if isDir { + type jsonTail struct { + ObjClass string `json:"oclass"` + SplitOff uint64 `json:"split_off"` + } + var jsonTails []jsonTail + for _, t := range tails { + jsonTails = append(jsonTails, jsonTail{ObjClass: t.oclass, SplitOff: t.splitOff}) + } + dirAttr := struct { + DirObjClass string `json:"dir_oclass"` + FileObjClass string `json:"file_oclass"` + FilePLTails []jsonTail `json:"file_pl_tails,omitempty"` + ChunkSize uint64 `json:"chunk_size"` + }{ + FileObjClass: C.GoString(&diroclassName[0]), + DirObjClass: C.GoString(&fileoclassName[0]), + FilePLTails: jsonTails, + ChunkSize: uint64(attrs.doi_chunk_size), + } jsonAttrs := struct { ObjAttr struct { OID string `json:"oid"` ObjClass string `json:"oclass"` } `json:"object"` - DirAttr struct { - DirObjClass string `json:"dir_oclass"` - FileObjClass string `json:"file_oclass"` - ChunkSize uint64 `json:"chunk_size"` - } `json:"directory"` + DirAttr interface{} `json:"directory"` }{ ObjAttr: struct { OID string `json:"oid"` @@ -311,38 +352,76 @@ func (cmd *fsGetAttrCmd) Execute(_ []string) error { OID: oidStr, ObjClass: C.GoString(&oclassName[0]), }, - DirAttr: struct { - DirObjClass string `json:"dir_oclass"` - FileObjClass string `json:"file_oclass"` - ChunkSize uint64 `json:"chunk_size"` - }{ - FileObjClass: C.GoString(&diroclassName[0]), - DirObjClass: C.GoString(&fileoclassName[0]), - ChunkSize: uint64(attrs.doi_chunk_size), - }, - } - return cmd.OutputJSON(jsonAttrs, nil) - } else { - jsonAttrs := &struct { - OID string `json:"oid"` - ObjClass string `json:"oclass"` - ChunkSize uint64 `json:"chunk_size"` - }{ - OID: oidStr, - ObjClass: C.GoString(&oclassName[0]), - ChunkSize: uint64(attrs.doi_chunk_size), + DirAttr: dirAttr, } return cmd.OutputJSON(jsonAttrs, nil) } + type jsonTail struct { + OID string `json:"oid,omitempty"` + ObjClass string `json:"oclass"` + SplitOff uint64 `json:"split_off"` + } + var jsonTails []jsonTail + for _, t := range tails { + jsonTails = append(jsonTails, jsonTail{OID: t.oidStr, ObjClass: t.oclass, SplitOff: t.splitOff}) + } + jsonAttrs := &struct { + OID string `json:"oid"` + ObjClass string `json:"oclass"` + ChunkSize uint64 `json:"chunk_size"` + Tails []jsonTail `json:"tails,omitempty"` + }{ + OID: oidStr, + ObjClass: C.GoString(&oclassName[0]), + ChunkSize: uint64(attrs.doi_chunk_size), + Tails: jsonTails, + } + return cmd.OutputJSON(jsonAttrs, nil) } - cmd.Infof("OID = %s", oidStr) - cmd.Infof("Object Class = %s", C.GoString(&oclassName[0])) - if C.mode_is_dir(cmode) { + if isDir { + cmd.Infof("OID = %s", oidStr) + cmd.Infof("Object Class = %s", C.GoString(&oclassName[0])) cmd.Infof("Directory Creation Object Class = %s", C.GoString(&diroclassName[0])) - cmd.Infof("File Creation Object Class = %s", C.GoString(&fileoclassName[0])) - cmd.Infof("File Creation Chunk Size = %d", attrs.doi_chunk_size) + if isPL { + cmd.Infof("File Creation Head Object Class = %s", C.GoString(&fileoclassName[0])) + for i, t := range tails { + if len(tails) > 1 { + cmd.Infof("File Creation Tail %d Object Class = %s", i+1, t.oclass) + cmd.Infof("File Creation Split Offset %d = %d", i+1, t.splitOff) + } else { + cmd.Infof("File Creation Tail Object Class = %s", t.oclass) + cmd.Infof("File Creation Split Offset = %d", t.splitOff) + } + } + cmd.Infof("File Creation Chunk Size = %d", attrs.doi_chunk_size) + } else { + cmd.Infof("File Creation Object Class = %s", C.GoString(&fileoclassName[0])) + cmd.Infof("File Creation Chunk Size = %d", attrs.doi_chunk_size) + } + } else if isPL { + cmd.Infof("Head OID = %s", oidStr) + cmd.Infof("Head Object Class = %s", C.GoString(&oclassName[0])) + for i, t := range tails { + if len(tails) > 1 { + cmd.Infof("Tail %d OID = %s", i+1, t.oidStr) + cmd.Infof("Tail %d Object Class = %s", i+1, t.oclass) + } else { + cmd.Infof("Tail OID = %s", t.oidStr) + cmd.Infof("Tail Object Class = %s", t.oclass) + } + } + cmd.Infof("Object Chunk Size = %d", attrs.doi_chunk_size) + for i, t := range tails { + if len(tails) > 1 { + cmd.Infof("Split Offset %d = %d", i+1, t.splitOff) + } else { + cmd.Infof("Split Offset = %d", t.splitOff) + } + } } else { + cmd.Infof("OID = %s", oidStr) + cmd.Infof("Object Class = %s", C.GoString(&oclassName[0])) cmd.Infof("Object Chunk Size = %d", attrs.doi_chunk_size) } return nil diff --git a/src/control/cmd/daos/pretty/container.go b/src/control/cmd/daos/pretty/container.go index 3604d71255f..78bb5f71d5d 100644 --- a/src/control/cmd/daos/pretty/container.go +++ b/src/control/cmd/daos/pretty/container.go @@ -48,7 +48,21 @@ func PrintContainerInfo(out io.Writer, ci *daos.ContainerInfo, verbose bool) err rows = append(rows, txtfmt.TableRow{"Dir Object Class": ci.DirObjectClass.String()}) } if ci.FileObjectClass != 0 { - rows = append(rows, txtfmt.TableRow{"File Object Class": ci.FileObjectClass.String()}) + label := "File Object Class" + if len(ci.FilePLTails) > 0 { + label = "File Head Object Class" + } + rows = append(rows, txtfmt.TableRow{label: ci.FileObjectClass.String()}) + } + for i, seg := range ci.FilePLTails { + tailLabel := "File Tail Object Class" + splitLabel := "Split Offset" + if len(ci.FilePLTails) > 1 { + tailLabel = fmt.Sprintf("File Tail %d Object Class", i+1) + splitLabel = fmt.Sprintf("Split Offset %d", i+1) + } + rows = append(rows, txtfmt.TableRow{tailLabel: seg.ObjectClass.String()}) + rows = append(rows, txtfmt.TableRow{splitLabel: humanize.IBytes(seg.SplitOffset)}) } if ci.Hints != "" { rows = append(rows, txtfmt.TableRow{"Hints": ci.Hints}) diff --git a/src/control/lib/daos/api/container.go b/src/control/lib/daos/api/container.go index abb3e3b2291..3d9a1b944dd 100644 --- a/src/control/lib/daos/api/container.go +++ b/src/control/lib/daos/api/container.go @@ -381,6 +381,13 @@ func containerQueryDFSAttrs(contConn *ContainerHandle) (*daos.POSIXAttributes, e pa.ObjectClass = daos.ObjectClass(attr.da_oclass_id) pa.DirObjectClass = daos.ObjectClass(attr.da_dir_oclass_id) pa.FileObjectClass = daos.ObjectClass(attr.da_file_oclass_id) + for i := 0; i < int(attr.da_file_pl_nr); i++ { + seg := attr.da_file_pl_segs[i] + pa.FilePLTails = append(pa.FilePLTails, daos.PLSegment{ + ObjectClass: daos.ObjectClass(seg.pls_oclass_id), + SplitOffset: uint64(seg.pls_split_off), + }) + } pa.ConsistencyMode = uint32(attr.da_mode) pa.Hints = C.GoString(&attr.da_hints[0]) diff --git a/src/control/lib/daos/api/libdfs_stubs.go b/src/control/lib/daos/api/libdfs_stubs.go index 7b8f5e21a55..881321dcf32 100644 --- a/src/control/lib/daos/api/libdfs_stubs.go +++ b/src/control/lib/daos/api/libdfs_stubs.go @@ -65,6 +65,11 @@ func dfs_query(dfs *C.dfs_t, attrs *C.dfs_attr_t) C.int { attrs.da_chunk_size = C.uint64_t(dfs_query_Attrs.ChunkSize) attrs.da_dir_oclass_id = C.uint32_t(dfs_query_Attrs.DirObjectClass) attrs.da_file_oclass_id = C.uint32_t(dfs_query_Attrs.FileObjectClass) + attrs.da_file_pl_nr = C.uint32_t(len(dfs_query_Attrs.FilePLTails)) + for i, seg := range dfs_query_Attrs.FilePLTails { + attrs.da_file_pl_segs[i].pls_oclass_id = C.uint32_t(seg.ObjectClass) + attrs.da_file_pl_segs[i].pls_split_off = C.uint64_t(seg.SplitOffset) + } attrs.da_oclass_id = C.uint32_t(dfs_query_Attrs.ObjectClass) attrs.da_mode = C.uint32_t(dfs_query_Attrs.ConsistencyMode) C.strcpy(&attrs.da_hints[0], C.CString(dfs_query_Attrs.Hints)) diff --git a/src/control/lib/daos/container.go b/src/control/lib/daos/container.go index fc3b5ece6f2..917c8332bfe 100644 --- a/src/control/lib/daos/container.go +++ b/src/control/lib/daos/container.go @@ -126,12 +126,21 @@ func (l *ContainerLayout) UnmarshalJSON(data []byte) error { } type ( + // PLSegment describes one progressive-layout tail segment of a file beyond the head. + PLSegment struct { + ObjectClass ObjectClass `json:"object_class"` + SplitOffset uint64 `json:"split_offset"` + } + // POSIXAttributes contains extended information about POSIX-layout containers. POSIXAttributes struct { ChunkSize uint64 `json:"chunk_size,omitempty"` ObjectClass ObjectClass `json:"object_class,omitempty"` DirObjectClass ObjectClass `json:"dir_object_class,omitempty"` FileObjectClass ObjectClass `json:"file_object_class,omitempty"` + // Progressive-layout tail segment(s) of the default file layout, ordered by split + // offset (empty when PL does not apply). The head class is FileObjectClass. + FilePLTails []PLSegment `json:"file_pl_tails,omitempty"` ConsistencyMode uint32 `json:"cons_mode,omitempty"` Hints string `json:"hints,omitempty"` } diff --git a/src/include/daos_fs.h b/src/include/daos_fs.h index 5d2f437fc92..fe85a0b16d0 100644 --- a/src/include/daos_fs.h +++ b/src/include/daos_fs.h @@ -39,6 +39,8 @@ extern "C" { #define DFS_MAX_FSIZE (~0ULL) /** Default chunk size for files (arrays) */ #define DFS_DEFAULT_CHUNK_SIZE 1048576 +/** Maximum number of progressive-layout tail segments reported for a file (head is separate) */ +#define DFS_PL_MAX_SEGMENTS 8 /** Maximum xattr name */ #define DFS_MAX_XATTR_NAME 255 /** Maximum xattr value */ @@ -68,6 +70,16 @@ typedef struct dfs dfs_t; /** read/write access */ #define DFS_RDWR O_RDWR +/** One progressive-layout segment of a file beyond the head. */ +typedef struct { + /** object class of this segment */ + daos_oclass_id_t pls_oclass_id; + /** logical byte offset at which this segment begins */ + daos_size_t pls_split_off; + /** object id of this segment; nil for template layouts (dir/container defaults) */ + daos_obj_id_t pls_oid; +} dfs_pl_seg_t; + /** struct holding attributes for a DFS container - all optional */ typedef struct { /** User ID for DFS container. */ @@ -92,6 +104,20 @@ typedef struct { * examples include: "file:single,dir:max", "directory:single,file:max", etc. */ char da_hints[DAOS_CONT_HINT_MAX_LEN]; + /** + * Output-only (populated by dfs_query()): number of progressive-layout tail segments of the + * default regular-file layout. When > 0, da_file_oclass_id is the compact HEAD class and + * da_file_pl_segs[0..nr-1] describe the wider tail segment(s). 0 when progressive layout + * does not apply to default files (an explicit file class is set, the layout version + * predates PL, or the pool is too small). + */ + uint32_t da_file_pl_nr; + /** + * Tail segment(s) of the default regular-file layout in increasing split-offset order + * (valid for indices [0, da_file_pl_nr); pls_oid is nil for this container-default + * template). + */ + dfs_pl_seg_t da_file_pl_segs[DFS_PL_MAX_SEGMENTS]; } dfs_attr_t; /** IO descriptor of ranges in a file to access */ @@ -114,10 +140,18 @@ typedef struct { daos_oclass_id_t doi_file_oclass_id; /** Object id */ daos_obj_id_t doi_oid; - /** Tail object id for progressive-layout files, or nil when absent */ - daos_obj_id_t doi_tail_oid; - /** Logical file offset where progressive layout switches from head to tail */ - daos_size_t doi_split_off; + /** + * Number of progressive-layout segments beyond the head. For a regular FILE the head is + * doi_oid / doi_oclass_id; for a DIRECTORY this describes the file-creation default (head + * is doi_file_oclass_id). 0 when progressive layout does not apply. + */ + uint32_t doi_pl_nr; + /** + * Progressive-layout segments beyond the head, ordered by increasing split offset (valid + * for indices [0, doi_pl_nr)). Each entry carries the segment's object class, split offset, + * and oid (nil for a directory file-creation template). + */ + dfs_pl_seg_t doi_pl_segs[DFS_PL_MAX_SEGMENTS]; } dfs_obj_info_t; /** diff --git a/src/tests/suite/dfs_unit_test.c b/src/tests/suite/dfs_unit_test.c index 3c5513b5ff0..91fcf806a7e 100644 --- a/src/tests/suite/dfs_unit_test.c +++ b/src/tests/suite/dfs_unit_test.c @@ -95,7 +95,14 @@ dfs_test_mount(void **state) assert_int_equal(attr.da_dir_oclass_id, exp_doc); rc = daos_obj_get_oclass(coh, DAOS_OT_ARRAY_BYTE, 0, 0, &exp_foc); assert_rc_equal(rc, 0); - assert_int_equal(attr.da_file_oclass_id, exp_foc); + if (attr.da_file_pl_nr > 0) + /* + * Progressive layout: da_file_oclass_id is the compact head class and the wide + * default class for arrays is what is used for the tail segment. + */ + assert_int_equal(attr.da_file_pl_segs[0].pls_oclass_id, exp_foc); + else + assert_int_equal(attr.da_file_oclass_id, exp_foc); rc = dfs_umount(dfs); assert_int_equal(rc, 0); @@ -3690,12 +3697,14 @@ assert_file_oclass_selection(dfs_t *dfs, daos_handle_t coh, const daos_pool_info : 0; print_message("PL test '%s': tail expected_present=%d actual_present=%d split_off " "expected=%zu actual=%zu chunk=%zu\n", - name, exp_has_tail, !daos_obj_id_is_nil(info.doi_tail_oid), exp_split_off, - info.doi_split_off, info.doi_chunk_size); - assert_int_equal(!daos_obj_id_is_nil(info.doi_tail_oid), exp_has_tail); - assert_int_equal(info.doi_split_off, exp_split_off); + name, exp_has_tail, info.doi_pl_nr > 0, exp_split_off, + info.doi_pl_nr > 0 ? info.doi_pl_segs[0].pls_split_off : 0, + info.doi_chunk_size); + assert_int_equal(info.doi_pl_nr > 0, exp_has_tail); if (exp_has_tail) { - actual_cid = daos_obj_id2class(info.doi_tail_oid); + assert_int_equal(info.doi_pl_nr, 1); + assert_int_equal(info.doi_pl_segs[0].pls_split_off, exp_split_off); + actual_cid = daos_obj_id2class(info.doi_pl_segs[0].pls_oid); daos_oclass_id2name(exp_tail, exp_tail_name); daos_oclass_id2name(actual_cid, act_tail_name); rc = compare_oclass(coh, actual_cid, exp_tail); @@ -3703,7 +3712,7 @@ assert_file_oclass_selection(dfs_t *dfs, daos_handle_t coh, const daos_pool_info act_tail_name); assert_rc_equal(rc, 0); } else { - assert_true(daos_obj_id_is_nil(info.doi_tail_oid)); + assert_int_equal(info.doi_pl_nr, 0); } rc = dfs_release(obj); @@ -4370,16 +4379,16 @@ dfs_test_pl_io(void **state) rc = dfs_obj_get_info(dfs_l, obj, &info); assert_int_equal(rc, 0); - split = info.doi_split_off; - print_message("PL IO: split_off=%zu has_tail=%d chunk=%zu\n", split, - !daos_obj_id_is_nil(info.doi_tail_oid), info.doi_chunk_size); + split = info.doi_pl_nr > 0 ? info.doi_pl_segs[0].pls_split_off : 0; + print_message("PL IO: split_off=%zu has_tail=%d chunk=%zu\n", split, info.doi_pl_nr > 0, + info.doi_chunk_size); if (split == 0) { /* PL did not engage in this environment (e.g. tail class has no group count). */ print_message("PL not active; skipping PL IO data-path checks\n"); goto out; } - assert_false(daos_obj_id_is_nil(info.doi_tail_oid)); + assert_true(info.doi_pl_nr > 0); assert_true(split > 8192); /* Head-only IO (entirely below split_off), sync then async. */