From 62ddd0c9c12dc3a82a2cd6896b07a1b6af4a8ddf Mon Sep 17 00:00:00 2001 From: Tomasz Gromadzki Date: Wed, 11 Mar 2026 06:39:43 +0100 Subject: [PATCH 1/9] DAOS-18495 md: 24k ABT stack size for md_on_ssd Increase min ABT stack size to 24k for md_on_ssd Signed-off-by: Tomasz Gromadzki Priority: 2 --- src/control/server/engine/config.go | 42 ++++++++++++++++++++++-- src/control/server/engine/config_test.go | 4 +-- src/control/server/server.go | 6 ++++ 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/control/server/engine/config.go b/src/control/server/engine/config.go index e8c16ee15fb..759ec47e985 100644 --- a/src/control/server/engine/config.go +++ b/src/control/server/engine/config.go @@ -31,8 +31,9 @@ const ( envLogDbgStreams = "DD_MASK" envLogSubsystems = "DD_SUBSYS" - minABTThreadStackSizeDCPM = 20480 - minABTThreadStackSizeUCX = 32768 + minABTThreadStackSizeDCPM = 20480 + minABTThreadStackSizeUCX = 32768 + minABTThreadStackSizeMdOnSsd = 24576 ) // FabricConfig encapsulates networking fabric configuration. @@ -367,6 +368,28 @@ func (c *Config) UpdatePMDKEnvarsStackSizeDCPM() error { return nil } +// Ensure at least 24KiB ABT stack size for md_on_ssd. +func (c *Config) UpdateMdOnSsdStackSize() error { + stackSizeStr, err := c.GetEnvVar("ABT_THREAD_STACKSIZE") + if err != nil { + c.EnvVars = append(c.EnvVars, fmt.Sprintf("ABT_THREAD_STACKSIZE=%d", + minABTThreadStackSizeMdOnSsd)) + return nil + } + // Ensure at least 24KiB ABT stack size for an engine in md_on_ssd mode. + stackSizeValue, err := strconv.Atoi(stackSizeStr) + if err != nil { + return errors.Errorf("env_var ABT_THREAD_STACKSIZE has invalid value: %s", + stackSizeStr) + } + if stackSizeValue < minABTThreadStackSizeMdOnSsd { + return errors.Errorf("env_var ABT_THREAD_STACKSIZE should be >= %d "+ + "for MD on SSD, found %d", minABTThreadStackSizeMdOnSsd, + stackSizeValue) + } + return nil +} + // Ensure proper configuration of shutdown (SDS) state func (c *Config) UpdatePMDKEnvarsPMemobjConf(isDCPM bool) error { pmemobjConfStr, pmemobjConfErr := c.GetEnvVar("PMEMOBJ_CONF") @@ -419,6 +442,21 @@ func (c *Config) UpdatePMDKEnvars() error { return nil } +// Ensure 24k for md_on_ssd configuration +func (c *Config) UpdateABTEnvarsMdOnSsd() error { + + if len(c.Storage.Tiers) == 0 { + return errors.New("Invalid config - no tier 0 defined") + } + + isDCPM := c.Storage.Tiers[0].Class == storage.ClassDcpm + + if !isDCPM { + return c.UpdateMdOnSsdStackSize() + } + return nil +} + // Increase ABT stack size for UCX provider. func (c *Config) UpdateABTEnvarsUCX() error { diff --git a/src/control/server/engine/config_test.go b/src/control/server/engine/config_test.go index 48c29a7b794..d8b462ce7b1 100644 --- a/src/control/server/engine/config_test.go +++ b/src/control/server/engine/config_test.go @@ -1110,7 +1110,7 @@ func TestConfig_UpdatePMDKEnvarsStackSizeDCPM(t *testing.T) { validConfig := func() *Config { return MockConfig().WithStorage( storage.NewTierConfig(). - WithStorageClass("dcpm")) + WithStorageClass(storage.ClassDcpm.String())) } for name, tc := range map[string]struct { @@ -1223,7 +1223,7 @@ func TestConfig_UpdateABTEnvarsUCX(t *testing.T) { func TestConfig_UpdatePMDKEnvarsPMemobjConfDCPM(t *testing.T) { validConfig := func() *Config { return MockConfig().WithStorage( - storage.NewTierConfig().WithStorageClass("dcpm")) + storage.NewTierConfig().WithStorageClass(storage.ClassDcpm.String())) } for name, tc := range map[string]struct { diff --git a/src/control/server/server.go b/src/control/server/server.go index 4ea72603f3d..de401670bee 100644 --- a/src/control/server/server.go +++ b/src/control/server/server.go @@ -117,6 +117,12 @@ func processConfig(log logging.Logger, cfg *config.Server, fis *hardware.FabricI } } + for _, ec := range cfg.Engines { + if err := ec.UpdateABTEnvarsMdOnSsd(); err != nil { + return err + } + } + return nil } From 71e905946ec578fbde97460301effc6572e6621c Mon Sep 17 00:00:00 2001 From: Tomasz Gromadzki Date: Wed, 11 Mar 2026 06:58:38 +0100 Subject: [PATCH 2/9] Unit tests for UpdateMdOnSsdStackSize() Priority: 2 Signed-off-by: Tomasz Gromadzki --- src/control/server/engine/config_test.go | 57 ++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/control/server/engine/config_test.go b/src/control/server/engine/config_test.go index d8b462ce7b1..8a8d8d45705 100644 --- a/src/control/server/engine/config_test.go +++ b/src/control/server/engine/config_test.go @@ -1162,6 +1162,63 @@ func TestConfig_UpdatePMDKEnvarsStackSizeDCPM(t *testing.T) { } } +func TestConfig_UpdateMdOnSsdStackSize(t *testing.T) { + validConfig := func() *Config { + return MockConfig().WithStorage( + storage.NewTierConfig(). + WithStorageClass(storage.ClassDcpm.String())) + } + + for name, tc := range map[string]struct { + cfg *Config + expErr error + expABTthreadStackSize int + }{ + "empty config should not fail": { + cfg: MockConfig(), + expABTthreadStackSize: minABTThreadStackSizeDCPM, + }, + "valid config for md_on_ssd should not fail": { + cfg: validConfig(). + WithEnvVarAbtThreadStackSize(minABTThreadStackSizeMdOnSsd), + expABTthreadStackSize: minABTThreadStackSizeMdOnSsd, + }, + "config for md_on_ssd without thread size should not fail": { + cfg: validConfig(), + expABTthreadStackSize: minABTThreadStackSizeMdOnSsd, + }, + "config for md_on_ssd with stack size big enough should not fail": { + cfg: validConfig(). + WithEnvVarAbtThreadStackSize(minABTThreadStackSizeMdOnSsd + 1), + expABTthreadStackSize: minABTThreadStackSizeMdOnSsd + 1, + }, + "config for md_on_ssd with stack size too small should fail": { + cfg: validConfig(). + WithEnvVarAbtThreadStackSize(minABTThreadStackSizeMdOnSsd - 1), + expErr: errors.New(fmt.Sprintf("env_var ABT_THREAD_STACKSIZE "+ + "should be >= %d for MD on SSD, found %d", + minABTThreadStackSizeMdOnSsd, minABTThreadStackSizeMdOnSsd-1)), + }, + "config for md_on_ssd with invalid ABT_THREAD_STACKSIZE value should fail": { + cfg: validConfig().WithEnvVars("ABT_THREAD_STACKSIZE=foo_bar"), + expErr: errors.New("env_var ABT_THREAD_STACKSIZE has invalid value: foo_bar"), + }, + } { + t.Run(name, func(t *testing.T) { + err := tc.cfg.UpdateMdOnSsdStackSize() + test.CmpErr(t, tc.expErr, err) + if err == nil { + stackSizeStr, err := tc.cfg.GetEnvVar("ABT_THREAD_STACKSIZE") + test.AssertTrue(t, err == nil, "Missing env var ABT_THREAD_STACKSIZE") + stackSizeVal, err := strconv.Atoi(stackSizeStr) + test.AssertTrue(t, err == nil, "Invalid env var ABT_THREAD_STACKSIZE") + test.AssertEqual(t, tc.expABTthreadStackSize, stackSizeVal, + "Invalid ABT_THREAD_STACKSIZE value") + } + }) + } +} + func TestConfig_UpdateABTEnvarsUCX(t *testing.T) { validConfig := func() *Config { return MockConfig(). From 5f117f8cd9f5431e60705f05bbe6003d9d33bb1d Mon Sep 17 00:00:00 2001 From: Tomasz Gromadzki Date: Wed, 11 Mar 2026 09:19:18 +0100 Subject: [PATCH 3/9] Fix test Signed-off-by: Tomasz Gromadzki Priority: 2 --- src/control/server/engine/config.go | 44 ++++++++++++------------ src/control/server/engine/config_test.go | 2 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/control/server/engine/config.go b/src/control/server/engine/config.go index 759ec47e985..6ca2cbb9d2e 100644 --- a/src/control/server/engine/config.go +++ b/src/control/server/engine/config.go @@ -368,28 +368,6 @@ func (c *Config) UpdatePMDKEnvarsStackSizeDCPM() error { return nil } -// Ensure at least 24KiB ABT stack size for md_on_ssd. -func (c *Config) UpdateMdOnSsdStackSize() error { - stackSizeStr, err := c.GetEnvVar("ABT_THREAD_STACKSIZE") - if err != nil { - c.EnvVars = append(c.EnvVars, fmt.Sprintf("ABT_THREAD_STACKSIZE=%d", - minABTThreadStackSizeMdOnSsd)) - return nil - } - // Ensure at least 24KiB ABT stack size for an engine in md_on_ssd mode. - stackSizeValue, err := strconv.Atoi(stackSizeStr) - if err != nil { - return errors.Errorf("env_var ABT_THREAD_STACKSIZE has invalid value: %s", - stackSizeStr) - } - if stackSizeValue < minABTThreadStackSizeMdOnSsd { - return errors.Errorf("env_var ABT_THREAD_STACKSIZE should be >= %d "+ - "for MD on SSD, found %d", minABTThreadStackSizeMdOnSsd, - stackSizeValue) - } - return nil -} - // Ensure proper configuration of shutdown (SDS) state func (c *Config) UpdatePMDKEnvarsPMemobjConf(isDCPM bool) error { pmemobjConfStr, pmemobjConfErr := c.GetEnvVar("PMEMOBJ_CONF") @@ -442,6 +420,28 @@ func (c *Config) UpdatePMDKEnvars() error { return nil } +// Ensure at least 24KiB ABT stack size for md_on_ssd. +func (c *Config) UpdateMdOnSsdStackSize() error { + stackSizeStr, err := c.GetEnvVar("ABT_THREAD_STACKSIZE") + if err != nil { + c.EnvVars = append(c.EnvVars, fmt.Sprintf("ABT_THREAD_STACKSIZE=%d", + minABTThreadStackSizeMdOnSsd)) + return nil + } + // Ensure at least 24KiB ABT stack size for an engine in md_on_ssd mode. + stackSizeValue, err := strconv.Atoi(stackSizeStr) + if err != nil { + return errors.Errorf("env_var ABT_THREAD_STACKSIZE has invalid value: %s", + stackSizeStr) + } + if stackSizeValue < minABTThreadStackSizeMdOnSsd { + return errors.Errorf("env_var ABT_THREAD_STACKSIZE should be >= %d "+ + "for MD on SSD, found %d", minABTThreadStackSizeMdOnSsd, + stackSizeValue) + } + return nil +} + // Ensure 24k for md_on_ssd configuration func (c *Config) UpdateABTEnvarsMdOnSsd() error { diff --git a/src/control/server/engine/config_test.go b/src/control/server/engine/config_test.go index 8a8d8d45705..604beb59b16 100644 --- a/src/control/server/engine/config_test.go +++ b/src/control/server/engine/config_test.go @@ -1176,7 +1176,7 @@ func TestConfig_UpdateMdOnSsdStackSize(t *testing.T) { }{ "empty config should not fail": { cfg: MockConfig(), - expABTthreadStackSize: minABTThreadStackSizeDCPM, + expABTthreadStackSize: minABTThreadStackSizeMdOnSsd, }, "valid config for md_on_ssd should not fail": { cfg: validConfig(). From b0a85f7fdc12e65b414f1efc0daf36e1bc9a0702 Mon Sep 17 00:00:00 2001 From: Tomasz Gromadzki Date: Wed, 11 Mar 2026 12:36:02 +0100 Subject: [PATCH 4/9] Final implementation Final implementation based on HasBdevRoleMeta(). Tests improvements. Signed-off-by: Tomasz Gromadzki Priority: 2 Allow-unstable-test: true --- src/control/server/engine/config.go | 8 +---- src/control/server/engine/config_test.go | 41 ++++++++++++++++-------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/control/server/engine/config.go b/src/control/server/engine/config.go index 6ca2cbb9d2e..b3224bde361 100644 --- a/src/control/server/engine/config.go +++ b/src/control/server/engine/config.go @@ -445,13 +445,7 @@ func (c *Config) UpdateMdOnSsdStackSize() error { // Ensure 24k for md_on_ssd configuration func (c *Config) UpdateABTEnvarsMdOnSsd() error { - if len(c.Storage.Tiers) == 0 { - return errors.New("Invalid config - no tier 0 defined") - } - - isDCPM := c.Storage.Tiers[0].Class == storage.ClassDcpm - - if !isDCPM { + if c.Storage.Tiers.HasBdevRoleMeta() { return c.UpdateMdOnSsdStackSize() } return nil diff --git a/src/control/server/engine/config_test.go b/src/control/server/engine/config_test.go index 604beb59b16..c6fe3f65c6b 100644 --- a/src/control/server/engine/config_test.go +++ b/src/control/server/engine/config_test.go @@ -1166,28 +1166,36 @@ func TestConfig_UpdateMdOnSsdStackSize(t *testing.T) { validConfig := func() *Config { return MockConfig().WithStorage( storage.NewTierConfig(). - WithStorageClass(storage.ClassDcpm.String())) + WithStorageClass(storage.ClassRam.String()), + storage.NewTierConfig(). + WithStorageClass(storage.ClassNvme.String()). + WithBdevDeviceRoles(storage.BdevRoleMeta)) } - for name, tc := range map[string]struct { cfg *Config expErr error expABTthreadStackSize int }{ - "empty config should not fail": { + "empty config should not set ABT_THREAD_STACKSIZE": { cfg: MockConfig(), - expABTthreadStackSize: minABTThreadStackSizeMdOnSsd, + expABTthreadStackSize: 0, }, - "valid config for md_on_ssd should not fail": { + "non-md_on_ssd config should not set ABT_THREAD_STACKSIZE": { + cfg: MockConfig().WithStorage( + storage.NewTierConfig(). + WithStorageClass(storage.ClassRam.String())), + expABTthreadStackSize: 0, + }, + "valid config for md_on_ssd should set ABT_THREAD_STACKSIZE": { cfg: validConfig(). WithEnvVarAbtThreadStackSize(minABTThreadStackSizeMdOnSsd), expABTthreadStackSize: minABTThreadStackSizeMdOnSsd, }, - "config for md_on_ssd without thread size should not fail": { + "config for md_on_ssd without thread size should sed ABT_THREAD_STACKSIZE": { cfg: validConfig(), expABTthreadStackSize: minABTThreadStackSizeMdOnSsd, }, - "config for md_on_ssd with stack size big enough should not fail": { + "config for md_on_ssd with stack size big enough should not change ABT_THREAD_STACKSIZE": { cfg: validConfig(). WithEnvVarAbtThreadStackSize(minABTThreadStackSizeMdOnSsd + 1), expABTthreadStackSize: minABTThreadStackSizeMdOnSsd + 1, @@ -1205,15 +1213,20 @@ func TestConfig_UpdateMdOnSsdStackSize(t *testing.T) { }, } { t.Run(name, func(t *testing.T) { - err := tc.cfg.UpdateMdOnSsdStackSize() + err := tc.cfg.UpdateABTEnvarsMdOnSsd() test.CmpErr(t, tc.expErr, err) if err == nil { - stackSizeStr, err := tc.cfg.GetEnvVar("ABT_THREAD_STACKSIZE") - test.AssertTrue(t, err == nil, "Missing env var ABT_THREAD_STACKSIZE") - stackSizeVal, err := strconv.Atoi(stackSizeStr) - test.AssertTrue(t, err == nil, "Invalid env var ABT_THREAD_STACKSIZE") - test.AssertEqual(t, tc.expABTthreadStackSize, stackSizeVal, - "Invalid ABT_THREAD_STACKSIZE value") + if tc.expABTthreadStackSize == 0 { + _, err := tc.cfg.GetEnvVar("ABT_THREAD_STACKSIZE") + test.AssertTrue(t, err != nil, "Unexpected env var ABT_THREAD_STACKSIZE") + } else { + stackSizeStr, err := tc.cfg.GetEnvVar("ABT_THREAD_STACKSIZE") + test.AssertTrue(t, err == nil, "Missing env var ABT_THREAD_STACKSIZE") + stackSizeVal, err := strconv.Atoi(stackSizeStr) + test.AssertTrue(t, err == nil, "Invalid env var ABT_THREAD_STACKSIZE") + test.AssertEqual(t, tc.expABTthreadStackSize, stackSizeVal, + "Invalid ABT_THREAD_STACKSIZE value") + } } }) } From 94a2f4f1869c53ce22937fcf34d020257c80575f Mon Sep 17 00:00:00 2001 From: Tomasz Gromadzki Date: Wed, 11 Mar 2026 14:33:10 +0100 Subject: [PATCH 5/9] Unit Test tuning Only Unit Tests and NLT to be re-run to confirm that logic has not been changed. Signed-off-by: Tomasz Gromadzki Priority: 2 Cancel-prev-build: false Skip-unit-test-memcheck: true Skip-test: true Skip-func-test: true Skip-func-vm: true Skip-func-hw-test: true --- src/control/server/engine/config_test.go | 26 ++++++++++++------------ 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/control/server/engine/config_test.go b/src/control/server/engine/config_test.go index c6fe3f65c6b..1c771cfe8b5 100644 --- a/src/control/server/engine/config_test.go +++ b/src/control/server/engine/config_test.go @@ -1191,7 +1191,7 @@ func TestConfig_UpdateMdOnSsdStackSize(t *testing.T) { WithEnvVarAbtThreadStackSize(minABTThreadStackSizeMdOnSsd), expABTthreadStackSize: minABTThreadStackSizeMdOnSsd, }, - "config for md_on_ssd without thread size should sed ABT_THREAD_STACKSIZE": { + "config for md_on_ssd without thread size should set ABT_THREAD_STACKSIZE": { cfg: validConfig(), expABTthreadStackSize: minABTThreadStackSizeMdOnSsd, }, @@ -1215,19 +1215,19 @@ func TestConfig_UpdateMdOnSsdStackSize(t *testing.T) { t.Run(name, func(t *testing.T) { err := tc.cfg.UpdateABTEnvarsMdOnSsd() test.CmpErr(t, tc.expErr, err) - if err == nil { - if tc.expABTthreadStackSize == 0 { - _, err := tc.cfg.GetEnvVar("ABT_THREAD_STACKSIZE") - test.AssertTrue(t, err != nil, "Unexpected env var ABT_THREAD_STACKSIZE") - } else { - stackSizeStr, err := tc.cfg.GetEnvVar("ABT_THREAD_STACKSIZE") - test.AssertTrue(t, err == nil, "Missing env var ABT_THREAD_STACKSIZE") - stackSizeVal, err := strconv.Atoi(stackSizeStr) - test.AssertTrue(t, err == nil, "Invalid env var ABT_THREAD_STACKSIZE") - test.AssertEqual(t, tc.expABTthreadStackSize, stackSizeVal, - "Invalid ABT_THREAD_STACKSIZE value") - } + if err != nil { + return + } + stackSizeStr, err := tc.cfg.GetEnvVar("ABT_THREAD_STACKSIZE") + if tc.expABTthreadStackSize == 0 { + test.AssertTrue(t, err != nil, "Unexpected env var ABT_THREAD_STACKSIZE") + return } + test.AssertTrue(t, err == nil, "Missing env var ABT_THREAD_STACKSIZE") + stackSizeVal, err := strconv.Atoi(stackSizeStr) + test.AssertTrue(t, err == nil, "Invalid env var ABT_THREAD_STACKSIZE") + test.AssertEqual(t, tc.expABTthreadStackSize, stackSizeVal, + "Invalid ABT_THREAD_STACKSIZE value") }) } } From 9265e2c76f58bf84197dac4b88ac467f0463b7c7 Mon Sep 17 00:00:00 2001 From: Tomasz Gromadzki Date: Thu, 12 Mar 2026 08:34:34 +0100 Subject: [PATCH 6/9] Trigger full validation Signed-off-by: Tomasz Gromadzki Priority: 2 Allow-unstable-test: true From e58b35b110b40cfdabba3918f6bb857f81192f25 Mon Sep 17 00:00:00 2001 From: Tomasz Gromadzki Date: Sat, 14 Mar 2026 23:16:45 +0100 Subject: [PATCH 7/9] Small code optimization based on review feedback. Signed-off-by: Tomasz Gromadzki Priority: 2 --- src/control/server/server.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/control/server/server.go b/src/control/server/server.go index de401670bee..a35b83be964 100644 --- a/src/control/server/server.go +++ b/src/control/server/server.go @@ -109,15 +109,11 @@ func processConfig(log logging.Logger, cfg *config.Server, fis *hardware.FabricI if err := ec.UpdateABTEnvarsUCX(); err != nil { return err } - } - for _, ec := range cfg.Engines { if err := ec.UpdatePMDKEnvars(); err != nil { return err } - } - for _, ec := range cfg.Engines { if err := ec.UpdateABTEnvarsMdOnSsd(); err != nil { return err } From d5bc34e7044f7312697c3edcf773045b4b7fc6f2 Mon Sep 17 00:00:00 2001 From: Tomasz Gromadzki Date: Mon, 16 Mar 2026 08:46:04 +0100 Subject: [PATCH 8/9] Revert "Merge remote-tracking branch 'origin/master' into grom72/DAOS-18495-ABT_THREAD_STACKSIZE-24k-md-on-ssd" This reverts commit 67ed3c3a75da4677bb533da1ce7d31dd5043a15c, reversing changes made to e58b35b110b40cfdabba3918f6bb857f81192f25. Priority: 2 Allow-unstable-test: true Signed-off-by: Tomasz Gromadzki --- .github/workflows/ossf-scorecard.yml | 2 +- .../workflows/rpm-build-and-test-report.yml | 4 +- .github/workflows/trivy.yml | 8 +- Jenkinsfile | 123 ++++++------ .../post_provision_config_common_functions.sh | 4 +- .../post_provision_config_nodes_EL.sh | 3 + ci/unit/required_packages.sh | 9 +- src/client/dfs/SConscript | 2 +- src/include/daos_srv/rsvc.h | 5 +- src/placement/jump_map_versions.c | 178 ++++++++++-------- src/pool/srv_pool.c | 17 -- src/rsvc/srv.c | 11 +- src/tests/ftest/object/create_many_dkeys.py | 104 ++++++++++ src/tests/ftest/object/create_many_dkeys.yaml | 20 ++ src/tests/ftest/pool/create.yaml | 2 +- src/tests/ftest/recovery/ddb.py | 126 +------------ src/tests/ftest/recovery/ddb_pmem.py | 88 +++++++++ src/utils/ddb/ddb_commands.c | 2 +- src/utils/ddb/ddb_parse.h | 2 +- src/utils/ddb/ddb_printer.c | 132 ++++++------- src/utils/ddb/ddb_printer.h | 17 +- src/utils/ddb/ddb_tree_path.c | 79 ++++---- src/utils/ddb/ddb_tree_path.h | 31 ++- src/utils/ddb/ddb_vos.c | 38 ++-- src/utils/ddb/ddb_vos.h | 20 +- .../ddb/tests/ddb_commands_print_tests.c | 22 +-- src/utils/ddb/tests/ddb_path_tests.c | 46 ++--- utils/cq/requirements.txt | 2 +- utils/rpms/mercury.changelog | 3 - utils/rpms/mercury.sh | 2 - utils/rpms/package_info.sh | 2 +- 31 files changed, 562 insertions(+), 542 deletions(-) create mode 100644 src/tests/ftest/object/create_many_dkeys.py create mode 100644 src/tests/ftest/object/create_many_dkeys.yaml diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 42a71b0c7cd..21822ae1038 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -71,6 +71,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 + uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 with: sarif_file: results.sarif diff --git a/.github/workflows/rpm-build-and-test-report.yml b/.github/workflows/rpm-build-and-test-report.yml index 8ce8d013d6c..aae911b31c6 100644 --- a/.github/workflows/rpm-build-and-test-report.yml +++ b/.github/workflows/rpm-build-and-test-report.yml @@ -93,7 +93,7 @@ jobs: esac echo "STAGE_NAME=Build RPM on $DISTRO_NAME $DISTRO_VERSION" >> $GITHUB_ENV - name: Test Report - uses: dorny/test-reporter@3d76b34a4535afbd0600d347b09a6ee5deb3ed7f # v2.6.0 + uses: dorny/test-reporter@b082adf0eced0765477756c2a610396589b8c637 # v2.5.0 with: artifact: ${{ env.STAGE_NAME }} test-results name: ${{ env.STAGE_NAME }} Test Results (dorny) @@ -112,7 +112,7 @@ jobs: - name: Set variables run: echo "STAGE_NAME=Functional Hardware ${{ matrix.stage }}" >> $GITHUB_ENV - name: Test Report - uses: dorny/test-reporter@3d76b34a4535afbd0600d347b09a6ee5deb3ed7f # v2.6.0 + uses: dorny/test-reporter@b082adf0eced0765477756c2a610396589b8c637 # v2.5.0 with: artifact: ${{ env.STAGE_NAME }} test-results name: ${{ env.STAGE_NAME }} Test Results (dorny) diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index e88a6ba54c5..21aa575975f 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -36,7 +36,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run Trivy vulnerability scanner in filesystem mode (table format) - uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0 + uses: aquasecurity/trivy-action@e368e328979b113139d6f9068e03accaed98a518 # 0.34.1 with: scan-type: 'fs' scan-ref: '.' @@ -61,14 +61,14 @@ jobs: sed -i 's/format: template/format: sarif/g' utils/trivy/trivy.yaml - name: Run Trivy vulnerability scanner in filesystem mode (sarif format) - uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0 + uses: aquasecurity/trivy-action@e368e328979b113139d6f9068e03accaed98a518 # 0.34.1 with: scan-type: 'fs' scan-ref: '.' trivy-config: 'utils/trivy/trivy.yaml' - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 + uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 with: sarif_file: 'trivy-results.sarif' @@ -79,7 +79,7 @@ jobs: sed -i 's/exit-code: 0/exit-code: 1/g' utils/trivy/trivy.yaml - name: Run Trivy vulnerability scanner in filesystem mode (human readable format) - uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0 + uses: aquasecurity/trivy-action@e368e328979b113139d6f9068e03accaed98a518 # 0.34.1 with: scan-type: 'fs' scan-ref: '.' diff --git a/Jenkinsfile b/Jenkinsfile index 7908e7042c0..d1b4d4abc1e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -56,7 +56,7 @@ Map nlt_test() { print 'Unstash failed, results from NLT stage will not be included' } sh label: 'Fault injection testing using NLT', - script: './ci/docker_nlt.sh --class-name fault-injection fi' + script: './ci/docker_nlt.sh --class-name el8.fault-injection fi' List filesList = [] filesList.addAll(findFiles(glob: '*.memcheck.xml')) int vgfail = 0 @@ -317,22 +317,22 @@ pipeline { description: 'Continue testing if a previous stage is Unstable') booleanParam(name: 'CI_UNIT_TEST', defaultValue: true, - description: 'Run the Unit Test test stage') + description: 'Run the Unit Test on EL 8 test stage') booleanParam(name: 'CI_NLT_TEST', defaultValue: true, description: 'Run the NLT test stage') booleanParam(name: 'CI_UNIT_TEST_MEMCHECK', defaultValue: true, - description: 'Run the Unit Test with memcheck test stage') - booleanParam(name: 'CI_FI_TEST', + description: 'Run the Unit Test with memcheck on EL 8 test stage') + booleanParam(name: 'CI_FI_el8_TEST', defaultValue: true, - description: 'Run the Fault injection testing test stage') - booleanParam(name: 'CI_TEST_EL_RPMs', + description: 'Run the Fault injection testing on EL 8 test stage') + booleanParam(name: 'CI_TEST_EL8_RPMs', defaultValue: true, - description: 'Run the Test RPMs on EL stage') - booleanParam(name: 'CI_TEST_LEAP_RPMs', + description: 'Run the Test RPMs on EL 8 test stage') + booleanParam(name: 'CI_TEST_LEAP15_RPMs', defaultValue: true, - description: 'Run the Test RPMs on Leap test stage') + description: 'Run the Test RPMs on Leap 15 test stage') booleanParam(name: 'CI_FUNCTIONAL_TEST_SKIP', defaultValue: false, description: 'Skip all functional test stages (Test)') @@ -343,17 +343,19 @@ pipeline { defaultValue: false, description: 'Run the Functional on EL 8 with Valgrind test stage') booleanParam(name: 'CI_FUNCTIONAL_el8_TEST', - defaultValue: false, + defaultValue: true, description: 'Run the Functional on EL 8 test stage') booleanParam(name: 'CI_FUNCTIONAL_el9_TEST', - defaultValue: true, + defaultValue: false, description: 'Run the Functional on EL 9 test stage') booleanParam(name: 'CI_FUNCTIONAL_leap15_TEST', defaultValue: false, - description: 'Run the Functional on Leap 15 test stage') + description: 'Run the Functional on Leap 15 test stage' + + ' Requires CI_MORE_FUNCTIONAL_PR_TESTS') booleanParam(name: 'CI_FUNCTIONAL_ubuntu20_TEST', defaultValue: false, - description: 'Run the Functional on Ubuntu 20.04 test stage') + description: 'Run the Functional on Ubuntu 20.04 test stage' + + ' Requires CI_MORE_FUNCTIONAL_PR_TESTS') booleanParam(name: 'CI_FUNCTIONAL_HARDWARE_TEST_SKIP', defaultValue: false, description: 'Skip Functional Hardware (Test Hardware) stage') @@ -527,7 +529,7 @@ pipeline { expression { !skip_build_stage() } } parallel { - stage('Build on EL 8') { + stage('Build on EL 8.8') { when { beforeAgent true expression { !skip_build_stage('el8') } @@ -542,8 +544,7 @@ pipeline { " -t ${sanitized_JOB_NAME()}-el8 " + ' --build-arg DAOS_PACKAGES_BUILD=no ' + ' --build-arg DAOS_KEEP_SRC=yes ' + - ' --build-arg REPOS="' + prRepos() + '"' + - ' --build-arg POINT_RELEASE=.10 ' + ' --build-arg REPOS="' + prRepos() + '"' } } steps { @@ -580,7 +581,7 @@ pipeline { } } } - stage('Build on EL 9') { + stage('Build on EL 9.6') { when { beforeAgent true expression { !skip_build_stage('el9') } @@ -596,7 +597,7 @@ pipeline { ' --build-arg DAOS_PACKAGES_BUILD=no ' + ' --build-arg DAOS_KEEP_SRC=yes ' + ' --build-arg REPOS="' + prRepos() + '"' + - ' --build-arg POINT_RELEASE=.7 ' + ' --build-arg POINT_RELEASE=.6 ' } } steps { @@ -633,7 +634,7 @@ pipeline { } } } - stage('Build on Leap 15') { + stage('Build on Leap 15.5') { when { beforeAgent true expression { !skip_build_stage('leap15') } @@ -683,7 +684,7 @@ pipeline { } } } - stage('Build on Leap 15 with Intel-C and TARGET_PREFIX') { + stage('Build on Leap 15.5 with Intel-C and TARGET_PREFIX') { when { beforeAgent true expression { !skip_build_stage('leap15', 'icc') } @@ -729,7 +730,7 @@ pipeline { expression { !skipStage() } } parallel { - stage('Unit Test') { + stage('Unit Test on EL 8.8') { when { beforeAgent true expression { !skipStage() } @@ -738,14 +739,11 @@ pipeline { label cachedCommitPragma(pragma: 'VM1-label', def_val: params.CI_UNIT_VM1_LABEL) } steps { - job_step_update( - unitTest(timeout_time: 60, - unstash_opt: true, - inst_repos: daosRepos(), - inst_rpms: unitPackages(target: 'el9'), - image_version: 'el9.7', - ) - ) + job_step_update( + unitTest(timeout_time: 60, + unstash_opt: true, + inst_repos: daosRepos(), + inst_rpms: unitPackages())) } post { always { @@ -754,7 +752,7 @@ pipeline { } } } - stage('Unit Test bdev') { + stage('Unit Test bdev on EL 8.8') { when { beforeAgent true expression { !skipStage() } @@ -767,8 +765,7 @@ pipeline { unitTest(timeout_time: 60, unstash_opt: true, inst_repos: daosRepos(), - inst_rpms: unitPackages(target: 'el9'), - image_version: 'el9.7')) + inst_rpms: unitPackages())) } post { always { @@ -777,7 +774,7 @@ pipeline { } } } - stage('NLT') { + stage('NLT on EL 8.8') { when { beforeAgent true expression { params.CI_NLT_TEST && !skipStage() } @@ -792,8 +789,7 @@ pipeline { test_script: 'ci/unit/test_nlt.sh', unstash_opt: true, unstash_tests: false, - inst_rpms: unitPackages(target: 'el9'), - image_version: 'el9.7')) + inst_rpms: unitPackages())) // recordCoverage(tools: [[parser: 'COBERTURA', pattern:'nltir.xml']], // skipPublishingChecks: true, // id: 'tlc', name: 'Fault Injection Interim Report') @@ -804,7 +800,7 @@ pipeline { unitTestPost artifacts: ['nlt_logs/'], testResults: 'nlt-junit.xml', always_script: 'ci/unit/test_nlt_post.sh', - valgrind_stash: 'nlt-memcheck' + valgrind_stash: 'el8-gcc-nlt-memcheck' recordIssues enabledForFailure: true, failOnError: false, ignoreQualityGate: true, @@ -818,7 +814,7 @@ pipeline { } } } - stage('Unit Test with memcheck') { + stage('Unit Test with memcheck on EL 8.8') { when { beforeAgent true expression { !skipStage() } @@ -832,19 +828,18 @@ pipeline { unstash_opt: true, ignore_failure: true, inst_repos: daosRepos(), - inst_rpms: unitPackages(target: 'el9'), - image_version: 'el9.7')) + inst_rpms: unitPackages())) } post { always { unitTestPost artifacts: ['unit_test_memcheck_logs.tar.gz', 'unit_test_memcheck_logs/**/*.log'], - valgrind_stash: 'unit-memcheck' + valgrind_stash: 'el8-gcc-unit-memcheck' job_status_update() } } - } // stage('Unit Test with memcheck') - stage('Unit Test bdev with memcheck') { + } // stage('Unit Test with memcheck on EL 8.8') + stage('Unit Test bdev with memcheck on EL 8.8') { when { beforeAgent true expression { !skipStage() } @@ -858,18 +853,17 @@ pipeline { unstash_opt: true, ignore_failure: true, inst_repos: daosRepos(), - inst_rpms: unitPackages(target: 'el9'), - image_version: 'el9.7')) + inst_rpms: unitPackages())) } post { always { unitTestPost artifacts: ['unit_test_memcheck_bdev_logs.tar.gz', 'unit_test_memcheck_bdev_logs/**/*.log'], - valgrind_stash: 'unit-bdev-memcheck' + valgrind_stash: 'el8-gcc-unit-memcheck-bdev' job_status_update() } } - } // stage('Unit Test bdev with memcheck') + } // stage('Unit Test bdev with memcheck on EL 8') } } stage('Test') { @@ -903,7 +897,7 @@ pipeline { } } } // stage('Functional on EL 8.8 with Valgrind') - stage('Functional on EL 8') { + stage('Functional on EL 8.8') { when { beforeAgent true expression { !skipStage() } @@ -917,8 +911,7 @@ pipeline { inst_repos: daosRepos(), inst_rpms: functionalPackages(1, next_version(), 'tests-internal') + ' mercury-libfabric', - test_function: 'runTestFunctionalV2', - image_version: 'el8.10')) + test_function: 'runTestFunctionalV2')) } post { always { @@ -926,7 +919,7 @@ pipeline { job_status_update() } } - } // stage('Functional on EL 8') + } // stage('Functional on EL 8.8') stage('Functional on EL 9') { when { beforeAgent true @@ -941,8 +934,7 @@ pipeline { inst_repos: daosRepos(), inst_rpms: functionalPackages(1, next_version(), 'tests-internal') + ' mercury-libfabric', - test_function: 'runTestFunctionalV2', - image_version: 'el9.7')) + test_function: 'runTestFunctionalV2')) } post { always { @@ -951,7 +943,7 @@ pipeline { } } } // stage('Functional on EL 9') - stage('Functional on Leap 15') { + stage('Functional on Leap 15.6') { when { beforeAgent true expression { !skipStage() } @@ -974,7 +966,7 @@ pipeline { job_status_update() } } // post - } // stage('Functional on Leap 15') + } // stage('Functional on Leap 15.6') stage('Functional on Ubuntu 20.04') { when { beforeAgent true @@ -998,19 +990,18 @@ pipeline { } } // post } // stage('Functional on Ubuntu 20.04') - stage('Fault injection testing') { + stage('Fault injection testing on EL 8.8') { when { beforeAgent true expression { !skipStage() } } agent { dockerfile { - filename 'utils/docker/Dockerfile.el.9' + filename 'utils/docker/Dockerfile.el.8' label 'docker_runner' additionalBuildArgs dockerBuildArgs(repo_type: 'stable', parallel_build: true, - deps_build: true) + - ' --build-arg POINT_RELEASE=.7 ' + deps_build: true) args '--tmpfs /mnt/daos_0' } } @@ -1049,16 +1040,16 @@ pipeline { stash name: 'fault-inject-valgrind', includes: '*.memcheck.xml', allowEmpty: true - archiveArtifacts artifacts: 'nlt_logs/fault-injection/', + archiveArtifacts artifacts: 'nlt_logs/el8.fault-injection/', allowEmptyArchive: true job_status_update() } } - } // stage('Fault injection testing') - stage('Test RPMs on EL 9.6') { + } // stage('Fault injection testing on EL 8.8') + stage('Test RPMs on EL 8.6') { when { beforeAgent true - expression { params.CI_TEST_EL_RPMs && !skipStage() } + expression { params.CI_TEST_EL8_RPMs && !skipStage() } } agent { label params.CI_UNIT_VM1_LABEL @@ -1075,11 +1066,11 @@ pipeline { rpm_test_post(env.STAGE_NAME, env.NODELIST) } } - } // stage('Test RPMs on EL 9.6') + } // stage('Test RPMs on EL 8.6') stage('Test RPMs on Leap 15.5') { when { beforeAgent true - expression { params.CI_TEST_LEAP_RPMs && !skipStage() } + expression { params.CI_TEST_LEAP15_RPMs && !skipStage() } } agent { label params.CI_UNIT_VM1_LABEL @@ -1268,8 +1259,8 @@ pipeline { } // stages post { always { - valgrindReportPublish valgrind_stashes: ['nlt-memcheck', - 'unit-memcheck', + valgrindReportPublish valgrind_stashes: ['el8-gcc-nlt-memcheck', + 'el8-gcc-unit-memcheck', 'fault-inject-valgrind'] job_status_update('final_status') jobStatusWrite(job_status_internal) diff --git a/ci/provisioning/post_provision_config_common_functions.sh b/ci/provisioning/post_provision_config_common_functions.sh index 46fba4b21c2..5c5e2a50fbd 100755 --- a/ci/provisioning/post_provision_config_common_functions.sh +++ b/ci/provisioning/post_provision_config_common_functions.sh @@ -100,9 +100,7 @@ retry_dnf() { if command -v dnf4; then dnfx="dnf4" fi - if "$dnfx" repolist | grep -q '^epel'; then - "$dnfx" config-manager --disable 'epel*' - fi + "$dnfx" config-manager --disable 'epel*' || true fi return 0 fi diff --git a/ci/provisioning/post_provision_config_nodes_EL.sh b/ci/provisioning/post_provision_config_nodes_EL.sh index d424ef5ccad..c9257d87c22 100644 --- a/ci/provisioning/post_provision_config_nodes_EL.sh +++ b/ci/provisioning/post_provision_config_nodes_EL.sh @@ -28,5 +28,8 @@ group_repo_post() { distro_custom() { # TODO: This code is not exiting on failure. + # Use a more recent python version for unit testing, this allows us to also test installing + # pydaos into virtual environments. + dnf -y install python39 python39-devel dnf -y install python3.11 python3.11-devel } diff --git a/ci/unit/required_packages.sh b/ci/unit/required_packages.sh index 4790812779c..5a57b0cb054 100755 --- a/ci/unit/required_packages.sh +++ b/ci/unit/required_packages.sh @@ -7,17 +7,16 @@ # set -eu -# Provided by pipeline-lib +# No longer used but provided by pipeline-lib # distro="$1" # quick_build="${2:-false}" OPENMPI_VER="" PY_MINOR_VER="" -DISTRO="${1:?ERROR: Missing distro argument. Usage: $0 }" -export DISTRO="${DISTRO%%.*}" - -pkgs="boost-python3$PY_MINOR_VER-devel \ +export DISTRO="el8" # should also work for el9 +pkgs="$(utils/rpms/package_version.sh argobots lib) \ + boost-python3$PY_MINOR_VER-devel \ capstone \ $(utils/rpms/package_version.sh argobots lib) \ $(utils/rpms/package_version.sh argobots debug) \ diff --git a/src/client/dfs/SConscript b/src/client/dfs/SConscript index 906b1bfd7d4..b2526c5589a 100644 --- a/src/client/dfs/SConscript +++ b/src/client/dfs/SConscript @@ -18,7 +18,7 @@ def configure_lustre(denv): _print("No installed Lustre version detected") else: _print("Installed Lustre version detected") - if not conf.CheckFunc('llapi_unlink_foreign'): + if not denv.CheckFunc('llapi_unlink_foreign'): _print("Lustre version is not compatible") else: _print("Lustre version is compatible") diff --git a/src/include/daos_srv/rsvc.h b/src/include/daos_srv/rsvc.h index d2105222380..7f66d66b329 100644 --- a/src/include/daos_srv/rsvc.h +++ b/src/include/daos_srv/rsvc.h @@ -1,6 +1,6 @@ /* * (C) Copyright 2019-2024 Intel Corporation. - * (C) Copyright 2025-2026 Hewlett Packard Enterprise Development LP + * (C) Copyright 2025 Hewlett Packard Enterprise Development LP * * SPDX-License-Identifier: BSD-2-Clause-Patent */ @@ -53,9 +53,6 @@ struct ds_rsvc_class { */ void (*sc_free)(struct ds_rsvc *svc); - /** Prepare for being inserted into the hash table. */ - int (*sc_insert)(struct ds_rsvc *svc); - /** * Bootstrap (i.e., initialize) the DB with the argument passed to * ds_rsvc_start. If supplied, this is called on a self-only service. diff --git a/src/placement/jump_map_versions.c b/src/placement/jump_map_versions.c index 2480ac4893c..f817ed96b04 100644 --- a/src/placement/jump_map_versions.c +++ b/src/placement/jump_map_versions.c @@ -1,7 +1,7 @@ /** * * (C) Copyright 2016-2024 Intel Corporation. - * (C) Copyright 2025-2026 Hewlett Packard Enterprise Development LP + * (C) Copyright 2025 Hewlett Packard Enterprise Development LP * * SPDX-License-Identifier: BSD-2-Clause-Patent */ @@ -15,11 +15,14 @@ #include #include -/* NB: this function checks if the component should be skipped in jump hash layout generation - * process, those components which are in NEW status or being added afterwards should be skipped. +/* NB: this function is to check if the component should be + * included in jump hash layout generation process, so those + * targets, which are in NEW status or being added afterwards + * should be excluded. */ static bool -comp_is_skipped(struct pool_component *comp, uint32_t allow_version, enum layout_gen_mode gen_mode) +is_excluded_comp(struct pool_component *comp, uint32_t allow_version, + enum layout_gen_mode gen_mode) { if (comp->co_status == PO_COMP_ST_NEW) return true; @@ -33,20 +36,13 @@ comp_is_skipped(struct pool_component *comp, uint32_t allow_version, enum layout if (gen_mode != POST_REBUILD) return true; } + return false; } -#define dom_is_skipped(dom, allow_ver, gen_mode) \ - comp_is_skipped(&(dom)->do_comp, allow_ver, gen_mode) -#define tgt_is_skipped(tgt, allow_ver, gen_mode) \ - comp_is_skipped(&(tgt)->ta_comp, allow_ver, gen_mode) - -/* TODO: shouldn't compute each time, it can be very slow for extension of large pool. - * This should be optimized in the future. - */ static inline uint32_t -dom_avail_children(struct pool_domain *curr_dom, uint32_t allow_version, - enum layout_gen_mode gen_mode, pool_comp_type_t fdom_lvl) +get_num_domains(struct pool_domain *curr_dom, uint32_t allow_version, + enum layout_gen_mode gen_mode, pool_comp_type_t fdom_lvl) { struct pool_domain *next_dom; struct pool_target *next_target; @@ -58,20 +54,19 @@ dom_avail_children(struct pool_domain *curr_dom, uint32_t allow_version, num_dom = curr_dom->do_child_nr; D_ASSERTF(num_dom > 0, "num dom %u\n", num_dom); - /* new children(domains/targets) are always appended to old children of the same parent, - * this is why it does backward search. - */ if (curr_dom->do_children == NULL || curr_dom->do_comp.co_type == fdom_lvl) { next_target = &curr_dom->do_targets[num_dom - 1]; - while (num_dom - 1 > 0 && tgt_is_skipped(next_target, allow_version, gen_mode)) { + while (num_dom - 1 > 0 && is_excluded_comp(&next_target->ta_comp, allow_version, + gen_mode)) { num_dom--; next_target = &curr_dom->do_targets[num_dom - 1]; } } else { next_dom = &curr_dom->do_children[num_dom - 1]; - while (num_dom - 1 > 0 && dom_is_skipped(next_dom, allow_version, gen_mode)) { + while (num_dom - 1 > 0 && is_excluded_comp(&next_dom->do_comp, allow_version, + gen_mode)) { num_dom--; next_dom = &curr_dom->do_children[num_dom - 1]; } @@ -86,17 +81,13 @@ tgt_isset_range(struct pool_target *tgts, uint8_t *tgts_used, uint32_t start_tgt { uint32_t index; - for (index = start_tgt; index <= end_tgt;) { - if (tgts_used[index >> 3] == 0xFF) { - index = (index | 7) + 1; /* jump to start of next byte */ + for (index = start_tgt; index <= end_tgt; ++index) { + if (is_excluded_comp(&tgts[index].ta_comp, allow_version, gen_mode)) continue; - } - - if (isclr(tgts_used, index) && - !tgt_is_skipped(&tgts[index], allow_version, gen_mode)) + if (isclr(tgts_used, index)) return false; - ++index; } + return true; } @@ -106,17 +97,13 @@ dom_isset_range(struct pool_domain *doms, uint8_t *doms_bits, uint32_t start_dom { uint32_t index; - for (index = start_dom; index <= end_dom;) { - if (doms_bits[index >> 3] == 0xFF) { - index = (index | 7) + 1; /* jump to start of next byte */ + for (index = start_dom; index <= end_dom; ++index) { + if (is_excluded_comp(&doms[index].do_comp, allow_version, gen_mode)) continue; - } - - if (isclr(doms_bits, index) && - !dom_is_skipped(&doms[index], allow_version, gen_mode)) + if (isclr(doms_bits, index)) return false; - ++index; } + return true; } @@ -127,22 +114,19 @@ dom_isset_2ranges(struct pool_domain *doms, uint8_t *doms_bits1, uint8_t *doms_b { uint32_t index; - for (index = start_dom; index <= end_dom;) { - if (doms_bits1[index >> 3] == 0xFF || doms_bits2[index >> 3] == 0xFF) { - index = (index | 7) + 1; /* jump to start of next byte */ + for (index = start_dom; index <= end_dom; ++index) { + if (is_excluded_comp(&doms[index].do_comp, allow_version, gen_mode)) continue; - } - if (isclr(doms_bits1, index) && isclr(doms_bits2, index) && - !dom_is_skipped(&doms[index], allow_version, gen_mode)) + if (isclr(doms_bits1, index) && isclr(doms_bits2, index)) return false; - ++index; } + return true; } static bool -dom_is_full(struct pool_domain *dom, struct pool_domain *root, uint8_t *dom_full, +is_dom_full(struct pool_domain *dom, struct pool_domain *root, uint8_t *dom_full, uint32_t allow_version, enum layout_gen_mode gen_mode) { uint32_t start_dom = dom->do_children - root; @@ -154,6 +138,40 @@ dom_is_full(struct pool_domain *dom, struct pool_domain *root, uint8_t *dom_full return false; } +struct pool_target * +_get_target(struct pool_domain *doms, uint32_t tgt_idx, uint32_t allow_version, + enum layout_gen_mode gen_mode) +{ + uint32_t idx = 0; + uint32_t i; + + for (i = 0; i < doms->do_target_nr; i++) { + if (!is_excluded_comp(&doms->do_targets[i].ta_comp, allow_version, gen_mode)) { + if (idx == tgt_idx) + return &doms->do_targets[idx]; + idx++; + } + } + return NULL; +} + +struct pool_domain * +_get_dom(struct pool_domain *doms, uint32_t dom_idx, uint32_t allow_version, + enum layout_gen_mode gen_mode) +{ + uint32_t idx = 0; + uint32_t i; + + for (i = 0; i < doms->do_child_nr; i++) { + if (!is_excluded_comp(&doms->do_children[i].do_comp, allow_version, gen_mode)) { + if (idx == dom_idx) + return &doms->do_children[idx]; + idx++; + } + } + return NULL; +} + /** * This function recursively chooses a single target to be used in the * object shard layout. This function is called for every shard that needs a @@ -204,16 +222,15 @@ __get_target_v1(struct pool_domain *root_pos, struct pool_domain *curr_pd, uint8_t found_target = 0; struct pool_domain *curr_dom; struct pool_domain *dom_stack[MAX_STACK] = { 0 }; - struct pool_target *tgt; int top = -1; obj_key = crc(obj_key, shard_num); curr_dom = curr_pd; do { - uint32_t children; /* sub-domains or targets */ + uint32_t avail_doms; /* Retrieve number of nodes in this domain */ - children = dom_avail_children(curr_dom, allow_version, gen_mode, fdom_lvl); + avail_doms = get_num_domains(curr_dom, allow_version, gen_mode, fdom_lvl); /* If choosing target (lowest fault domain level) */ if (curr_dom->do_comp.co_type == fdom_lvl) { @@ -242,15 +259,16 @@ __get_target_v1(struct pool_domain *root_pos, struct pool_domain *curr_pd, */ obj_key = crc(obj_key, fail_num++); /* Get target for shard */ - selected_tgt = d_hash_jump(obj_key, children); + selected_tgt = d_hash_jump(obj_key, avail_doms); do { - selected_tgt = selected_tgt % children; - tgt = &curr_dom->do_targets[selected_tgt]; + selected_tgt = selected_tgt % avail_doms; + /* Retrieve actual target using index */ + *target = _get_target(curr_dom, selected_tgt, allow_version, + gen_mode); /* Get target id to check if target used */ - tgt_idx = tgt - root_pos->do_targets; + tgt_idx = *target - root_pos->do_targets; selected_tgt++; } while (isset(tgts_used, tgt_idx)); - *target = tgt; setbit(tgts_used, tgt_idx); D_DEBUG(DB_PL, "selected tgt %d\n", tgt_idx); @@ -264,7 +282,7 @@ __get_target_v1(struct pool_domain *root_pos, struct pool_domain *curr_pd, D_DEBUG(DB_PL, "dom %d used up\n", (int)(curr_dom - root_pos)); /* Check and set if all of its parent are full */ while(top != -1) { - if (dom_is_full(dom_stack[top], root_pos, dom_full, + if (is_dom_full(dom_stack[top], root_pos, dom_full, allow_version, gen_mode)) { uint32_t off = dom_stack[top] - root_pos; @@ -291,8 +309,8 @@ __get_target_v1(struct pool_domain *root_pos, struct pool_domain *curr_pd, /* Check if all targets under the domain range has been * used up (occupied), go back to its parent if it does. */ - range_set = - dom_is_full(curr_dom, root_pos, dom_full, allow_version, gen_mode); + range_set = is_dom_full(curr_dom, root_pos, dom_full, allow_version, + gen_mode); if (range_set) { if (top == -1) { if (curr_pd != root_pos) { @@ -370,8 +388,13 @@ __get_target_v1(struct pool_domain *root_pos, struct pool_domain *curr_pd, /* Keep choosing the new domain until the one has not been used. */ do { - selected_dom = d_hash_jump(key, children); - key = crc(key, fail_num++); + struct pool_domain *_dom; + + selected_dom = d_hash_jump(key, avail_doms); + key = crc(key, fail_num++); + _dom = _get_dom(curr_dom, selected_dom, allow_version, gen_mode); + if (_dom != NULL) + selected_dom = _dom - curr_dom->do_children; } while (isset(dom_used, start_dom + selected_dom) || isset(dom_cur_grp_used, start_dom + selected_dom)); @@ -407,17 +430,17 @@ __get_target_v2(struct pool_domain *root_pos, struct pool_domain *curr_pd, { int range_set; uint8_t found_target = 0; - struct pool_target *tgt; struct pool_domain *curr_dom; struct pool_domain *dom_stack[MAX_STACK] = {0}; int top = -1; curr_dom = curr_pd; do { - uint32_t children; /* sub-domains or targets */ + uint32_t avail_doms; uint64_t key; - children = dom_avail_children(curr_dom, allow_version, gen_mode, fdom_lvl); + /* Retrieve number of nodes in this domain */ + avail_doms = get_num_domains(curr_dom, allow_version, gen_mode, fdom_lvl); /* If choosing target (lowest fault domain level) */ if (curr_dom->do_comp.co_type == fdom_lvl) { @@ -446,15 +469,16 @@ __get_target_v2(struct pool_domain *root_pos, struct pool_domain *curr_pd, */ key = jm_crc(obj_key, shard_num, fail_num++); /* Get target for shard */ - selected_tgt = d_hash_jump(key, children); + selected_tgt = d_hash_jump(key, avail_doms); do { - selected_tgt = selected_tgt % children; - tgt = &curr_dom->do_targets[selected_tgt]; + selected_tgt = selected_tgt % avail_doms; + /* Retrieve actual target using index */ + *target = + _get_target(curr_dom, selected_tgt, allow_version, gen_mode); /* Get target id to check if target used */ - tgt_idx = tgt - root_pos->do_targets; + tgt_idx = *target - root_pos->do_targets; selected_tgt++; } while (isset(tgts_used, tgt_idx)); - *target = tgt; setbit(tgts_used, tgt_idx); D_DEBUG(DB_PL, "selected tgt %d\n", tgt_idx); @@ -468,7 +492,7 @@ __get_target_v2(struct pool_domain *root_pos, struct pool_domain *curr_pd, D_DEBUG(DB_PL, "dom %d used up\n", (int)(curr_dom - root_pos)); /* Check and set if all of its parent are full */ while (top != -1) { - if (dom_is_full(dom_stack[top], root_pos, dom_full, + if (is_dom_full(dom_stack[top], root_pos, dom_full, allow_version, gen_mode)) { uint32_t off = dom_stack[top] - root_pos; @@ -495,7 +519,7 @@ __get_target_v2(struct pool_domain *root_pos, struct pool_domain *curr_pd, * used up (occupied), go back to its parent if it does. */ range_set = - dom_is_full(curr_dom, root_pos, dom_full, allow_version, gen_mode); + is_dom_full(curr_dom, root_pos, dom_full, allow_version, gen_mode); if (range_set) { if (top == -1) { if (curr_pd != root_pos) { @@ -574,8 +598,13 @@ __get_target_v2(struct pool_domain *root_pos, struct pool_domain *curr_pd, /* Keep choosing the new domain until the one has not been used. */ do { + struct pool_domain *_dom; + key = jm_crc(obj_key, shard_num, fail_num++); - selected_dom = d_hash_jump(key, children); + selected_dom = d_hash_jump(key, avail_doms); + _dom = _get_dom(curr_dom, selected_dom, allow_version, gen_mode); + if (_dom != NULL) + selected_dom = _dom - curr_dom->do_children; } while (isset(dom_used, start_dom + selected_dom) || isset(dom_cur_grp_used, start_dom + selected_dom)); @@ -897,9 +926,10 @@ get_target_v0(struct pool_domain *curr_dom, struct pool_target **target, dom_size = (struct pool_domain *)(root_pos->do_targets) - (root_pos) + 1; retry: do { - uint32_t children; /* sub-components or targets */ + uint32_t num_doms; - children = dom_avail_children(curr_dom, allow_version, gen_mode, fdom_lvl); + /* Retrieve number of nodes in this domain */ + num_doms = get_num_domains(curr_dom, allow_version, gen_mode, fdom_lvl); /* If choosing target (lowest fault domain level) */ if (curr_dom->do_children == NULL || curr_dom->do_comp.co_type == fdom_lvl) { @@ -909,7 +939,7 @@ get_target_v0(struct pool_domain *curr_dom, struct pool_target **target, uint32_t end_tgt; start_tgt = curr_dom->do_targets - root_pos->do_targets; - end_tgt = start_tgt + (children - 1); + end_tgt = start_tgt + (num_doms - 1); range_set = isset_range(tgts_used, start_tgt, end_tgt); if (range_set) { @@ -926,9 +956,9 @@ get_target_v0(struct pool_domain *curr_dom, struct pool_target **target, */ obj_key = crc(obj_key, fail_num++); /* Get target for shard */ - selected_dom = d_hash_jump(obj_key, children); + selected_dom = d_hash_jump(obj_key, num_doms); do { - selected_dom = selected_dom % children; + selected_dom = selected_dom % num_doms; /* Retrieve actual target using index */ *target = &curr_dom->do_targets[selected_dom]; /* Get target id to check if target used */ @@ -957,7 +987,7 @@ get_target_v0(struct pool_domain *curr_dom, struct pool_target **target, key = obj_key; start_dom = (curr_dom->do_children) - root_pos; - end_dom = start_dom + (children - 1); + end_dom = start_dom + (num_doms - 1); /* Check if all targets under the domain range has been * used up (occupied), go back to its parent if it does. @@ -1046,7 +1076,7 @@ get_target_v0(struct pool_domain *curr_dom, struct pool_target **target, * not been used is found */ do { - selected_dom = d_hash_jump(key, children); + selected_dom = d_hash_jump(key, num_doms); key = crc(key, fail_num++); } while (isset(dom_used, start_dom + selected_dom)); diff --git a/src/pool/srv_pool.c b/src/pool/srv_pool.c index c2a59cec0a9..b7ada57c251 100644 --- a/src/pool/srv_pool.c +++ b/src/pool/srv_pool.c @@ -1910,20 +1910,6 @@ pool_svc_free_cb(struct ds_rsvc *rsvc) D_FREE(svc); } -static int -pool_svc_insert_cb(struct ds_rsvc *rsvc) -{ - struct pool_svc *svc = pool_svc_obj(rsvc); - - /* - * While we were starting svc, there might be a ds_pool_stop call who - * is waiting for us to put svc->ps_pool. - */ - if (svc->ps_pool->sp_stopping) - return -DER_CANCELED; - return 0; -} - /* * Update svc->ps_pool with map_buf and map_version. This ensures that * svc->ps_pool matches the latest pool map. @@ -2741,19 +2727,16 @@ pool_svc_map_dist_cb(struct ds_rsvc *rsvc, uint32_t *version) return rc; } -/* clang-format off */ static struct ds_rsvc_class pool_svc_rsvc_class = { .sc_name = pool_svc_name_cb, .sc_locate = pool_svc_locate_cb, .sc_alloc = pool_svc_alloc_cb, .sc_free = pool_svc_free_cb, - .sc_insert = pool_svc_insert_cb, .sc_step_up = pool_svc_step_up_cb, .sc_step_down = pool_svc_step_down_cb, .sc_drain = pool_svc_drain_cb, .sc_map_dist = pool_svc_map_dist_cb }; -/* clang-format on */ void ds_pool_rsvc_class_register(void) diff --git a/src/rsvc/srv.c b/src/rsvc/srv.c index 6cedab28ba8..3f2b599eb2b 100644 --- a/src/rsvc/srv.c +++ b/src/rsvc/srv.c @@ -1,6 +1,6 @@ /* * (C) Copyright 2019-2024 Intel Corporation. - * (C) Copyright 2025-2026 Hewlett Packard Enterprise Development LP + * (C) Copyright 2025 Hewlett Packard Enterprise Development LP * * SPDX-License-Identifier: BSD-2-Clause-Patent */ @@ -1042,19 +1042,10 @@ ds_rsvc_start(enum ds_rsvc_class_id class, d_iov_t *id, uuid_t db_uuid, uint64_t if (rc != 0) goto out; - if (rsvc_class(class)->sc_insert != NULL) { - rc = rsvc_class(class)->sc_insert(svc); - if (rc != 0) { - D_DEBUG(DB_MD, "%s: sc_insert: " DF_RC "\n", svc->s_name, DP_RC(rc)); - goto err_svc_started; - } - } - rc = d_hash_rec_insert(&rsvc_hash, svc->s_id.iov_buf, svc->s_id.iov_len, &svc->s_entry, true /* exclusive */); if (rc != 0) { D_DEBUG(DB_MD, "%s: insert: "DF_RC"\n", svc->s_name, DP_RC(rc)); -err_svc_started: stop(svc, mode == DS_RSVC_CREATE /* destroy */); goto out; } diff --git a/src/tests/ftest/object/create_many_dkeys.py b/src/tests/ftest/object/create_many_dkeys.py new file mode 100644 index 00000000000..47f74017b9e --- /dev/null +++ b/src/tests/ftest/object/create_many_dkeys.py @@ -0,0 +1,104 @@ +''' + (C) Copyright 2018-2023 Intel Corporation. + + SPDX-License-Identifier: BSD-2-Clause-Patent +''' +import ctypes +import sys + +import avocado +from apricot import TestWithServers +from general_utils import create_string_buffer +from pydaos.raw import DaosApiError, IORequest +from test_utils_container import add_container +from test_utils_pool import add_pool + + +class CreateManyDkeys(TestWithServers): + """ + Test Class Description: + Tests that create large numbers of keys in objects/containers and then + destroy the containers and verify the space has been reclaimed. + + :avocado: recursive + """ + + def write_a_bunch_of_values(self, pool, how_many): + """Write data to an object, each with a dkey and akey. + + Args: + pool (TestPool): the pool in which to write data + how_many (int): how many key:value pairs are written + """ + self.log_step("Creating a container") + container = add_container(self, pool) + container.open() + + ioreq = IORequest(self.context, container.container, None) + + self.log_step("Writing the dataset") + inc = 50000 + last_key = inc + for key in range(how_many): + c_dkey = create_string_buffer("dkey {0}".format(key)) + c_akey = create_string_buffer("akey {0}".format(key)) + c_value = create_string_buffer( + "some data that gets stored with the key {0}".format(key)) + c_size = ctypes.c_size_t(ctypes.sizeof(c_value)) + ioreq.single_insert(c_dkey, + c_akey, + c_value, + c_size) + + if key > last_key: + print("written: {}".format(key)) + sys.stdout.flush() + last_key = key + inc + + self.log_step("Verifying the dataset") + last_key = inc + for key in range(how_many): + c_dkey = create_string_buffer("dkey {0}".format(key)) + c_akey = create_string_buffer("akey {0}".format(key)) + the_data = "some data that gets stored with the key {0}".format(key) + val = ioreq.single_fetch(c_dkey, c_akey, len(the_data) + 1) + exp_value = val.value.decode("utf-8") + if the_data != exp_value: + self.log.debug("Expected Value: %s", the_data) + self.log.debug("Received Value: %s", exp_value) + self.fail("ERROR: Data mismatch for dkey='dkey {key}', akey='akey {key}'") + + if key > last_key: + print("verified: {}".format(key)) + sys.stdout.flush() + last_key = key + inc + + self.log_step("Destroying the container") + container.destroy() + + @avocado.fail_on(DaosApiError) + def test_many_dkeys(self): + """ + Test ID: DAOS-1701 + Test Description: Test many of dkeys in same object. + Use Cases: 1. large key counts + 2. space reclamation after destroy + + :avocado: tags=all,full_regression + :avocado: tags=vm + :avocado: tags=object + :avocado: tags=CreateManyDkeys,test_many_dkeys + """ + no_of_dkeys = self.params.get("number_of_dkeys", '/run/dkeys/') + + self.log_step("Creating a pool") + pool = add_pool(self) + + # write a lot of individual data items, verify them, then destroy + self.write_a_bunch_of_values(pool, no_of_dkeys) + + # do it again, which should verify the first container + # was truly destroyed because a second round won't fit otherwise + self.write_a_bunch_of_values(pool, no_of_dkeys) + + self.log.info('Test passed') diff --git a/src/tests/ftest/object/create_many_dkeys.yaml b/src/tests/ftest/object/create_many_dkeys.yaml new file mode 100644 index 00000000000..f9206d654f8 --- /dev/null +++ b/src/tests/ftest/object/create_many_dkeys.yaml @@ -0,0 +1,20 @@ +hosts: + test_servers: 2 + test_clients: 1 +timeout: 3600 +server_config: + name: daos_server + engines_per_host: 1 + engines: + 0: + targets: 4 + nr_xs_helpers: 0 + storage: + 0: + class: ram + scm_mount: /mnt/daos + system_ram_reserved: 1 +pool: + scm_size: 3G +dkeys: + number_of_dkeys: 1000000 diff --git a/src/tests/ftest/pool/create.yaml b/src/tests/ftest/pool/create.yaml index 004f750b040..87a5a385fab 100644 --- a/src/tests/ftest/pool/create.yaml +++ b/src/tests/ftest/pool/create.yaml @@ -6,7 +6,7 @@ timeouts: test_create_max_pool_scm_only: 180 test_create_max_pool: 300 test_create_no_space: 300 - test_create_no_space_loop: 4100 + test_create_no_space_loop: 3700 server_config: name: daos_server diff --git a/src/tests/ftest/recovery/ddb.py b/src/tests/ftest/recovery/ddb.py index 1ca71e71091..21ac91230b4 100644 --- a/src/tests/ftest/recovery/ddb.py +++ b/src/tests/ftest/recovery/ddb.py @@ -10,8 +10,6 @@ from apricot import TestWithServers from ddb_utils import DdbCommand -from exception_utils import CommandFailure -from file_utils import distribute_files from general_utils import create_string_buffer, get_random_string, report_errors from pydaos.raw import DaosObjClass, IORequest from run_utils import command_as_user, run_remote @@ -390,7 +388,7 @@ def test_recovery_ddb_rm(self): self.log_step("Call ddb rm to remove the akey.") if md_on_ssd: # "ddb rm" command for MD-on-SSD is quite different. - # PMEM: ddb -w /mnt/daos//vos-0 rm + # PMEM: ddb /mnt/daos//vos-0 rm # MD-on-SSD: ddb -w --db_path=/var/tmp/daos_testing/control_metadata/daos_control # /engine0 --vos_path /mnt/daos_load//vos-0 rm ddb_command.db_path.update(value=" ".join(["--db_path", db_path])) @@ -486,125 +484,3 @@ def test_recovery_ddb_rm(self): self.run_cmd_check_result(command=f"rm -rf {daos_load_path}") report_errors(test=self, errors=errors) - - def test_recovery_ddb_load(self): - """Test ddb value_load. - - 1. Create a pool and a container. - 2. Insert one object with one dkey with the API. - 3. Stop the server to use ddb. - 4. Find the vos file name. e.g., /mnt/daos0//vos-0. - 5. Load new data into [0]/[0]/[0]/[0] - 6. Restart the server. - 7. Reset the object, container, and pool to use the API. - 8. Verify the data in the akey with single_fetch(). - - :avocado: tags=all,full_regression - :avocado: tags=hw,medium - :avocado: tags=recovery - :avocado: tags=DdbTest,ddb_cmd,test_recovery_ddb_load - """ - # This is where we load pool for MD-on-SSD. It's called tmpfs_mount in ddb prov_mem - # documentation, but use daos_load_path here for clarity. - daos_load_path = "/mnt/daos_load" - md_on_ssd = self.server_managers[0].manager.job.using_control_metadata - if md_on_ssd: - self.log_step("MD-on-SSD: Create a directory to load pool data under /mnt.") - self.run_cmd_check_result(command=f"mkdir {daos_load_path}") - - self.log_step("Create a pool and a container.") - pool = self.get_pool(connect=True) - container = self.get_container(pool) - - if md_on_ssd: - vos_path = '""' - else: - # Find the vos file name. e.g., /mnt/daos0//vos-0. - vos_paths = self.server_managers[0].get_vos_files(pool) - if not vos_paths: - self.fail("vos file wasn't found!") - vos_path = vos_paths[0] - - ddb_command = DdbCommand( - server_host=self.server_managers[0].hosts[0], path=self.bin, vos_path=vos_path) - - self.log_step("Insert one object with one dkey with API.") - obj_dataset = insert_objects( - context=self.context, container=container, object_count=1, dkey_count=1, akey_count=1, - base_dkey=self.random_dkey, base_akey=self.random_akey, base_data=self.random_data) - ioreqs = obj_dataset[0] - dkeys_inserted = obj_dataset[1] - akeys_inserted = obj_dataset[2] - data_list = obj_dataset[3] - - # For debugging/reference, call single_fetch and get the data just inserted. - # Pass in size + 1 to single_fetch to avoid the no-space error. - data_size = len(data_list[0]) + 1 - data = ioreqs[0].single_fetch( - dkey=dkeys_inserted[0], akey=akeys_inserted[0], size=data_size) - self.log.info("data (before) = %s", data.value.decode('utf-8')) - - self.log_step("Stop the server to use ddb.") - dmg_command = self.get_dmg_command() - dmg_command.system_stop() - - db_path = None - if md_on_ssd: - self.log_step(f"MD-on-SSD: Load pool dir to {daos_load_path}") - db_path = os.path.join( - self.server_managers[0].manager.job.yaml.metadata_params.path.value, "daos_control", - "engine0") - ddb_command.prov_mem(db_path=db_path, tmpfs_mount=daos_load_path) - - self.log_step("Load new data into [0]/[0]/[0]/[0]; Create a file in test node.") - load_file_path = os.path.join(self.test_dir, "new_data.txt") - new_data = get_random_string(20) - with open(load_file_path, "w", encoding="utf-8") as file: - file.write(new_data) - - self.log_step("Copy the created file to server node.") - result = distribute_files( - self.log, self.server_managers[0].hosts[0], load_file_path, load_file_path, False) - if not result.passed: - raise CommandFailure(f"ERROR: Copying new_data.txt to {result.failed_hosts}") - - self.log_step("The file with the new data is ready. Run ddb load.") - if md_on_ssd: - # "ddb value_load" command for MD-on-SSD is quite different. - # PMEM: ddb -w /mnt/daos//vos-0 value_load - # MD-on-SSD: ddb -w --db_path=/var/tmp/daos_testing/control_metadata/daos_control - # /engine0 --vos_path /mnt/daos_load//vos-0 value_load - # - ddb_command.db_path.update(value=" ".join(["--db_path", db_path])) - ddb_command.vos_path.update( - value=os.path.join(daos_load_path, pool.uuid.lower(), "vos-0")) - ddb_command.value_load(component_path="[0]/[0]/[0]/[0]", load_file_path=load_file_path) - - self.log_step("Restart the server.") - dmg_command.system_start() - - self.log_step("Reset the object, container, and pool to use the API after server restart.") - ioreqs[0].obj.close() - container.close() - pool.disconnect() - pool.connect() - container.open() - ioreqs[0].obj.open() - - self.log_step("Verify the data in the akey with single_fetch().") - data_size = len(new_data) + 1 - data = ioreqs[0].single_fetch( - dkey=dkeys_inserted[0], akey=akeys_inserted[0], size=data_size) - actual_data = data.value.decode('utf-8') - self.log.info("data (after) = %s", actual_data) - errors = [] - if new_data != actual_data: - msg = f"ddb load failed! Expected = {new_data}; Actual = {actual_data}" - errors.append(msg) - - if md_on_ssd: - self.log_step(f"MD-on-SSD: Clean {daos_load_path}") - self.run_cmd_check_result(command=f"umount {daos_load_path}") - self.run_cmd_check_result(command=f"rm -rf {daos_load_path}") - - report_errors(test=self, errors=errors) diff --git a/src/tests/ftest/recovery/ddb_pmem.py b/src/tests/ftest/recovery/ddb_pmem.py index d2b1b12b000..5c3f5f80df2 100644 --- a/src/tests/ftest/recovery/ddb_pmem.py +++ b/src/tests/ftest/recovery/ddb_pmem.py @@ -9,6 +9,8 @@ from apricot import TestWithServers from ddb_utils import DdbCommand +from exception_utils import CommandFailure +from file_utils import distribute_files from general_utils import (DaosTestError, create_string_buffer, get_random_string, report_errors, run_command) from pydaos.raw import DaosObjClass, IORequest @@ -115,6 +117,92 @@ def __init__(self, *args, **kwargs): self.random_akey = get_random_string(10) self.random_data = get_random_string(10) + def test_recovery_ddb_load(self): + """Test ddb value_load. + + 1. Create a pool and a container. + 2. Insert one object with one dkey with the API. + 3. Stop the server to use ddb. + 4. Find the vos file name. e.g., /mnt/daos0//vos-0. + 5. Load new data into [0]/[0]/[0]/[0] + 6. Restart the server. + 7. Reset the object, container, and pool to use the API. + 8. Verify the data in the akey with single_fetch(). + + :avocado: tags=all,full_regression + :avocado: tags=vm + :avocado: tags=recovery + :avocado: tags=DdbPMEMTest,ddb_cmd,test_recovery_ddb_load + """ + self.log_step("Create a pool and a container.") + pool = self.get_pool(connect=True) + container = self.get_container(pool) + + self.log_step("Insert one object with one dkey with API.") + obj_dataset = insert_objects( + context=self.context, container=container, object_count=1, dkey_count=1, akey_count=1, + base_dkey=self.random_dkey, base_akey=self.random_akey, base_data=self.random_data) + ioreqs = obj_dataset[0] + dkeys_inserted = obj_dataset[1] + akeys_inserted = obj_dataset[2] + data_list = obj_dataset[3] + + # For debugging/reference, call single_fetch and get the data just inserted. + # Pass in size + 1 to single_fetch to avoid the no-space error. + data_size = len(data_list[0]) + 1 + data = ioreqs[0].single_fetch( + dkey=dkeys_inserted[0], akey=akeys_inserted[0], size=data_size) + self.log.info("data (before) = %s", data.value.decode('utf-8')) + + self.log_step("Stop the server to use ddb.") + dmg_command = self.get_dmg_command() + dmg_command.system_stop() + + self.log_step("Find the vos file name.") + host = self.server_managers[0].hosts[0:1] + vos_paths = self.server_managers[0].get_vos_files(pool) + if not vos_paths: + self.fail("vos file wasn't found!") + ddb_command = DdbCommand(host, self.bin, vos_paths[0]) + + self.log_step("Load new data into [0]/[0]/[0]/[0]; Create a file in test node.") + load_file_path = os.path.join(self.test_dir, "new_data.txt") + new_data = get_random_string(20) + with open(load_file_path, "w", encoding="utf-8") as file: + file.write(new_data) + + self.log_step("Copy the created file to server node.") + result = distribute_files(self.log, host, load_file_path, load_file_path, False) + if not result.passed: + raise CommandFailure(f"ERROR: Copying new_data.txt to {result.failed_hosts}") + + self.log_step("The file with the new data is ready. Run ddb load.") + ddb_command.value_load(component_path="[0]/[0]/[0]/[0]", load_file_path=load_file_path) + + self.log_step("Restart the server.") + dmg_command.system_start() + + self.log_step("Reset the object, container, and pool to use the API after server restart.") + ioreqs[0].obj.close() + container.close() + pool.disconnect() + pool.connect() + container.open() + ioreqs[0].obj.open() + + self.log_step("Verify the data in the akey with single_fetch().") + data_size = len(new_data) + 1 + data = ioreqs[0].single_fetch( + dkey=dkeys_inserted[0], akey=akeys_inserted[0], size=data_size) + actual_data = data.value.decode('utf-8') + self.log.info("data (after) = %s", actual_data) + errors = [] + if new_data != actual_data: + msg = f"ddb load failed! Expected = {new_data}; Actual = {actual_data}" + errors.append(msg) + + report_errors(test=self, errors=errors) + def test_recovery_ddb_dump_value(self): """Test ddb dump_value. diff --git a/src/utils/ddb/ddb_commands.c b/src/utils/ddb/ddb_commands.c index 73fce057cd4..77553fe2bd2 100644 --- a/src/utils/ddb/ddb_commands.c +++ b/src/utils/ddb/ddb_commands.c @@ -306,7 +306,7 @@ print_value_cb(void *cb_args, d_iov_t *value) return 0; } - ddb_iov_to_printable_buf(value, buf, ARRAY_SIZE(buf), NULL); + ddb_iov_to_printable_buf(value, buf, ARRAY_SIZE(buf)); ddb_printf(ctx, "Value (size: %lu):\n", value->iov_len); ddb_printf(ctx, "%s\n", buf); return 0; diff --git a/src/utils/ddb/ddb_parse.h b/src/utils/ddb/ddb_parse.h index fad1a828fab..439791823a2 100644 --- a/src/utils/ddb/ddb_parse.h +++ b/src/utils/ddb/ddb_parse.h @@ -44,7 +44,7 @@ void ddb_str2argv_free(struct argv_parsed *parse_args); int ddb_parse_program_args(struct ddb_ctx *ctx, uint32_t argc, char **argv, struct program_args *pa); -/* See ddb_key_to_printable_buf for how the keys will be printed */ +/* See ddb_iov_to_printable_buf for how the keys will be printed */ int ddb_parse_key(const char *input, daos_key_t *key); /* Parse a string into the parts of a dtx_id. See DF_DTIF for how the format of the dtx_id is diff --git a/src/utils/ddb/ddb_printer.c b/src/utils/ddb/ddb_printer.c index 60c4be27da4..dd78efbb481 100644 --- a/src/utils/ddb/ddb_printer.c +++ b/src/utils/ddb/ddb_printer.c @@ -24,91 +24,69 @@ ddb_can_print(d_iov_t *iov) uint32_t len = iov->iov_len; int i; - for (i = 0; i < len; i++) { + for (i = 0 ; i < len ; i++) { if (str[i] == '\0') return true; - if (!isprint(str[i])) + if (!isprint(str[i]) && str[i] != '\n' && str[i] != '\r') return false; } return true; } /* - * Converts contents of an @iov to something that is more printable. + * Converts contents of an iov to something that is more printable. * - * Returns number of characters that would have been written if @buf is large enough, - * not including the null terminator. + * Returns number of characters that would have been written if buf_len was long + * enough, not including null terminator */ -uint32_t -ddb_iov_to_printable_buf(d_iov_t *iov, char buf[], uint32_t buf_len, const char *prefix) +int +ddb_iov_to_printable_buf(d_iov_t *iov, char buf[], uint32_t buf_len) { - char tmp[32]; - uint32_t new_len; - uint32_t result = 0; - int i; - if (iov->iov_len == 0 || iov->iov_buf == NULL) return 0; if (ddb_can_print(iov)) return snprintf(buf, buf_len, "%.*s", (int)iov->iov_len, (char *)iov->iov_buf); - if (prefix != NULL) - result = snprintf(buf, buf_len, "%s", prefix); - - for (i = 0; i < iov->iov_len; i++) { - new_len = snprintf(tmp, ARRAY_SIZE(tmp), "%02x", ((uint8_t *)iov->iov_buf)[i]); - if (new_len + result > buf_len) - result += new_len; /* Buffer is not big enough. */ - else - result += sprintf(buf + result, "%s", tmp); - } - - if (result > buf_len) { - buf[buf_len - 1] = '\0'; - for (i = 2; buf_len >= i && i <= 4; i++) - buf[buf_len - i] = '.'; - } - - return result; -} - -/* - * Converts contents of an @key to something that is more printable. - * - * Returns number of characters that would have been written if @buf is long enough, - * not including the null terminator. - */ -uint32_t -ddb_key_to_printable_buf(daos_key_t *key, enum daos_otype_t otype, char buf[], uint32_t buf_len) -{ - char tmp[32]; + switch (iov->iov_len) { + case sizeof(uint8_t): + return snprintf(buf, buf_len, "uint8:0x%x", ((uint8_t *)iov->iov_buf)[0]); + case sizeof(uint16_t): + return snprintf(buf, buf_len, "uint16:0x%04hx", ((uint16_t *)iov->iov_buf)[0]); + case sizeof(uint32_t): + return snprintf(buf, buf_len, "uint32:0x%x", ((uint32_t *)iov->iov_buf)[0]); + case sizeof(uint64_t): + return snprintf(buf, buf_len, "uint64:0x%lx", ((uint64_t *)iov->iov_buf)[0]); + default: + { + char tmp_buf[32]; + uint32_t new_len; + uint32_t result = 0; + int i; + + result += snprintf(buf, buf_len, "bin(%lu):0x", iov->iov_len); + + for (i = 0; i < iov->iov_len; i++) { + new_len = snprintf(tmp_buf, ARRAY_SIZE(tmp_buf), "%02x", + ((uint8_t *)iov->iov_buf)[i]); + if (new_len + result > buf_len) { + /* Buffer not big enough */ + result += new_len; + } else { + result += sprintf(buf + result, "%s", tmp_buf); + } + } - if (key->iov_len == 0 || key->iov_buf == NULL) - return 0; + if (result > buf_len) { + buf[buf_len - 1] = '\0'; + buf[buf_len - 2] = '.'; + buf[buf_len - 3] = '.'; + buf[buf_len - 4] = '.'; - if (ddb_key_is_lexical(otype)) - return snprintf(buf, buf_len, "%.*s", (int)key->iov_len, (char *)key->iov_buf); - - if (ddb_key_is_int(otype)) { - switch (key->iov_len) { - case sizeof(uint8_t): - return snprintf(buf, buf_len, "uint8:0x%x", ((uint8_t *)key->iov_buf)[0]); - case sizeof(uint16_t): - return snprintf(buf, buf_len, "uint16:0x%04hx", - ((uint16_t *)key->iov_buf)[0]); - case sizeof(uint32_t): - return snprintf(buf, buf_len, "uint32:0x%x", ((uint32_t *)key->iov_buf)[0]); - case sizeof(uint64_t): - return snprintf(buf, buf_len, "uint64:0x%lx", - ((uint64_t *)key->iov_buf)[0]); - /* Fall through. */ } + return result; + } } - - snprintf(tmp, ARRAY_SIZE(tmp), "bin(%lu):0x", key->iov_len); - - return ddb_iov_to_printable_buf(key, buf, buf_len, tmp); } void @@ -137,22 +115,22 @@ ddb_print_key(struct ddb_ctx *ctx, struct ddb_key *key, uint32_t indent) memset(buf, 0, buf_len); - ddb_key_to_printable_buf(&key->ddbk_key, key->ddbk_otype, buf, buf_len); + ddb_iov_to_printable_buf(&key->ddbk_key, buf, buf_len); print_indent(ctx, indent); - - if (ddb_key_is_lexical(key->ddbk_otype) || - (!ddb_key_is_int(key->ddbk_otype) && ddb_can_print(&key->ddbk_key))) - ddb_printf(ctx, DF_IDX " '%s' (%lu)%s\n", DP_IDX(key->ddbk_idx), buf, + if (ddb_can_print(&key->ddbk_key)) { + ddb_printf(ctx, DF_IDX" '%s' (%lu)%s\n", + DP_IDX(key->ddbk_idx), + buf, key->ddbk_key.iov_len, - key->ddbk_child_type == VOS_ITER_SINGLE ? " (SV)" - : key->ddbk_child_type == VOS_ITER_RECX ? " (ARRAY)" - : ""); - else - ddb_printf(ctx, DF_IDX " {%s}%s\n", DP_IDX(key->ddbk_idx), buf, - key->ddbk_child_type == VOS_ITER_SINGLE ? " (SV)" - : key->ddbk_child_type == VOS_ITER_RECX ? " (ARRAY)" - : ""); + key->ddbk_child_type == VOS_ITER_SINGLE ? " (SV)" : + key->ddbk_child_type == VOS_ITER_RECX ? " (ARRAY)" : ""); + return; + } + + ddb_printf(ctx, DF_IDX" {%s}%s\n", DP_IDX(key->ddbk_idx), buf, + key->ddbk_child_type == VOS_ITER_SINGLE ? " (SV)" : + key->ddbk_child_type == VOS_ITER_RECX ? " (ARRAY)" : ""); } void diff --git a/src/utils/ddb/ddb_printer.h b/src/utils/ddb/ddb_printer.h index 642b1fcf156..5c6cc5a7332 100644 --- a/src/utils/ddb/ddb_printer.h +++ b/src/utils/ddb/ddb_printer.h @@ -1,6 +1,5 @@ /** * (C) Copyright 2022 Intel Corporation. - * (C) Copyright 2025-2026 Hewlett Packard Enterprise Development LP * * SPDX-License-Identifier: BSD-2-Clause-Patent */ @@ -13,10 +12,7 @@ #define DF_IDX "[%d]" #define DP_IDX(idx) idx -uint32_t -ddb_iov_to_printable_buf(d_iov_t *iov, char buf[], uint32_t buf_len, const char *prefix); -uint32_t -ddb_key_to_printable_buf(daos_key_t *key, enum daos_otype_t otype, char buf[], uint32_t buf_len); +int ddb_iov_to_printable_buf(d_iov_t *iov, char buf[], uint32_t buf_len); void ddb_print_cont(struct ddb_ctx *ctx, struct ddb_cont *cont); void ddb_print_obj(struct ddb_ctx *ctx, struct ddb_obj *obj, uint32_t indent); void ddb_print_key(struct ddb_ctx *ctx, struct ddb_key *key, uint32_t indent); @@ -33,16 +29,5 @@ bool ddb_can_print(d_iov_t *iov); /* some utility functions helpful for printing */ void ddb_bytes_hr(uint64_t bytes, char *buf, uint32_t buf_len); -static inline bool -ddb_key_is_lexical(enum daos_otype_t otype) -{ - return daos_is_dkey_lexical_type(otype) || daos_is_akey_lexical_type(otype); -} - -static inline bool -ddb_key_is_int(enum daos_otype_t otype) -{ - return daos_is_dkey_uint64_type(otype) || daos_is_akey_uint64_type(otype); -} #endif /* DAOS_DDB_PRINTER_H */ diff --git a/src/utils/ddb/ddb_tree_path.c b/src/utils/ddb/ddb_tree_path.c index 20fa553f2bd..3bfc104424e 100644 --- a/src/utils/ddb/ddb_tree_path.c +++ b/src/utils/ddb/ddb_tree_path.c @@ -304,55 +304,58 @@ itp_parse(const char *path, struct dv_indexed_tree_path *itp) } bool -itp_part_set_cont(struct indexed_tree_path_part *part, void *part_value) +itp_part_set_cont(union itp_part_type *part, void *part_value) { const uint8_t *cont_uuid = part_value; if (cont_uuid == NULL || uuid_is_null(cont_uuid)) return false; - uuid_copy(part->itp_part_value.itp_uuid, cont_uuid); + uuid_copy(part->itp_uuid, cont_uuid); return true; } bool -itp_part_set_obj(struct indexed_tree_path_part *part, void *part_value) +itp_part_set_obj(union itp_part_type *part, void *part_value) { daos_unit_oid_t *oid = part_value; if (daos_unit_oid_is_null(*oid)) return false; - part->itp_part_value.itp_oid = *oid; - part->itp_otype = daos_obj_id2type(oid->id_pub); + part->itp_oid = *oid; return true; } bool -itp_part_set_key(struct indexed_tree_path_part *part, void *part_value) +itp_part_set_key(union itp_part_type *part, void *part_value) { daos_key_t *key = part_value; if (key->iov_len == 0) return false; - daos_iov_copy(&part->itp_part_value.itp_key, key); + daos_iov_copy(&part->itp_key, key); return true; } bool -itp_part_set_recx(struct indexed_tree_path_part *part, void *part_value) +itp_part_set_recx(union itp_part_type *part, void *part_value) { daos_recx_t *recx = part_value; if (recx->rx_nr == 0) return false; - part->itp_part_value.itp_recx = *recx; + part->itp_recx = *recx; return true; } -static bool (*part_set_fn[PATH_PART_END])(struct indexed_tree_path_part *part, void *part_value) = { - itp_part_set_cont, itp_part_set_obj, itp_part_set_key, itp_part_set_key, itp_part_set_recx, +static bool (*part_set_fn[PATH_PART_END])(union itp_part_type *part, void *part_value) = { + itp_part_set_cont, + itp_part_set_obj, + itp_part_set_key, + itp_part_set_key, + itp_part_set_recx, }; bool @@ -360,7 +363,7 @@ itp_part_value_set(struct dv_indexed_tree_path *itp, enum path_parts part_key, v { struct indexed_tree_path_part *p = &itp->itp_parts[part_key]; - if (part_set_fn[part_key](p, part_value)) { + if (part_set_fn[part_key](&p->itp_part_value, part_value)) { p->itp_has_part_value = true; return true; } @@ -776,15 +779,15 @@ dvp_is_empty(struct dv_tree_path *vtp) */ void -itp_print_part_cont(struct ddb_ctx *ctx, struct indexed_tree_path_part *v) +itp_print_part_cont(struct ddb_ctx *ctx, union itp_part_type *v) { - ddb_printf(ctx, DF_UUIDF, DP_UUID(v->itp_part_value.itp_uuid)); + ddb_printf(ctx, DF_UUIDF, DP_UUID(v->itp_uuid)); } void -itp_print_part_obj(struct ddb_ctx *ctx, struct indexed_tree_path_part *v) +itp_print_part_obj(struct ddb_ctx *ctx, union itp_part_type *v) { - ddb_printf(ctx, DF_UOID, DP_UOID(v->itp_part_value.itp_oid)); + ddb_printf(ctx, DF_UOID, DP_UOID(v->itp_oid)); } bool @@ -803,26 +806,18 @@ itp_key_safe_str(char *buf, size_t buf_len) int e; bool escaped = false; + if (tmp_idx + 1 >= tmp_end) { /* +1 for escape character if needed */ + D_ERROR("Buffer was too small to hold the escape characters"); + return false; + } for (e = 0; e < ARRAY_SIZE(escape_chars) && !escaped; ++e) { if (buf[i] == escape_chars[e]) { - if (tmp_idx + 1 >= tmp_end) { /* +1 for escape character */ - D_ERROR("Too small buffer (%ld) to hold escape character\n", - buf_len); - return false; - } - sprintf(tmp_idx, "\\%c", buf[i]); tmp_idx += 2; escaped = true; } } if (!escaped) { - if (tmp_idx >= tmp_end) { - D_ERROR("Too small buffer (%ld) because former escape characters\n", - buf_len); - return false; - } - sprintf(tmp_idx, "%c", buf[i]); tmp_idx++; } @@ -833,16 +828,15 @@ itp_key_safe_str(char *buf, size_t buf_len) } void -itp_print_part_key(struct ddb_ctx *ctx, struct indexed_tree_path_part *part) +itp_print_part_key(struct ddb_ctx *ctx, union itp_part_type *key_part) { - char buf[DDB_MAX_PRINTABLE_KEY]; - d_iov_t *key_iov = &part->itp_part_value.itp_key; + char buf[DDB_MAX_PRITABLE_KEY]; + d_iov_t *key_iov = &key_part->itp_key; - ddb_key_to_printable_buf(key_iov, part->itp_otype, buf, ARRAY_SIZE(buf)); - if (ddb_key_is_lexical(part->itp_otype) || - (!ddb_key_is_int(part->itp_otype) && ddb_can_print(key_iov))) { + ddb_iov_to_printable_buf(key_iov, buf, ARRAY_SIZE(buf)); + if (ddb_can_print(key_iov)) { /* +1 to make sure there's room for a null terminator */ - char key_str[key_iov->iov_len + 1]; + char key_str[key_part->itp_key.iov_len + 1]; memcpy(key_str, key_iov->iov_buf, key_iov->iov_len); key_str[key_iov->iov_len] = '\0'; @@ -865,14 +859,17 @@ itp_print_part_key(struct ddb_ctx *ctx, struct indexed_tree_path_part *part) } void -itp_print_part_recx(struct ddb_ctx *ctx, struct indexed_tree_path_part *v) +itp_print_part_recx(struct ddb_ctx *ctx, union itp_part_type *v) { - ddb_printf(ctx, DF_DDB_RECX, DP_DDB_RECX(v->itp_part_value.itp_recx)); + ddb_printf(ctx, DF_DDB_RECX, DP_DDB_RECX(v->itp_recx)); } -static void (*print_fn[PATH_PART_END])(struct ddb_ctx *ctx, struct indexed_tree_path_part *v) = { - itp_print_part_cont, itp_print_part_obj, itp_print_part_key, - itp_print_part_key, itp_print_part_recx, +static void (*print_fn[PATH_PART_END])(struct ddb_ctx *ctx, union itp_part_type *v) = { + itp_print_part_cont, + itp_print_part_obj, + itp_print_part_key, + itp_print_part_key, + itp_print_part_recx, }; void @@ -889,7 +886,7 @@ itp_print_parts(struct ddb_ctx *ctx, struct dv_indexed_tree_path *itp) if (!itp->itp_parts[i].itp_has_part_value) break; ddb_print(ctx, "/"); - print_fn[i](ctx, &itp->itp_parts[i]); + print_fn[i](ctx, &itp->itp_parts[i].itp_part_value); } } diff --git a/src/utils/ddb/ddb_tree_path.h b/src/utils/ddb/ddb_tree_path.h index fb3fe9c10b8..af8c3d92dcf 100644 --- a/src/utils/ddb/ddb_tree_path.h +++ b/src/utils/ddb/ddb_tree_path.h @@ -1,6 +1,5 @@ /** * (C) Copyright 2023 Intel Corporation. - * (C) Copyright 2025-2026 Hewlett Packard Enterprise Development LP * * SPDX-License-Identifier: BSD-2-Clause-Patent */ @@ -13,9 +12,9 @@ #define DF_DDB_RECX "{"DF_U64"-"DF_U64"}" #define DP_DDB_RECX(r) (r).rx_idx, ((r).rx_idx + (r).rx_nr - 1) -#define INVALID_IDX (-1) -#define INVALID_PATH "INVALID PATH" -#define DDB_MAX_PRINTABLE_KEY 1024 +#define INVALID_IDX (-1) +#define INVALID_PATH "INVALID PATH" +#define DDB_MAX_PRITABLE_KEY 1024 #define ERROR_BASE 5000 enum ddb_parse_error { @@ -52,11 +51,10 @@ struct indexed_tree_path_part { daos_unit_oid_t itp_oid; daos_key_t itp_key; /* akey or dkey */ daos_recx_t itp_recx; - } itp_part_value; - uint32_t itp_part_idx; - enum daos_otype_t itp_otype; - bool itp_has_part_idx; - bool itp_has_part_value; + } itp_part_value; + uint32_t itp_part_idx; + bool itp_has_part_idx; + bool itp_has_part_value; }; struct dv_indexed_tree_path { @@ -95,14 +93,10 @@ bool itp_idx_set(struct dv_indexed_tree_path *itp, enum path_parts part_key, uint32_t idx); /* Functions for setting parts as a specific path part (i.e. container, object, ... */ -bool -itp_part_set_cont(struct indexed_tree_path_part *part, void *part_value); -bool -itp_part_set_obj(struct indexed_tree_path_part *part, void *part_value); -bool -itp_part_set_key(struct indexed_tree_path_part *part, void *part_value); -bool - itp_part_set_recx(struct indexed_tree_path_part *part, void *part_value); +bool itp_part_set_cont(union itp_part_type *part, void *part_value); +bool itp_part_set_obj(union itp_part_type *part, void *part_value); +bool itp_part_set_key(union itp_part_type *part, void *part_value); +bool itp_part_set_recx(union itp_part_type *part, void *part_value); /* Functions for setting the parts (cont, obj, ...) of a indexed tree path */ bool itp_set_cont(struct dv_indexed_tree_path *itp, uuid_t cont_uuid, uint32_t idx); @@ -177,8 +171,7 @@ int itp_recx_idx(struct dv_indexed_tree_path *itp); void itp_print_indexes(struct ddb_ctx *ctx, struct dv_indexed_tree_path *itp); void itp_print_parts(struct ddb_ctx *ctx, struct dv_indexed_tree_path *itp); void itp_print_full(struct ddb_ctx *ctx, struct dv_indexed_tree_path *itp); -void - itp_print_part_key(struct ddb_ctx *ctx, struct indexed_tree_path_part *part); +void itp_print_part_key(struct ddb_ctx *ctx, union itp_part_type *key_part); /* * This function is used when printing keys. It checks each character in the buffer and will diff --git a/src/utils/ddb/ddb_vos.c b/src/utils/ddb/ddb_vos.c index a8e65670553..4ee22fbe099 100644 --- a/src/utils/ddb/ddb_vos.c +++ b/src/utils/ddb/ddb_vos.c @@ -404,19 +404,19 @@ vos_vtp_compare(struct dv_indexed_tree_path *vtp, vos_iter_entry_t *entry, enum } static void -set_oid(vos_iter_entry_t *entry, struct indexed_tree_path_part *part) +set_oid(vos_iter_entry_t *entry, union itp_part_type *part) { itp_part_set_obj(part, &entry->ie_oid); } static void -set_key(vos_iter_entry_t *entry, struct indexed_tree_path_part *part) +set_key(vos_iter_entry_t *entry, union itp_part_type *part) { itp_part_set_key(part, &entry->ie_key); } static void -set_recx(vos_iter_entry_t *entry, struct indexed_tree_path_part *part) +set_recx(vos_iter_entry_t *entry, union itp_part_type *part) { itp_part_set_recx(part, &entry->ie_orig_recx); } @@ -424,16 +424,18 @@ set_recx(vos_iter_entry_t *entry, struct indexed_tree_path_part *part) static void vos_itp_set(struct dv_indexed_tree_path *itp, vos_iter_entry_t *entry, enum path_parts part_key) { - void (*set_fn[PATH_PART_END])(vos_iter_entry_t *entry, - struct indexed_tree_path_part *part) = { + void (*set_fn[PATH_PART_END])(vos_iter_entry_t *entry, union itp_part_type *part) = { NULL, /* Won't set containers */ - set_oid, set_key, set_key, set_recx, + set_oid, + set_key, + set_key, + set_recx, }; D_ASSERT(part_key < PATH_PART_END); D_ASSERT(set_fn[part_key] != NULL); - set_fn[part_key](entry, &itp->itp_parts[part_key]); + set_fn[part_key](entry, &itp->itp_parts[part_key].itp_part_value); itp->itp_parts[part_key].itp_has_part_value = true; } @@ -733,7 +735,8 @@ dv_oid_to_obj(daos_obj_id_t oid, struct ddb_obj *obj) * obj_class_fini(); */ - get_object_type(daos_obj_id2type(oid), obj->ddbo_otype_str); + obj->ddbo_otype = daos_obj_id2type(oid); + get_object_type(obj->ddbo_otype, obj->ddbo_otype_str); } static int @@ -769,10 +772,9 @@ handle_dkey(struct ddb_iter_ctx *ctx, vos_iter_entry_t *entry) itp_unset_dkey(&ctx->itp); /* make sure dkey is freed from any previous handle */ itp_set_dkey(&ctx->itp, &entry->ie_key, ctx->dkey_seen); - dkey.ddbk_path = &ctx->itp; - dkey.ddbk_idx = ctx->dkey_seen++; - dkey.ddbk_key = entry->ie_key; - dkey.ddbk_otype = daos_obj_id2type(ctx->current_obj.id_pub); + dkey.ddbk_path = &ctx->itp; + dkey.ddbk_idx = ctx->dkey_seen++; + dkey.ddbk_key = entry->ie_key; dkey.ddbk_child_type = entry->ie_child_type; ctx->current_dkey = entry->ie_key; @@ -796,10 +798,9 @@ handle_akey(struct ddb_iter_ctx *ctx, vos_iter_entry_t *entry) itp_set_akey(&ctx->itp, &entry->ie_key, ctx->akey_seen); itp_unset_recx(&ctx->itp); - akey.ddbk_path = &ctx->itp; - akey.ddbk_idx = ctx->akey_seen++; - akey.ddbk_key = entry->ie_key; - akey.ddbk_otype = daos_obj_id2type(ctx->current_obj.id_pub); + akey.ddbk_path = &ctx->itp; + akey.ddbk_idx = ctx->akey_seen++; + akey.ddbk_key = entry->ie_key; akey.ddbk_child_type = entry->ie_child_type; ctx->current_akey = entry->ie_key; @@ -920,10 +921,9 @@ dv_iterate(daos_handle_t poh, struct dv_tree_path *path, bool recursive, vos_iter_type_t type; struct ddb_iter_ctx ctx = {0}; - ctx.handlers = handlers; + ctx.handlers = handlers; ctx.handler_args = handler_args; - ctx.poh = poh; - ctx.current_obj = path->vtp_oid; + ctx.poh = poh; itp_copy(&ctx.itp, itp); param.ip_epr.epr_hi = DAOS_EPOCH_MAX; diff --git a/src/utils/ddb/ddb_vos.h b/src/utils/ddb/ddb_vos.h index 3c1a48809b2..675c4ceb33b 100644 --- a/src/utils/ddb/ddb_vos.h +++ b/src/utils/ddb/ddb_vos.h @@ -20,19 +20,19 @@ struct ddb_cont { }; struct ddb_obj { - daos_obj_id_t ddbo_oid; - uint32_t ddbo_idx; - uint32_t ddbo_nr_grps; - char ddbo_otype_str[32]; - struct dv_indexed_tree_path *ddbo_path; + daos_obj_id_t ddbo_oid; + uint32_t ddbo_idx; + enum daos_otype_t ddbo_otype; + char ddbo_otype_str[32]; + uint32_t ddbo_nr_grps; + struct dv_indexed_tree_path *ddbo_path; }; struct ddb_key { - daos_key_t ddbk_key; - uint32_t ddbk_idx; - enum daos_otype_t ddbk_otype; - vos_iter_type_t ddbk_child_type; - struct dv_indexed_tree_path *ddbk_path; + daos_key_t ddbk_key; + uint32_t ddbk_idx; + vos_iter_type_t ddbk_child_type; + struct dv_indexed_tree_path *ddbk_path; }; struct ddb_sv { diff --git a/src/utils/ddb/tests/ddb_commands_print_tests.c b/src/utils/ddb/tests/ddb_commands_print_tests.c index 129568acbc9..9b60b070dd5 100644 --- a/src/utils/ddb/tests/ddb_commands_print_tests.c +++ b/src/utils/ddb/tests/ddb_commands_print_tests.c @@ -1,6 +1,6 @@ /** * (C) Copyright 2022-2023 Intel Corporation. - * (C) Copyright 2025-2026 Hewlett Packard Enterprise Development LP + * (C) Copyright 2025 Hewlett Packard Enterprise Development LP * * SPDX-License-Identifier: BSD-2-Clause-Patent */ @@ -60,7 +60,6 @@ print_key_test(void **state) key.ddbk_idx = 4; d_iov_set(&key.ddbk_key, key_buf, ARRAY_SIZE(key_buf)); - key.ddbk_otype = DAOS_OT_MULTI_LEXICAL; ddb_print_key(&g_ctx, &key, 0); @@ -94,7 +93,6 @@ print_key_test(void **state) * If key length is a number type, then print as that. */ memset(key_buf, 0, ARRAY_SIZE(key_buf)); - key.ddbk_otype = DAOS_OT_MULTI_UINT64; /* char key */ key_buf[0] = 0xab; @@ -288,17 +286,17 @@ iov_to_printable_test(void **state) char buf[buf_len]; char input_buf[buf_len]; - assert_int_equal(0, ddb_iov_to_printable_buf(&iov, buf, buf_len, NULL)); + assert_int_equal(0, ddb_iov_to_printable_buf(&iov, buf, buf_len)); /* buf is plenty big */ sprintf(input_buf, "This is some text"); d_iov_set(&iov, input_buf, strlen(input_buf) + 1); - assert_int_equal(17, ddb_iov_to_printable_buf(&iov, buf, buf_len, NULL)); + assert_int_equal(17, ddb_iov_to_printable_buf(&iov, buf, buf_len)); assert_string_equal(input_buf, buf); /* buf is too small */ memset(buf, 0, buf_len); - assert_int_equal(17, ddb_iov_to_printable_buf(&iov, buf, 10, NULL)); + assert_int_equal(17, ddb_iov_to_printable_buf(&iov, buf, 10)); assert_string_equal("This is s", buf); /* Binary type - enough buffer*/ @@ -307,27 +305,27 @@ iov_to_printable_test(void **state) /* chars written to buffer is 30. For each byte, 2 are printed (10 bytes * 2) plus * the prefix of 'bin(10):' is 10 more chars. */ - assert_int_equal(30, ddb_key_to_printable_buf(&iov, 0, buf, buf_len)); + assert_int_equal(30, ddb_iov_to_printable_buf(&iov, buf, buf_len)); assert_string_equal("bin(10):0xabababababababababab", buf); /* Binary type - not enough buffer*/ - assert_int_equal(30, ddb_key_to_printable_buf(&iov, 0, buf, 20)); + assert_int_equal(30, ddb_iov_to_printable_buf(&iov, buf, 20)); assert_string_equal("bin(10):0xababab...", buf); /* Number types */ d_iov_set(&iov, input_buf, 8); /* uint64 */ - assert_int_equal(25, ddb_key_to_printable_buf(&iov, DAOS_OT_MULTI_UINT64, buf, buf_len)); + assert_int_equal(25, ddb_iov_to_printable_buf(&iov, buf, buf_len)); assert_string_equal("uint64:0xabababababababab", buf); - assert_int_equal(25, ddb_key_to_printable_buf(&iov, DAOS_OT_MULTI_UINT64, buf, 10)); + assert_int_equal(25, ddb_iov_to_printable_buf(&iov, buf, 10)); assert_string_equal("uint64:0x", buf); d_iov_set(&iov, input_buf, 4); /* uint32 */ - assert_int_equal(17, ddb_key_to_printable_buf(&iov, DAOS_OT_ARRAY_BYTE, buf, buf_len)); + assert_int_equal(17, ddb_iov_to_printable_buf(&iov, buf, buf_len)); assert_string_equal("uint32:0xabababab", buf); d_iov_set(&iov, input_buf, 1); /* uint8 */ - assert_int_equal(10, ddb_key_to_printable_buf(&iov, DAOS_OT_ARRAY_BYTE, buf, buf_len)); + assert_int_equal(10, ddb_iov_to_printable_buf(&iov, buf, buf_len)); assert_string_equal("uint8:0xab", buf); } diff --git a/src/utils/ddb/tests/ddb_path_tests.c b/src/utils/ddb/tests/ddb_path_tests.c index 5b8443d1b7d..7c0c7a3a421 100644 --- a/src/utils/ddb/tests/ddb_path_tests.c +++ b/src/utils/ddb/tests/ddb_path_tests.c @@ -1,6 +1,5 @@ /** * (C) Copyright 2023 Intel Corporation. - * (C) Copyright 2026 Hewlett Packard Enterprise Development LP * * SPDX-License-Identifier: BSD-2-Clause-Patent */ @@ -74,7 +73,6 @@ key_safe_str_tests(void **state) assert_string_equal(small_buf, "///////"); } -/* clang-format off */ static void key_printing_and_parsing_tests(void **state) { @@ -82,36 +80,32 @@ key_printing_and_parsing_tests(void **state) * These tests will parse the first argument, then print it. The printed * value will be compared to the second (expected) argument. */ -#define assert_key_parsed_printed(parsed, printed, int_key) \ - do { \ - struct indexed_tree_path_part __part = {0}; \ - assert_true(ddb_parse_key(parsed, &__part.itp_part_value.itp_key) > 0); \ - __part.itp_otype = int_key ? DAOS_OT_MULTI_UINT64 : 0; \ - itp_print_part_key(&g_ctx, &__part); \ - assert_printed_exact(printed); \ - dvt_fake_print_reset(); \ - daos_iov_free(&__part.itp_part_value.itp_key); \ - } while (0) - - assert_key_parsed_printed("akey", "akey", false); - assert_key_parsed_printed("akey{4}", "akey", false); - assert_key_parsed_printed("akey{64}", "akey{64}", false); +#define assert_key_parsed_printed(parsed, printed) do {\ + union itp_part_type __v = {0}; \ + assert_true(ddb_parse_key(parsed, &__v.itp_key) > 0); \ + itp_print_part_key(&g_ctx, &__v); \ + assert_printed_exact(printed); \ + dvt_fake_print_reset(); \ + daos_iov_free(&__v.itp_key); \ +} while (0) + + assert_key_parsed_printed("akey", "akey"); + assert_key_parsed_printed("akey{4}", "akey"); + assert_key_parsed_printed("akey{64}", "akey{64}"); /* binary should take size as input, but doesn't need it. It will always print it however */ - assert_key_parsed_printed("{bin:0xabcdef1234}", "{bin(5):0xabcdef1234}", false); - assert_key_parsed_printed("{bin(5):0xabcdef1234}", "{bin(5):0xabcdef1234}", false); + assert_key_parsed_printed("{bin:0xabcdef1234}", "{bin(5):0xabcdef1234}"); + assert_key_parsed_printed("{bin(5):0xabcdef1234}", "{bin(5):0xabcdef1234}"); /* Int types. Hex letters' case doesn't matter. Will always print as lower case */ - assert_key_parsed_printed("{uint64:10}", "{uint64:0xa}", true); - assert_key_parsed_printed("{uint64:0xABCDEF1234}", "{uint64:0xabcdef1234}", true); - assert_key_parsed_printed("{uint32:0x12345678}", "{uint32:0x12345678}", true); - assert_key_parsed_printed("{uint16:0x1234}", "{uint16:0x1234}", true); - assert_key_parsed_printed("{uint8:0xAF}", "{uint8:0xaf}", true); + assert_key_parsed_printed("{uint64:0xABCDEF1234}", "{uint64:0xabcdef1234}"); + assert_key_parsed_printed("{uint32:0x12345678}", "{uint32:0x12345678}"); + assert_key_parsed_printed("{uint16:0x1234}", "{uint16:0x1234}"); + assert_key_parsed_printed("{uint8:0xAF}", "{uint8:0xaf}"); /* Parsing doesn't handle too big of values yet, so will get truncated */ - assert_key_parsed_printed("{uint8:0xFFFAAA}", "{uint8:0xaa}", true); - assert_key_parsed_printed("\\/", "\\/", false); + assert_key_parsed_printed("{uint8:0xFFFAAA}", "{uint8:0xaa}"); + assert_key_parsed_printed("\\/", "\\/"); } -/* clang-format on */ /* Test setting and printing the full path given the path parts structure */ static void diff --git a/utils/cq/requirements.txt b/utils/cq/requirements.txt index d96833d9599..ef9340fcb73 100644 --- a/utils/cq/requirements.txt +++ b/utils/cq/requirements.txt @@ -6,7 +6,7 @@ flake8==7.3.0 isort==8.0.1 pylint==4.0.5 yamllint==1.38.0 -codespell==2.4.2 +codespell==2.4.1 # Used by ci/jira_query.py which pip installs it standalone. jira torch>=2.2.0 diff --git a/utils/rpms/mercury.changelog b/utils/rpms/mercury.changelog index 2ec75cecb4c..c00f682eb61 100644 --- a/utils/rpms/mercury.changelog +++ b/utils/rpms/mercury.changelog @@ -1,7 +1,4 @@ %changelog -* Mon Mar 09 2026 Jerome Soumagne - 2.4.1-2 -- Require mercury-libfabric or mercury-ucx - * Mon Jan 26 2026 Jerome Soumagne - 2.4.1-1 - Update to 2.4.1 - Separate libfabric plugin from main build to align with ucx plugin diff --git a/utils/rpms/mercury.sh b/utils/rpms/mercury.sh index 1e6ea021983..5f30dd89372 100755 --- a/utils/rpms/mercury.sh +++ b/utils/rpms/mercury.sh @@ -43,9 +43,7 @@ clean_bin "${files[@]}" append_install_list "${files[@]}" ARCH="${isa}" -DEPENDS=("(mercury-libfabric or mercury-ucx)") build_package "mercury" -DEPENDS=() TARGET_PATH="${libdir}/mercury" list_files files "${SL_MERCURY_PREFIX}/lib64/mercury/libna_plugin_ofi.so" diff --git a/utils/rpms/package_info.sh b/utils/rpms/package_info.sh index 5be9fb49ac8..ef3338124d4 100644 --- a/utils/rpms/package_info.sh +++ b/utils/rpms/package_info.sh @@ -45,7 +45,7 @@ export libfabric_version="1.22.0" export libfabric_release="5${distro_name}" export libfabric_full="${libfabric_version}-${libfabric_release}" export mercury_version="2.4.1" -export mercury_release="2${distro_name}" +export mercury_release="1${distro_name}" export mercury_full="${mercury_version}-${mercury_release}" export argobots_version="1.2" export argobots_release="4${distro_name}" From 9c59cc1b46ad3a0540ba271a672e2fed2130f2d6 Mon Sep 17 00:00:00 2001 From: Tomasz Gromadzki Date: Mon, 16 Mar 2026 08:49:07 +0100 Subject: [PATCH 9/9] Reapply "Merge remote-tracking branch 'origin/master' into grom72/DAOS-18495-ABT_THREAD_STACKSIZE-24k-md-on-ssd" This reverts commit d5bc34e7044f7312697c3edcf773045b4b7fc6f2. Priority: 2 Allow-unstable-test: true Signed-off-by: Tomasz Gromadzki --- .github/workflows/ossf-scorecard.yml | 2 +- .../workflows/rpm-build-and-test-report.yml | 4 +- .github/workflows/trivy.yml | 8 +- Jenkinsfile | 123 ++++++------ .../post_provision_config_common_functions.sh | 4 +- .../post_provision_config_nodes_EL.sh | 3 - ci/unit/required_packages.sh | 9 +- src/client/dfs/SConscript | 2 +- src/include/daos_srv/rsvc.h | 5 +- src/placement/jump_map_versions.c | 178 ++++++++---------- src/pool/srv_pool.c | 17 ++ src/rsvc/srv.c | 11 +- src/tests/ftest/object/create_many_dkeys.py | 104 ---------- src/tests/ftest/object/create_many_dkeys.yaml | 20 -- src/tests/ftest/pool/create.yaml | 2 +- src/tests/ftest/recovery/ddb.py | 126 ++++++++++++- src/tests/ftest/recovery/ddb_pmem.py | 88 --------- src/utils/ddb/ddb_commands.c | 2 +- src/utils/ddb/ddb_parse.h | 2 +- src/utils/ddb/ddb_printer.c | 132 +++++++------ src/utils/ddb/ddb_printer.h | 17 +- src/utils/ddb/ddb_tree_path.c | 79 ++++---- src/utils/ddb/ddb_tree_path.h | 31 +-- src/utils/ddb/ddb_vos.c | 38 ++-- src/utils/ddb/ddb_vos.h | 20 +- .../ddb/tests/ddb_commands_print_tests.c | 22 ++- src/utils/ddb/tests/ddb_path_tests.c | 46 +++-- utils/cq/requirements.txt | 2 +- utils/rpms/mercury.changelog | 3 + utils/rpms/mercury.sh | 2 + utils/rpms/package_info.sh | 2 +- 31 files changed, 542 insertions(+), 562 deletions(-) delete mode 100644 src/tests/ftest/object/create_many_dkeys.py delete mode 100644 src/tests/ftest/object/create_many_dkeys.yaml diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 21822ae1038..42a71b0c7cd 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -71,6 +71,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 + uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 with: sarif_file: results.sarif diff --git a/.github/workflows/rpm-build-and-test-report.yml b/.github/workflows/rpm-build-and-test-report.yml index aae911b31c6..8ce8d013d6c 100644 --- a/.github/workflows/rpm-build-and-test-report.yml +++ b/.github/workflows/rpm-build-and-test-report.yml @@ -93,7 +93,7 @@ jobs: esac echo "STAGE_NAME=Build RPM on $DISTRO_NAME $DISTRO_VERSION" >> $GITHUB_ENV - name: Test Report - uses: dorny/test-reporter@b082adf0eced0765477756c2a610396589b8c637 # v2.5.0 + uses: dorny/test-reporter@3d76b34a4535afbd0600d347b09a6ee5deb3ed7f # v2.6.0 with: artifact: ${{ env.STAGE_NAME }} test-results name: ${{ env.STAGE_NAME }} Test Results (dorny) @@ -112,7 +112,7 @@ jobs: - name: Set variables run: echo "STAGE_NAME=Functional Hardware ${{ matrix.stage }}" >> $GITHUB_ENV - name: Test Report - uses: dorny/test-reporter@b082adf0eced0765477756c2a610396589b8c637 # v2.5.0 + uses: dorny/test-reporter@3d76b34a4535afbd0600d347b09a6ee5deb3ed7f # v2.6.0 with: artifact: ${{ env.STAGE_NAME }} test-results name: ${{ env.STAGE_NAME }} Test Results (dorny) diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 21aa575975f..e88a6ba54c5 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -36,7 +36,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run Trivy vulnerability scanner in filesystem mode (table format) - uses: aquasecurity/trivy-action@e368e328979b113139d6f9068e03accaed98a518 # 0.34.1 + uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0 with: scan-type: 'fs' scan-ref: '.' @@ -61,14 +61,14 @@ jobs: sed -i 's/format: template/format: sarif/g' utils/trivy/trivy.yaml - name: Run Trivy vulnerability scanner in filesystem mode (sarif format) - uses: aquasecurity/trivy-action@e368e328979b113139d6f9068e03accaed98a518 # 0.34.1 + uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0 with: scan-type: 'fs' scan-ref: '.' trivy-config: 'utils/trivy/trivy.yaml' - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 + uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 with: sarif_file: 'trivy-results.sarif' @@ -79,7 +79,7 @@ jobs: sed -i 's/exit-code: 0/exit-code: 1/g' utils/trivy/trivy.yaml - name: Run Trivy vulnerability scanner in filesystem mode (human readable format) - uses: aquasecurity/trivy-action@e368e328979b113139d6f9068e03accaed98a518 # 0.34.1 + uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0 with: scan-type: 'fs' scan-ref: '.' diff --git a/Jenkinsfile b/Jenkinsfile index d1b4d4abc1e..7908e7042c0 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -56,7 +56,7 @@ Map nlt_test() { print 'Unstash failed, results from NLT stage will not be included' } sh label: 'Fault injection testing using NLT', - script: './ci/docker_nlt.sh --class-name el8.fault-injection fi' + script: './ci/docker_nlt.sh --class-name fault-injection fi' List filesList = [] filesList.addAll(findFiles(glob: '*.memcheck.xml')) int vgfail = 0 @@ -317,22 +317,22 @@ pipeline { description: 'Continue testing if a previous stage is Unstable') booleanParam(name: 'CI_UNIT_TEST', defaultValue: true, - description: 'Run the Unit Test on EL 8 test stage') + description: 'Run the Unit Test test stage') booleanParam(name: 'CI_NLT_TEST', defaultValue: true, description: 'Run the NLT test stage') booleanParam(name: 'CI_UNIT_TEST_MEMCHECK', defaultValue: true, - description: 'Run the Unit Test with memcheck on EL 8 test stage') - booleanParam(name: 'CI_FI_el8_TEST', + description: 'Run the Unit Test with memcheck test stage') + booleanParam(name: 'CI_FI_TEST', defaultValue: true, - description: 'Run the Fault injection testing on EL 8 test stage') - booleanParam(name: 'CI_TEST_EL8_RPMs', + description: 'Run the Fault injection testing test stage') + booleanParam(name: 'CI_TEST_EL_RPMs', defaultValue: true, - description: 'Run the Test RPMs on EL 8 test stage') - booleanParam(name: 'CI_TEST_LEAP15_RPMs', + description: 'Run the Test RPMs on EL stage') + booleanParam(name: 'CI_TEST_LEAP_RPMs', defaultValue: true, - description: 'Run the Test RPMs on Leap 15 test stage') + description: 'Run the Test RPMs on Leap test stage') booleanParam(name: 'CI_FUNCTIONAL_TEST_SKIP', defaultValue: false, description: 'Skip all functional test stages (Test)') @@ -343,19 +343,17 @@ pipeline { defaultValue: false, description: 'Run the Functional on EL 8 with Valgrind test stage') booleanParam(name: 'CI_FUNCTIONAL_el8_TEST', - defaultValue: true, + defaultValue: false, description: 'Run the Functional on EL 8 test stage') booleanParam(name: 'CI_FUNCTIONAL_el9_TEST', - defaultValue: false, + defaultValue: true, description: 'Run the Functional on EL 9 test stage') booleanParam(name: 'CI_FUNCTIONAL_leap15_TEST', defaultValue: false, - description: 'Run the Functional on Leap 15 test stage' + - ' Requires CI_MORE_FUNCTIONAL_PR_TESTS') + description: 'Run the Functional on Leap 15 test stage') booleanParam(name: 'CI_FUNCTIONAL_ubuntu20_TEST', defaultValue: false, - description: 'Run the Functional on Ubuntu 20.04 test stage' + - ' Requires CI_MORE_FUNCTIONAL_PR_TESTS') + description: 'Run the Functional on Ubuntu 20.04 test stage') booleanParam(name: 'CI_FUNCTIONAL_HARDWARE_TEST_SKIP', defaultValue: false, description: 'Skip Functional Hardware (Test Hardware) stage') @@ -529,7 +527,7 @@ pipeline { expression { !skip_build_stage() } } parallel { - stage('Build on EL 8.8') { + stage('Build on EL 8') { when { beforeAgent true expression { !skip_build_stage('el8') } @@ -544,7 +542,8 @@ pipeline { " -t ${sanitized_JOB_NAME()}-el8 " + ' --build-arg DAOS_PACKAGES_BUILD=no ' + ' --build-arg DAOS_KEEP_SRC=yes ' + - ' --build-arg REPOS="' + prRepos() + '"' + ' --build-arg REPOS="' + prRepos() + '"' + + ' --build-arg POINT_RELEASE=.10 ' } } steps { @@ -581,7 +580,7 @@ pipeline { } } } - stage('Build on EL 9.6') { + stage('Build on EL 9') { when { beforeAgent true expression { !skip_build_stage('el9') } @@ -597,7 +596,7 @@ pipeline { ' --build-arg DAOS_PACKAGES_BUILD=no ' + ' --build-arg DAOS_KEEP_SRC=yes ' + ' --build-arg REPOS="' + prRepos() + '"' + - ' --build-arg POINT_RELEASE=.6 ' + ' --build-arg POINT_RELEASE=.7 ' } } steps { @@ -634,7 +633,7 @@ pipeline { } } } - stage('Build on Leap 15.5') { + stage('Build on Leap 15') { when { beforeAgent true expression { !skip_build_stage('leap15') } @@ -684,7 +683,7 @@ pipeline { } } } - stage('Build on Leap 15.5 with Intel-C and TARGET_PREFIX') { + stage('Build on Leap 15 with Intel-C and TARGET_PREFIX') { when { beforeAgent true expression { !skip_build_stage('leap15', 'icc') } @@ -730,7 +729,7 @@ pipeline { expression { !skipStage() } } parallel { - stage('Unit Test on EL 8.8') { + stage('Unit Test') { when { beforeAgent true expression { !skipStage() } @@ -739,11 +738,14 @@ pipeline { label cachedCommitPragma(pragma: 'VM1-label', def_val: params.CI_UNIT_VM1_LABEL) } steps { - job_step_update( - unitTest(timeout_time: 60, - unstash_opt: true, - inst_repos: daosRepos(), - inst_rpms: unitPackages())) + job_step_update( + unitTest(timeout_time: 60, + unstash_opt: true, + inst_repos: daosRepos(), + inst_rpms: unitPackages(target: 'el9'), + image_version: 'el9.7', + ) + ) } post { always { @@ -752,7 +754,7 @@ pipeline { } } } - stage('Unit Test bdev on EL 8.8') { + stage('Unit Test bdev') { when { beforeAgent true expression { !skipStage() } @@ -765,7 +767,8 @@ pipeline { unitTest(timeout_time: 60, unstash_opt: true, inst_repos: daosRepos(), - inst_rpms: unitPackages())) + inst_rpms: unitPackages(target: 'el9'), + image_version: 'el9.7')) } post { always { @@ -774,7 +777,7 @@ pipeline { } } } - stage('NLT on EL 8.8') { + stage('NLT') { when { beforeAgent true expression { params.CI_NLT_TEST && !skipStage() } @@ -789,7 +792,8 @@ pipeline { test_script: 'ci/unit/test_nlt.sh', unstash_opt: true, unstash_tests: false, - inst_rpms: unitPackages())) + inst_rpms: unitPackages(target: 'el9'), + image_version: 'el9.7')) // recordCoverage(tools: [[parser: 'COBERTURA', pattern:'nltir.xml']], // skipPublishingChecks: true, // id: 'tlc', name: 'Fault Injection Interim Report') @@ -800,7 +804,7 @@ pipeline { unitTestPost artifacts: ['nlt_logs/'], testResults: 'nlt-junit.xml', always_script: 'ci/unit/test_nlt_post.sh', - valgrind_stash: 'el8-gcc-nlt-memcheck' + valgrind_stash: 'nlt-memcheck' recordIssues enabledForFailure: true, failOnError: false, ignoreQualityGate: true, @@ -814,7 +818,7 @@ pipeline { } } } - stage('Unit Test with memcheck on EL 8.8') { + stage('Unit Test with memcheck') { when { beforeAgent true expression { !skipStage() } @@ -828,18 +832,19 @@ pipeline { unstash_opt: true, ignore_failure: true, inst_repos: daosRepos(), - inst_rpms: unitPackages())) + inst_rpms: unitPackages(target: 'el9'), + image_version: 'el9.7')) } post { always { unitTestPost artifacts: ['unit_test_memcheck_logs.tar.gz', 'unit_test_memcheck_logs/**/*.log'], - valgrind_stash: 'el8-gcc-unit-memcheck' + valgrind_stash: 'unit-memcheck' job_status_update() } } - } // stage('Unit Test with memcheck on EL 8.8') - stage('Unit Test bdev with memcheck on EL 8.8') { + } // stage('Unit Test with memcheck') + stage('Unit Test bdev with memcheck') { when { beforeAgent true expression { !skipStage() } @@ -853,17 +858,18 @@ pipeline { unstash_opt: true, ignore_failure: true, inst_repos: daosRepos(), - inst_rpms: unitPackages())) + inst_rpms: unitPackages(target: 'el9'), + image_version: 'el9.7')) } post { always { unitTestPost artifacts: ['unit_test_memcheck_bdev_logs.tar.gz', 'unit_test_memcheck_bdev_logs/**/*.log'], - valgrind_stash: 'el8-gcc-unit-memcheck-bdev' + valgrind_stash: 'unit-bdev-memcheck' job_status_update() } } - } // stage('Unit Test bdev with memcheck on EL 8') + } // stage('Unit Test bdev with memcheck') } } stage('Test') { @@ -897,7 +903,7 @@ pipeline { } } } // stage('Functional on EL 8.8 with Valgrind') - stage('Functional on EL 8.8') { + stage('Functional on EL 8') { when { beforeAgent true expression { !skipStage() } @@ -911,7 +917,8 @@ pipeline { inst_repos: daosRepos(), inst_rpms: functionalPackages(1, next_version(), 'tests-internal') + ' mercury-libfabric', - test_function: 'runTestFunctionalV2')) + test_function: 'runTestFunctionalV2', + image_version: 'el8.10')) } post { always { @@ -919,7 +926,7 @@ pipeline { job_status_update() } } - } // stage('Functional on EL 8.8') + } // stage('Functional on EL 8') stage('Functional on EL 9') { when { beforeAgent true @@ -934,7 +941,8 @@ pipeline { inst_repos: daosRepos(), inst_rpms: functionalPackages(1, next_version(), 'tests-internal') + ' mercury-libfabric', - test_function: 'runTestFunctionalV2')) + test_function: 'runTestFunctionalV2', + image_version: 'el9.7')) } post { always { @@ -943,7 +951,7 @@ pipeline { } } } // stage('Functional on EL 9') - stage('Functional on Leap 15.6') { + stage('Functional on Leap 15') { when { beforeAgent true expression { !skipStage() } @@ -966,7 +974,7 @@ pipeline { job_status_update() } } // post - } // stage('Functional on Leap 15.6') + } // stage('Functional on Leap 15') stage('Functional on Ubuntu 20.04') { when { beforeAgent true @@ -990,18 +998,19 @@ pipeline { } } // post } // stage('Functional on Ubuntu 20.04') - stage('Fault injection testing on EL 8.8') { + stage('Fault injection testing') { when { beforeAgent true expression { !skipStage() } } agent { dockerfile { - filename 'utils/docker/Dockerfile.el.8' + filename 'utils/docker/Dockerfile.el.9' label 'docker_runner' additionalBuildArgs dockerBuildArgs(repo_type: 'stable', parallel_build: true, - deps_build: true) + deps_build: true) + + ' --build-arg POINT_RELEASE=.7 ' args '--tmpfs /mnt/daos_0' } } @@ -1040,16 +1049,16 @@ pipeline { stash name: 'fault-inject-valgrind', includes: '*.memcheck.xml', allowEmpty: true - archiveArtifacts artifacts: 'nlt_logs/el8.fault-injection/', + archiveArtifacts artifacts: 'nlt_logs/fault-injection/', allowEmptyArchive: true job_status_update() } } - } // stage('Fault injection testing on EL 8.8') - stage('Test RPMs on EL 8.6') { + } // stage('Fault injection testing') + stage('Test RPMs on EL 9.6') { when { beforeAgent true - expression { params.CI_TEST_EL8_RPMs && !skipStage() } + expression { params.CI_TEST_EL_RPMs && !skipStage() } } agent { label params.CI_UNIT_VM1_LABEL @@ -1066,11 +1075,11 @@ pipeline { rpm_test_post(env.STAGE_NAME, env.NODELIST) } } - } // stage('Test RPMs on EL 8.6') + } // stage('Test RPMs on EL 9.6') stage('Test RPMs on Leap 15.5') { when { beforeAgent true - expression { params.CI_TEST_LEAP15_RPMs && !skipStage() } + expression { params.CI_TEST_LEAP_RPMs && !skipStage() } } agent { label params.CI_UNIT_VM1_LABEL @@ -1259,8 +1268,8 @@ pipeline { } // stages post { always { - valgrindReportPublish valgrind_stashes: ['el8-gcc-nlt-memcheck', - 'el8-gcc-unit-memcheck', + valgrindReportPublish valgrind_stashes: ['nlt-memcheck', + 'unit-memcheck', 'fault-inject-valgrind'] job_status_update('final_status') jobStatusWrite(job_status_internal) diff --git a/ci/provisioning/post_provision_config_common_functions.sh b/ci/provisioning/post_provision_config_common_functions.sh index 5c5e2a50fbd..46fba4b21c2 100755 --- a/ci/provisioning/post_provision_config_common_functions.sh +++ b/ci/provisioning/post_provision_config_common_functions.sh @@ -100,7 +100,9 @@ retry_dnf() { if command -v dnf4; then dnfx="dnf4" fi - "$dnfx" config-manager --disable 'epel*' || true + if "$dnfx" repolist | grep -q '^epel'; then + "$dnfx" config-manager --disable 'epel*' + fi fi return 0 fi diff --git a/ci/provisioning/post_provision_config_nodes_EL.sh b/ci/provisioning/post_provision_config_nodes_EL.sh index c9257d87c22..d424ef5ccad 100644 --- a/ci/provisioning/post_provision_config_nodes_EL.sh +++ b/ci/provisioning/post_provision_config_nodes_EL.sh @@ -28,8 +28,5 @@ group_repo_post() { distro_custom() { # TODO: This code is not exiting on failure. - # Use a more recent python version for unit testing, this allows us to also test installing - # pydaos into virtual environments. - dnf -y install python39 python39-devel dnf -y install python3.11 python3.11-devel } diff --git a/ci/unit/required_packages.sh b/ci/unit/required_packages.sh index 5a57b0cb054..4790812779c 100755 --- a/ci/unit/required_packages.sh +++ b/ci/unit/required_packages.sh @@ -7,16 +7,17 @@ # set -eu -# No longer used but provided by pipeline-lib +# Provided by pipeline-lib # distro="$1" # quick_build="${2:-false}" OPENMPI_VER="" PY_MINOR_VER="" -export DISTRO="el8" # should also work for el9 -pkgs="$(utils/rpms/package_version.sh argobots lib) \ - boost-python3$PY_MINOR_VER-devel \ +DISTRO="${1:?ERROR: Missing distro argument. Usage: $0 }" +export DISTRO="${DISTRO%%.*}" + +pkgs="boost-python3$PY_MINOR_VER-devel \ capstone \ $(utils/rpms/package_version.sh argobots lib) \ $(utils/rpms/package_version.sh argobots debug) \ diff --git a/src/client/dfs/SConscript b/src/client/dfs/SConscript index b2526c5589a..906b1bfd7d4 100644 --- a/src/client/dfs/SConscript +++ b/src/client/dfs/SConscript @@ -18,7 +18,7 @@ def configure_lustre(denv): _print("No installed Lustre version detected") else: _print("Installed Lustre version detected") - if not denv.CheckFunc('llapi_unlink_foreign'): + if not conf.CheckFunc('llapi_unlink_foreign'): _print("Lustre version is not compatible") else: _print("Lustre version is compatible") diff --git a/src/include/daos_srv/rsvc.h b/src/include/daos_srv/rsvc.h index 7f66d66b329..d2105222380 100644 --- a/src/include/daos_srv/rsvc.h +++ b/src/include/daos_srv/rsvc.h @@ -1,6 +1,6 @@ /* * (C) Copyright 2019-2024 Intel Corporation. - * (C) Copyright 2025 Hewlett Packard Enterprise Development LP + * (C) Copyright 2025-2026 Hewlett Packard Enterprise Development LP * * SPDX-License-Identifier: BSD-2-Clause-Patent */ @@ -53,6 +53,9 @@ struct ds_rsvc_class { */ void (*sc_free)(struct ds_rsvc *svc); + /** Prepare for being inserted into the hash table. */ + int (*sc_insert)(struct ds_rsvc *svc); + /** * Bootstrap (i.e., initialize) the DB with the argument passed to * ds_rsvc_start. If supplied, this is called on a self-only service. diff --git a/src/placement/jump_map_versions.c b/src/placement/jump_map_versions.c index f817ed96b04..2480ac4893c 100644 --- a/src/placement/jump_map_versions.c +++ b/src/placement/jump_map_versions.c @@ -1,7 +1,7 @@ /** * * (C) Copyright 2016-2024 Intel Corporation. - * (C) Copyright 2025 Hewlett Packard Enterprise Development LP + * (C) Copyright 2025-2026 Hewlett Packard Enterprise Development LP * * SPDX-License-Identifier: BSD-2-Clause-Patent */ @@ -15,14 +15,11 @@ #include #include -/* NB: this function is to check if the component should be - * included in jump hash layout generation process, so those - * targets, which are in NEW status or being added afterwards - * should be excluded. +/* NB: this function checks if the component should be skipped in jump hash layout generation + * process, those components which are in NEW status or being added afterwards should be skipped. */ static bool -is_excluded_comp(struct pool_component *comp, uint32_t allow_version, - enum layout_gen_mode gen_mode) +comp_is_skipped(struct pool_component *comp, uint32_t allow_version, enum layout_gen_mode gen_mode) { if (comp->co_status == PO_COMP_ST_NEW) return true; @@ -36,13 +33,20 @@ is_excluded_comp(struct pool_component *comp, uint32_t allow_version, if (gen_mode != POST_REBUILD) return true; } - return false; } +#define dom_is_skipped(dom, allow_ver, gen_mode) \ + comp_is_skipped(&(dom)->do_comp, allow_ver, gen_mode) +#define tgt_is_skipped(tgt, allow_ver, gen_mode) \ + comp_is_skipped(&(tgt)->ta_comp, allow_ver, gen_mode) + +/* TODO: shouldn't compute each time, it can be very slow for extension of large pool. + * This should be optimized in the future. + */ static inline uint32_t -get_num_domains(struct pool_domain *curr_dom, uint32_t allow_version, - enum layout_gen_mode gen_mode, pool_comp_type_t fdom_lvl) +dom_avail_children(struct pool_domain *curr_dom, uint32_t allow_version, + enum layout_gen_mode gen_mode, pool_comp_type_t fdom_lvl) { struct pool_domain *next_dom; struct pool_target *next_target; @@ -54,19 +58,20 @@ get_num_domains(struct pool_domain *curr_dom, uint32_t allow_version, num_dom = curr_dom->do_child_nr; D_ASSERTF(num_dom > 0, "num dom %u\n", num_dom); + /* new children(domains/targets) are always appended to old children of the same parent, + * this is why it does backward search. + */ if (curr_dom->do_children == NULL || curr_dom->do_comp.co_type == fdom_lvl) { next_target = &curr_dom->do_targets[num_dom - 1]; - while (num_dom - 1 > 0 && is_excluded_comp(&next_target->ta_comp, allow_version, - gen_mode)) { + while (num_dom - 1 > 0 && tgt_is_skipped(next_target, allow_version, gen_mode)) { num_dom--; next_target = &curr_dom->do_targets[num_dom - 1]; } } else { next_dom = &curr_dom->do_children[num_dom - 1]; - while (num_dom - 1 > 0 && is_excluded_comp(&next_dom->do_comp, allow_version, - gen_mode)) { + while (num_dom - 1 > 0 && dom_is_skipped(next_dom, allow_version, gen_mode)) { num_dom--; next_dom = &curr_dom->do_children[num_dom - 1]; } @@ -81,13 +86,17 @@ tgt_isset_range(struct pool_target *tgts, uint8_t *tgts_used, uint32_t start_tgt { uint32_t index; - for (index = start_tgt; index <= end_tgt; ++index) { - if (is_excluded_comp(&tgts[index].ta_comp, allow_version, gen_mode)) + for (index = start_tgt; index <= end_tgt;) { + if (tgts_used[index >> 3] == 0xFF) { + index = (index | 7) + 1; /* jump to start of next byte */ continue; - if (isclr(tgts_used, index)) + } + + if (isclr(tgts_used, index) && + !tgt_is_skipped(&tgts[index], allow_version, gen_mode)) return false; + ++index; } - return true; } @@ -97,13 +106,17 @@ dom_isset_range(struct pool_domain *doms, uint8_t *doms_bits, uint32_t start_dom { uint32_t index; - for (index = start_dom; index <= end_dom; ++index) { - if (is_excluded_comp(&doms[index].do_comp, allow_version, gen_mode)) + for (index = start_dom; index <= end_dom;) { + if (doms_bits[index >> 3] == 0xFF) { + index = (index | 7) + 1; /* jump to start of next byte */ continue; - if (isclr(doms_bits, index)) + } + + if (isclr(doms_bits, index) && + !dom_is_skipped(&doms[index], allow_version, gen_mode)) return false; + ++index; } - return true; } @@ -114,19 +127,22 @@ dom_isset_2ranges(struct pool_domain *doms, uint8_t *doms_bits1, uint8_t *doms_b { uint32_t index; - for (index = start_dom; index <= end_dom; ++index) { - if (is_excluded_comp(&doms[index].do_comp, allow_version, gen_mode)) + for (index = start_dom; index <= end_dom;) { + if (doms_bits1[index >> 3] == 0xFF || doms_bits2[index >> 3] == 0xFF) { + index = (index | 7) + 1; /* jump to start of next byte */ continue; + } - if (isclr(doms_bits1, index) && isclr(doms_bits2, index)) + if (isclr(doms_bits1, index) && isclr(doms_bits2, index) && + !dom_is_skipped(&doms[index], allow_version, gen_mode)) return false; + ++index; } - return true; } static bool -is_dom_full(struct pool_domain *dom, struct pool_domain *root, uint8_t *dom_full, +dom_is_full(struct pool_domain *dom, struct pool_domain *root, uint8_t *dom_full, uint32_t allow_version, enum layout_gen_mode gen_mode) { uint32_t start_dom = dom->do_children - root; @@ -138,40 +154,6 @@ is_dom_full(struct pool_domain *dom, struct pool_domain *root, uint8_t *dom_full return false; } -struct pool_target * -_get_target(struct pool_domain *doms, uint32_t tgt_idx, uint32_t allow_version, - enum layout_gen_mode gen_mode) -{ - uint32_t idx = 0; - uint32_t i; - - for (i = 0; i < doms->do_target_nr; i++) { - if (!is_excluded_comp(&doms->do_targets[i].ta_comp, allow_version, gen_mode)) { - if (idx == tgt_idx) - return &doms->do_targets[idx]; - idx++; - } - } - return NULL; -} - -struct pool_domain * -_get_dom(struct pool_domain *doms, uint32_t dom_idx, uint32_t allow_version, - enum layout_gen_mode gen_mode) -{ - uint32_t idx = 0; - uint32_t i; - - for (i = 0; i < doms->do_child_nr; i++) { - if (!is_excluded_comp(&doms->do_children[i].do_comp, allow_version, gen_mode)) { - if (idx == dom_idx) - return &doms->do_children[idx]; - idx++; - } - } - return NULL; -} - /** * This function recursively chooses a single target to be used in the * object shard layout. This function is called for every shard that needs a @@ -222,15 +204,16 @@ __get_target_v1(struct pool_domain *root_pos, struct pool_domain *curr_pd, uint8_t found_target = 0; struct pool_domain *curr_dom; struct pool_domain *dom_stack[MAX_STACK] = { 0 }; + struct pool_target *tgt; int top = -1; obj_key = crc(obj_key, shard_num); curr_dom = curr_pd; do { - uint32_t avail_doms; + uint32_t children; /* sub-domains or targets */ /* Retrieve number of nodes in this domain */ - avail_doms = get_num_domains(curr_dom, allow_version, gen_mode, fdom_lvl); + children = dom_avail_children(curr_dom, allow_version, gen_mode, fdom_lvl); /* If choosing target (lowest fault domain level) */ if (curr_dom->do_comp.co_type == fdom_lvl) { @@ -259,16 +242,15 @@ __get_target_v1(struct pool_domain *root_pos, struct pool_domain *curr_pd, */ obj_key = crc(obj_key, fail_num++); /* Get target for shard */ - selected_tgt = d_hash_jump(obj_key, avail_doms); + selected_tgt = d_hash_jump(obj_key, children); do { - selected_tgt = selected_tgt % avail_doms; - /* Retrieve actual target using index */ - *target = _get_target(curr_dom, selected_tgt, allow_version, - gen_mode); + selected_tgt = selected_tgt % children; + tgt = &curr_dom->do_targets[selected_tgt]; /* Get target id to check if target used */ - tgt_idx = *target - root_pos->do_targets; + tgt_idx = tgt - root_pos->do_targets; selected_tgt++; } while (isset(tgts_used, tgt_idx)); + *target = tgt; setbit(tgts_used, tgt_idx); D_DEBUG(DB_PL, "selected tgt %d\n", tgt_idx); @@ -282,7 +264,7 @@ __get_target_v1(struct pool_domain *root_pos, struct pool_domain *curr_pd, D_DEBUG(DB_PL, "dom %d used up\n", (int)(curr_dom - root_pos)); /* Check and set if all of its parent are full */ while(top != -1) { - if (is_dom_full(dom_stack[top], root_pos, dom_full, + if (dom_is_full(dom_stack[top], root_pos, dom_full, allow_version, gen_mode)) { uint32_t off = dom_stack[top] - root_pos; @@ -309,8 +291,8 @@ __get_target_v1(struct pool_domain *root_pos, struct pool_domain *curr_pd, /* Check if all targets under the domain range has been * used up (occupied), go back to its parent if it does. */ - range_set = is_dom_full(curr_dom, root_pos, dom_full, allow_version, - gen_mode); + range_set = + dom_is_full(curr_dom, root_pos, dom_full, allow_version, gen_mode); if (range_set) { if (top == -1) { if (curr_pd != root_pos) { @@ -388,13 +370,8 @@ __get_target_v1(struct pool_domain *root_pos, struct pool_domain *curr_pd, /* Keep choosing the new domain until the one has not been used. */ do { - struct pool_domain *_dom; - - selected_dom = d_hash_jump(key, avail_doms); - key = crc(key, fail_num++); - _dom = _get_dom(curr_dom, selected_dom, allow_version, gen_mode); - if (_dom != NULL) - selected_dom = _dom - curr_dom->do_children; + selected_dom = d_hash_jump(key, children); + key = crc(key, fail_num++); } while (isset(dom_used, start_dom + selected_dom) || isset(dom_cur_grp_used, start_dom + selected_dom)); @@ -430,17 +407,17 @@ __get_target_v2(struct pool_domain *root_pos, struct pool_domain *curr_pd, { int range_set; uint8_t found_target = 0; + struct pool_target *tgt; struct pool_domain *curr_dom; struct pool_domain *dom_stack[MAX_STACK] = {0}; int top = -1; curr_dom = curr_pd; do { - uint32_t avail_doms; + uint32_t children; /* sub-domains or targets */ uint64_t key; - /* Retrieve number of nodes in this domain */ - avail_doms = get_num_domains(curr_dom, allow_version, gen_mode, fdom_lvl); + children = dom_avail_children(curr_dom, allow_version, gen_mode, fdom_lvl); /* If choosing target (lowest fault domain level) */ if (curr_dom->do_comp.co_type == fdom_lvl) { @@ -469,16 +446,15 @@ __get_target_v2(struct pool_domain *root_pos, struct pool_domain *curr_pd, */ key = jm_crc(obj_key, shard_num, fail_num++); /* Get target for shard */ - selected_tgt = d_hash_jump(key, avail_doms); + selected_tgt = d_hash_jump(key, children); do { - selected_tgt = selected_tgt % avail_doms; - /* Retrieve actual target using index */ - *target = - _get_target(curr_dom, selected_tgt, allow_version, gen_mode); + selected_tgt = selected_tgt % children; + tgt = &curr_dom->do_targets[selected_tgt]; /* Get target id to check if target used */ - tgt_idx = *target - root_pos->do_targets; + tgt_idx = tgt - root_pos->do_targets; selected_tgt++; } while (isset(tgts_used, tgt_idx)); + *target = tgt; setbit(tgts_used, tgt_idx); D_DEBUG(DB_PL, "selected tgt %d\n", tgt_idx); @@ -492,7 +468,7 @@ __get_target_v2(struct pool_domain *root_pos, struct pool_domain *curr_pd, D_DEBUG(DB_PL, "dom %d used up\n", (int)(curr_dom - root_pos)); /* Check and set if all of its parent are full */ while (top != -1) { - if (is_dom_full(dom_stack[top], root_pos, dom_full, + if (dom_is_full(dom_stack[top], root_pos, dom_full, allow_version, gen_mode)) { uint32_t off = dom_stack[top] - root_pos; @@ -519,7 +495,7 @@ __get_target_v2(struct pool_domain *root_pos, struct pool_domain *curr_pd, * used up (occupied), go back to its parent if it does. */ range_set = - is_dom_full(curr_dom, root_pos, dom_full, allow_version, gen_mode); + dom_is_full(curr_dom, root_pos, dom_full, allow_version, gen_mode); if (range_set) { if (top == -1) { if (curr_pd != root_pos) { @@ -598,13 +574,8 @@ __get_target_v2(struct pool_domain *root_pos, struct pool_domain *curr_pd, /* Keep choosing the new domain until the one has not been used. */ do { - struct pool_domain *_dom; - key = jm_crc(obj_key, shard_num, fail_num++); - selected_dom = d_hash_jump(key, avail_doms); - _dom = _get_dom(curr_dom, selected_dom, allow_version, gen_mode); - if (_dom != NULL) - selected_dom = _dom - curr_dom->do_children; + selected_dom = d_hash_jump(key, children); } while (isset(dom_used, start_dom + selected_dom) || isset(dom_cur_grp_used, start_dom + selected_dom)); @@ -926,10 +897,9 @@ get_target_v0(struct pool_domain *curr_dom, struct pool_target **target, dom_size = (struct pool_domain *)(root_pos->do_targets) - (root_pos) + 1; retry: do { - uint32_t num_doms; + uint32_t children; /* sub-components or targets */ - /* Retrieve number of nodes in this domain */ - num_doms = get_num_domains(curr_dom, allow_version, gen_mode, fdom_lvl); + children = dom_avail_children(curr_dom, allow_version, gen_mode, fdom_lvl); /* If choosing target (lowest fault domain level) */ if (curr_dom->do_children == NULL || curr_dom->do_comp.co_type == fdom_lvl) { @@ -939,7 +909,7 @@ get_target_v0(struct pool_domain *curr_dom, struct pool_target **target, uint32_t end_tgt; start_tgt = curr_dom->do_targets - root_pos->do_targets; - end_tgt = start_tgt + (num_doms - 1); + end_tgt = start_tgt + (children - 1); range_set = isset_range(tgts_used, start_tgt, end_tgt); if (range_set) { @@ -956,9 +926,9 @@ get_target_v0(struct pool_domain *curr_dom, struct pool_target **target, */ obj_key = crc(obj_key, fail_num++); /* Get target for shard */ - selected_dom = d_hash_jump(obj_key, num_doms); + selected_dom = d_hash_jump(obj_key, children); do { - selected_dom = selected_dom % num_doms; + selected_dom = selected_dom % children; /* Retrieve actual target using index */ *target = &curr_dom->do_targets[selected_dom]; /* Get target id to check if target used */ @@ -987,7 +957,7 @@ get_target_v0(struct pool_domain *curr_dom, struct pool_target **target, key = obj_key; start_dom = (curr_dom->do_children) - root_pos; - end_dom = start_dom + (num_doms - 1); + end_dom = start_dom + (children - 1); /* Check if all targets under the domain range has been * used up (occupied), go back to its parent if it does. @@ -1076,7 +1046,7 @@ get_target_v0(struct pool_domain *curr_dom, struct pool_target **target, * not been used is found */ do { - selected_dom = d_hash_jump(key, num_doms); + selected_dom = d_hash_jump(key, children); key = crc(key, fail_num++); } while (isset(dom_used, start_dom + selected_dom)); diff --git a/src/pool/srv_pool.c b/src/pool/srv_pool.c index b7ada57c251..c2a59cec0a9 100644 --- a/src/pool/srv_pool.c +++ b/src/pool/srv_pool.c @@ -1910,6 +1910,20 @@ pool_svc_free_cb(struct ds_rsvc *rsvc) D_FREE(svc); } +static int +pool_svc_insert_cb(struct ds_rsvc *rsvc) +{ + struct pool_svc *svc = pool_svc_obj(rsvc); + + /* + * While we were starting svc, there might be a ds_pool_stop call who + * is waiting for us to put svc->ps_pool. + */ + if (svc->ps_pool->sp_stopping) + return -DER_CANCELED; + return 0; +} + /* * Update svc->ps_pool with map_buf and map_version. This ensures that * svc->ps_pool matches the latest pool map. @@ -2727,16 +2741,19 @@ pool_svc_map_dist_cb(struct ds_rsvc *rsvc, uint32_t *version) return rc; } +/* clang-format off */ static struct ds_rsvc_class pool_svc_rsvc_class = { .sc_name = pool_svc_name_cb, .sc_locate = pool_svc_locate_cb, .sc_alloc = pool_svc_alloc_cb, .sc_free = pool_svc_free_cb, + .sc_insert = pool_svc_insert_cb, .sc_step_up = pool_svc_step_up_cb, .sc_step_down = pool_svc_step_down_cb, .sc_drain = pool_svc_drain_cb, .sc_map_dist = pool_svc_map_dist_cb }; +/* clang-format on */ void ds_pool_rsvc_class_register(void) diff --git a/src/rsvc/srv.c b/src/rsvc/srv.c index 3f2b599eb2b..6cedab28ba8 100644 --- a/src/rsvc/srv.c +++ b/src/rsvc/srv.c @@ -1,6 +1,6 @@ /* * (C) Copyright 2019-2024 Intel Corporation. - * (C) Copyright 2025 Hewlett Packard Enterprise Development LP + * (C) Copyright 2025-2026 Hewlett Packard Enterprise Development LP * * SPDX-License-Identifier: BSD-2-Clause-Patent */ @@ -1042,10 +1042,19 @@ ds_rsvc_start(enum ds_rsvc_class_id class, d_iov_t *id, uuid_t db_uuid, uint64_t if (rc != 0) goto out; + if (rsvc_class(class)->sc_insert != NULL) { + rc = rsvc_class(class)->sc_insert(svc); + if (rc != 0) { + D_DEBUG(DB_MD, "%s: sc_insert: " DF_RC "\n", svc->s_name, DP_RC(rc)); + goto err_svc_started; + } + } + rc = d_hash_rec_insert(&rsvc_hash, svc->s_id.iov_buf, svc->s_id.iov_len, &svc->s_entry, true /* exclusive */); if (rc != 0) { D_DEBUG(DB_MD, "%s: insert: "DF_RC"\n", svc->s_name, DP_RC(rc)); +err_svc_started: stop(svc, mode == DS_RSVC_CREATE /* destroy */); goto out; } diff --git a/src/tests/ftest/object/create_many_dkeys.py b/src/tests/ftest/object/create_many_dkeys.py deleted file mode 100644 index 47f74017b9e..00000000000 --- a/src/tests/ftest/object/create_many_dkeys.py +++ /dev/null @@ -1,104 +0,0 @@ -''' - (C) Copyright 2018-2023 Intel Corporation. - - SPDX-License-Identifier: BSD-2-Clause-Patent -''' -import ctypes -import sys - -import avocado -from apricot import TestWithServers -from general_utils import create_string_buffer -from pydaos.raw import DaosApiError, IORequest -from test_utils_container import add_container -from test_utils_pool import add_pool - - -class CreateManyDkeys(TestWithServers): - """ - Test Class Description: - Tests that create large numbers of keys in objects/containers and then - destroy the containers and verify the space has been reclaimed. - - :avocado: recursive - """ - - def write_a_bunch_of_values(self, pool, how_many): - """Write data to an object, each with a dkey and akey. - - Args: - pool (TestPool): the pool in which to write data - how_many (int): how many key:value pairs are written - """ - self.log_step("Creating a container") - container = add_container(self, pool) - container.open() - - ioreq = IORequest(self.context, container.container, None) - - self.log_step("Writing the dataset") - inc = 50000 - last_key = inc - for key in range(how_many): - c_dkey = create_string_buffer("dkey {0}".format(key)) - c_akey = create_string_buffer("akey {0}".format(key)) - c_value = create_string_buffer( - "some data that gets stored with the key {0}".format(key)) - c_size = ctypes.c_size_t(ctypes.sizeof(c_value)) - ioreq.single_insert(c_dkey, - c_akey, - c_value, - c_size) - - if key > last_key: - print("written: {}".format(key)) - sys.stdout.flush() - last_key = key + inc - - self.log_step("Verifying the dataset") - last_key = inc - for key in range(how_many): - c_dkey = create_string_buffer("dkey {0}".format(key)) - c_akey = create_string_buffer("akey {0}".format(key)) - the_data = "some data that gets stored with the key {0}".format(key) - val = ioreq.single_fetch(c_dkey, c_akey, len(the_data) + 1) - exp_value = val.value.decode("utf-8") - if the_data != exp_value: - self.log.debug("Expected Value: %s", the_data) - self.log.debug("Received Value: %s", exp_value) - self.fail("ERROR: Data mismatch for dkey='dkey {key}', akey='akey {key}'") - - if key > last_key: - print("verified: {}".format(key)) - sys.stdout.flush() - last_key = key + inc - - self.log_step("Destroying the container") - container.destroy() - - @avocado.fail_on(DaosApiError) - def test_many_dkeys(self): - """ - Test ID: DAOS-1701 - Test Description: Test many of dkeys in same object. - Use Cases: 1. large key counts - 2. space reclamation after destroy - - :avocado: tags=all,full_regression - :avocado: tags=vm - :avocado: tags=object - :avocado: tags=CreateManyDkeys,test_many_dkeys - """ - no_of_dkeys = self.params.get("number_of_dkeys", '/run/dkeys/') - - self.log_step("Creating a pool") - pool = add_pool(self) - - # write a lot of individual data items, verify them, then destroy - self.write_a_bunch_of_values(pool, no_of_dkeys) - - # do it again, which should verify the first container - # was truly destroyed because a second round won't fit otherwise - self.write_a_bunch_of_values(pool, no_of_dkeys) - - self.log.info('Test passed') diff --git a/src/tests/ftest/object/create_many_dkeys.yaml b/src/tests/ftest/object/create_many_dkeys.yaml deleted file mode 100644 index f9206d654f8..00000000000 --- a/src/tests/ftest/object/create_many_dkeys.yaml +++ /dev/null @@ -1,20 +0,0 @@ -hosts: - test_servers: 2 - test_clients: 1 -timeout: 3600 -server_config: - name: daos_server - engines_per_host: 1 - engines: - 0: - targets: 4 - nr_xs_helpers: 0 - storage: - 0: - class: ram - scm_mount: /mnt/daos - system_ram_reserved: 1 -pool: - scm_size: 3G -dkeys: - number_of_dkeys: 1000000 diff --git a/src/tests/ftest/pool/create.yaml b/src/tests/ftest/pool/create.yaml index 87a5a385fab..004f750b040 100644 --- a/src/tests/ftest/pool/create.yaml +++ b/src/tests/ftest/pool/create.yaml @@ -6,7 +6,7 @@ timeouts: test_create_max_pool_scm_only: 180 test_create_max_pool: 300 test_create_no_space: 300 - test_create_no_space_loop: 3700 + test_create_no_space_loop: 4100 server_config: name: daos_server diff --git a/src/tests/ftest/recovery/ddb.py b/src/tests/ftest/recovery/ddb.py index 21ac91230b4..1ca71e71091 100644 --- a/src/tests/ftest/recovery/ddb.py +++ b/src/tests/ftest/recovery/ddb.py @@ -10,6 +10,8 @@ from apricot import TestWithServers from ddb_utils import DdbCommand +from exception_utils import CommandFailure +from file_utils import distribute_files from general_utils import create_string_buffer, get_random_string, report_errors from pydaos.raw import DaosObjClass, IORequest from run_utils import command_as_user, run_remote @@ -388,7 +390,7 @@ def test_recovery_ddb_rm(self): self.log_step("Call ddb rm to remove the akey.") if md_on_ssd: # "ddb rm" command for MD-on-SSD is quite different. - # PMEM: ddb /mnt/daos//vos-0 rm + # PMEM: ddb -w /mnt/daos//vos-0 rm # MD-on-SSD: ddb -w --db_path=/var/tmp/daos_testing/control_metadata/daos_control # /engine0 --vos_path /mnt/daos_load//vos-0 rm ddb_command.db_path.update(value=" ".join(["--db_path", db_path])) @@ -484,3 +486,125 @@ def test_recovery_ddb_rm(self): self.run_cmd_check_result(command=f"rm -rf {daos_load_path}") report_errors(test=self, errors=errors) + + def test_recovery_ddb_load(self): + """Test ddb value_load. + + 1. Create a pool and a container. + 2. Insert one object with one dkey with the API. + 3. Stop the server to use ddb. + 4. Find the vos file name. e.g., /mnt/daos0//vos-0. + 5. Load new data into [0]/[0]/[0]/[0] + 6. Restart the server. + 7. Reset the object, container, and pool to use the API. + 8. Verify the data in the akey with single_fetch(). + + :avocado: tags=all,full_regression + :avocado: tags=hw,medium + :avocado: tags=recovery + :avocado: tags=DdbTest,ddb_cmd,test_recovery_ddb_load + """ + # This is where we load pool for MD-on-SSD. It's called tmpfs_mount in ddb prov_mem + # documentation, but use daos_load_path here for clarity. + daos_load_path = "/mnt/daos_load" + md_on_ssd = self.server_managers[0].manager.job.using_control_metadata + if md_on_ssd: + self.log_step("MD-on-SSD: Create a directory to load pool data under /mnt.") + self.run_cmd_check_result(command=f"mkdir {daos_load_path}") + + self.log_step("Create a pool and a container.") + pool = self.get_pool(connect=True) + container = self.get_container(pool) + + if md_on_ssd: + vos_path = '""' + else: + # Find the vos file name. e.g., /mnt/daos0//vos-0. + vos_paths = self.server_managers[0].get_vos_files(pool) + if not vos_paths: + self.fail("vos file wasn't found!") + vos_path = vos_paths[0] + + ddb_command = DdbCommand( + server_host=self.server_managers[0].hosts[0], path=self.bin, vos_path=vos_path) + + self.log_step("Insert one object with one dkey with API.") + obj_dataset = insert_objects( + context=self.context, container=container, object_count=1, dkey_count=1, akey_count=1, + base_dkey=self.random_dkey, base_akey=self.random_akey, base_data=self.random_data) + ioreqs = obj_dataset[0] + dkeys_inserted = obj_dataset[1] + akeys_inserted = obj_dataset[2] + data_list = obj_dataset[3] + + # For debugging/reference, call single_fetch and get the data just inserted. + # Pass in size + 1 to single_fetch to avoid the no-space error. + data_size = len(data_list[0]) + 1 + data = ioreqs[0].single_fetch( + dkey=dkeys_inserted[0], akey=akeys_inserted[0], size=data_size) + self.log.info("data (before) = %s", data.value.decode('utf-8')) + + self.log_step("Stop the server to use ddb.") + dmg_command = self.get_dmg_command() + dmg_command.system_stop() + + db_path = None + if md_on_ssd: + self.log_step(f"MD-on-SSD: Load pool dir to {daos_load_path}") + db_path = os.path.join( + self.server_managers[0].manager.job.yaml.metadata_params.path.value, "daos_control", + "engine0") + ddb_command.prov_mem(db_path=db_path, tmpfs_mount=daos_load_path) + + self.log_step("Load new data into [0]/[0]/[0]/[0]; Create a file in test node.") + load_file_path = os.path.join(self.test_dir, "new_data.txt") + new_data = get_random_string(20) + with open(load_file_path, "w", encoding="utf-8") as file: + file.write(new_data) + + self.log_step("Copy the created file to server node.") + result = distribute_files( + self.log, self.server_managers[0].hosts[0], load_file_path, load_file_path, False) + if not result.passed: + raise CommandFailure(f"ERROR: Copying new_data.txt to {result.failed_hosts}") + + self.log_step("The file with the new data is ready. Run ddb load.") + if md_on_ssd: + # "ddb value_load" command for MD-on-SSD is quite different. + # PMEM: ddb -w /mnt/daos//vos-0 value_load + # MD-on-SSD: ddb -w --db_path=/var/tmp/daos_testing/control_metadata/daos_control + # /engine0 --vos_path /mnt/daos_load//vos-0 value_load + # + ddb_command.db_path.update(value=" ".join(["--db_path", db_path])) + ddb_command.vos_path.update( + value=os.path.join(daos_load_path, pool.uuid.lower(), "vos-0")) + ddb_command.value_load(component_path="[0]/[0]/[0]/[0]", load_file_path=load_file_path) + + self.log_step("Restart the server.") + dmg_command.system_start() + + self.log_step("Reset the object, container, and pool to use the API after server restart.") + ioreqs[0].obj.close() + container.close() + pool.disconnect() + pool.connect() + container.open() + ioreqs[0].obj.open() + + self.log_step("Verify the data in the akey with single_fetch().") + data_size = len(new_data) + 1 + data = ioreqs[0].single_fetch( + dkey=dkeys_inserted[0], akey=akeys_inserted[0], size=data_size) + actual_data = data.value.decode('utf-8') + self.log.info("data (after) = %s", actual_data) + errors = [] + if new_data != actual_data: + msg = f"ddb load failed! Expected = {new_data}; Actual = {actual_data}" + errors.append(msg) + + if md_on_ssd: + self.log_step(f"MD-on-SSD: Clean {daos_load_path}") + self.run_cmd_check_result(command=f"umount {daos_load_path}") + self.run_cmd_check_result(command=f"rm -rf {daos_load_path}") + + report_errors(test=self, errors=errors) diff --git a/src/tests/ftest/recovery/ddb_pmem.py b/src/tests/ftest/recovery/ddb_pmem.py index 5c3f5f80df2..d2b1b12b000 100644 --- a/src/tests/ftest/recovery/ddb_pmem.py +++ b/src/tests/ftest/recovery/ddb_pmem.py @@ -9,8 +9,6 @@ from apricot import TestWithServers from ddb_utils import DdbCommand -from exception_utils import CommandFailure -from file_utils import distribute_files from general_utils import (DaosTestError, create_string_buffer, get_random_string, report_errors, run_command) from pydaos.raw import DaosObjClass, IORequest @@ -117,92 +115,6 @@ def __init__(self, *args, **kwargs): self.random_akey = get_random_string(10) self.random_data = get_random_string(10) - def test_recovery_ddb_load(self): - """Test ddb value_load. - - 1. Create a pool and a container. - 2. Insert one object with one dkey with the API. - 3. Stop the server to use ddb. - 4. Find the vos file name. e.g., /mnt/daos0//vos-0. - 5. Load new data into [0]/[0]/[0]/[0] - 6. Restart the server. - 7. Reset the object, container, and pool to use the API. - 8. Verify the data in the akey with single_fetch(). - - :avocado: tags=all,full_regression - :avocado: tags=vm - :avocado: tags=recovery - :avocado: tags=DdbPMEMTest,ddb_cmd,test_recovery_ddb_load - """ - self.log_step("Create a pool and a container.") - pool = self.get_pool(connect=True) - container = self.get_container(pool) - - self.log_step("Insert one object with one dkey with API.") - obj_dataset = insert_objects( - context=self.context, container=container, object_count=1, dkey_count=1, akey_count=1, - base_dkey=self.random_dkey, base_akey=self.random_akey, base_data=self.random_data) - ioreqs = obj_dataset[0] - dkeys_inserted = obj_dataset[1] - akeys_inserted = obj_dataset[2] - data_list = obj_dataset[3] - - # For debugging/reference, call single_fetch and get the data just inserted. - # Pass in size + 1 to single_fetch to avoid the no-space error. - data_size = len(data_list[0]) + 1 - data = ioreqs[0].single_fetch( - dkey=dkeys_inserted[0], akey=akeys_inserted[0], size=data_size) - self.log.info("data (before) = %s", data.value.decode('utf-8')) - - self.log_step("Stop the server to use ddb.") - dmg_command = self.get_dmg_command() - dmg_command.system_stop() - - self.log_step("Find the vos file name.") - host = self.server_managers[0].hosts[0:1] - vos_paths = self.server_managers[0].get_vos_files(pool) - if not vos_paths: - self.fail("vos file wasn't found!") - ddb_command = DdbCommand(host, self.bin, vos_paths[0]) - - self.log_step("Load new data into [0]/[0]/[0]/[0]; Create a file in test node.") - load_file_path = os.path.join(self.test_dir, "new_data.txt") - new_data = get_random_string(20) - with open(load_file_path, "w", encoding="utf-8") as file: - file.write(new_data) - - self.log_step("Copy the created file to server node.") - result = distribute_files(self.log, host, load_file_path, load_file_path, False) - if not result.passed: - raise CommandFailure(f"ERROR: Copying new_data.txt to {result.failed_hosts}") - - self.log_step("The file with the new data is ready. Run ddb load.") - ddb_command.value_load(component_path="[0]/[0]/[0]/[0]", load_file_path=load_file_path) - - self.log_step("Restart the server.") - dmg_command.system_start() - - self.log_step("Reset the object, container, and pool to use the API after server restart.") - ioreqs[0].obj.close() - container.close() - pool.disconnect() - pool.connect() - container.open() - ioreqs[0].obj.open() - - self.log_step("Verify the data in the akey with single_fetch().") - data_size = len(new_data) + 1 - data = ioreqs[0].single_fetch( - dkey=dkeys_inserted[0], akey=akeys_inserted[0], size=data_size) - actual_data = data.value.decode('utf-8') - self.log.info("data (after) = %s", actual_data) - errors = [] - if new_data != actual_data: - msg = f"ddb load failed! Expected = {new_data}; Actual = {actual_data}" - errors.append(msg) - - report_errors(test=self, errors=errors) - def test_recovery_ddb_dump_value(self): """Test ddb dump_value. diff --git a/src/utils/ddb/ddb_commands.c b/src/utils/ddb/ddb_commands.c index 77553fe2bd2..73fce057cd4 100644 --- a/src/utils/ddb/ddb_commands.c +++ b/src/utils/ddb/ddb_commands.c @@ -306,7 +306,7 @@ print_value_cb(void *cb_args, d_iov_t *value) return 0; } - ddb_iov_to_printable_buf(value, buf, ARRAY_SIZE(buf)); + ddb_iov_to_printable_buf(value, buf, ARRAY_SIZE(buf), NULL); ddb_printf(ctx, "Value (size: %lu):\n", value->iov_len); ddb_printf(ctx, "%s\n", buf); return 0; diff --git a/src/utils/ddb/ddb_parse.h b/src/utils/ddb/ddb_parse.h index 439791823a2..fad1a828fab 100644 --- a/src/utils/ddb/ddb_parse.h +++ b/src/utils/ddb/ddb_parse.h @@ -44,7 +44,7 @@ void ddb_str2argv_free(struct argv_parsed *parse_args); int ddb_parse_program_args(struct ddb_ctx *ctx, uint32_t argc, char **argv, struct program_args *pa); -/* See ddb_iov_to_printable_buf for how the keys will be printed */ +/* See ddb_key_to_printable_buf for how the keys will be printed */ int ddb_parse_key(const char *input, daos_key_t *key); /* Parse a string into the parts of a dtx_id. See DF_DTIF for how the format of the dtx_id is diff --git a/src/utils/ddb/ddb_printer.c b/src/utils/ddb/ddb_printer.c index dd78efbb481..60c4be27da4 100644 --- a/src/utils/ddb/ddb_printer.c +++ b/src/utils/ddb/ddb_printer.c @@ -24,69 +24,91 @@ ddb_can_print(d_iov_t *iov) uint32_t len = iov->iov_len; int i; - for (i = 0 ; i < len ; i++) { + for (i = 0; i < len; i++) { if (str[i] == '\0') return true; - if (!isprint(str[i]) && str[i] != '\n' && str[i] != '\r') + if (!isprint(str[i])) return false; } return true; } /* - * Converts contents of an iov to something that is more printable. + * Converts contents of an @iov to something that is more printable. * - * Returns number of characters that would have been written if buf_len was long - * enough, not including null terminator + * Returns number of characters that would have been written if @buf is large enough, + * not including the null terminator. */ -int -ddb_iov_to_printable_buf(d_iov_t *iov, char buf[], uint32_t buf_len) +uint32_t +ddb_iov_to_printable_buf(d_iov_t *iov, char buf[], uint32_t buf_len, const char *prefix) { + char tmp[32]; + uint32_t new_len; + uint32_t result = 0; + int i; + if (iov->iov_len == 0 || iov->iov_buf == NULL) return 0; if (ddb_can_print(iov)) return snprintf(buf, buf_len, "%.*s", (int)iov->iov_len, (char *)iov->iov_buf); - switch (iov->iov_len) { - case sizeof(uint8_t): - return snprintf(buf, buf_len, "uint8:0x%x", ((uint8_t *)iov->iov_buf)[0]); - case sizeof(uint16_t): - return snprintf(buf, buf_len, "uint16:0x%04hx", ((uint16_t *)iov->iov_buf)[0]); - case sizeof(uint32_t): - return snprintf(buf, buf_len, "uint32:0x%x", ((uint32_t *)iov->iov_buf)[0]); - case sizeof(uint64_t): - return snprintf(buf, buf_len, "uint64:0x%lx", ((uint64_t *)iov->iov_buf)[0]); - default: - { - char tmp_buf[32]; - uint32_t new_len; - uint32_t result = 0; - int i; - - result += snprintf(buf, buf_len, "bin(%lu):0x", iov->iov_len); - - for (i = 0; i < iov->iov_len; i++) { - new_len = snprintf(tmp_buf, ARRAY_SIZE(tmp_buf), "%02x", - ((uint8_t *)iov->iov_buf)[i]); - if (new_len + result > buf_len) { - /* Buffer not big enough */ - result += new_len; - } else { - result += sprintf(buf + result, "%s", tmp_buf); - } - } + if (prefix != NULL) + result = snprintf(buf, buf_len, "%s", prefix); - if (result > buf_len) { - buf[buf_len - 1] = '\0'; - buf[buf_len - 2] = '.'; - buf[buf_len - 3] = '.'; - buf[buf_len - 4] = '.'; + for (i = 0; i < iov->iov_len; i++) { + new_len = snprintf(tmp, ARRAY_SIZE(tmp), "%02x", ((uint8_t *)iov->iov_buf)[i]); + if (new_len + result > buf_len) + result += new_len; /* Buffer is not big enough. */ + else + result += sprintf(buf + result, "%s", tmp); + } - } - return result; + if (result > buf_len) { + buf[buf_len - 1] = '\0'; + for (i = 2; buf_len >= i && i <= 4; i++) + buf[buf_len - i] = '.'; } + + return result; +} + +/* + * Converts contents of an @key to something that is more printable. + * + * Returns number of characters that would have been written if @buf is long enough, + * not including the null terminator. + */ +uint32_t +ddb_key_to_printable_buf(daos_key_t *key, enum daos_otype_t otype, char buf[], uint32_t buf_len) +{ + char tmp[32]; + + if (key->iov_len == 0 || key->iov_buf == NULL) + return 0; + + if (ddb_key_is_lexical(otype)) + return snprintf(buf, buf_len, "%.*s", (int)key->iov_len, (char *)key->iov_buf); + + if (ddb_key_is_int(otype)) { + switch (key->iov_len) { + case sizeof(uint8_t): + return snprintf(buf, buf_len, "uint8:0x%x", ((uint8_t *)key->iov_buf)[0]); + case sizeof(uint16_t): + return snprintf(buf, buf_len, "uint16:0x%04hx", + ((uint16_t *)key->iov_buf)[0]); + case sizeof(uint32_t): + return snprintf(buf, buf_len, "uint32:0x%x", ((uint32_t *)key->iov_buf)[0]); + case sizeof(uint64_t): + return snprintf(buf, buf_len, "uint64:0x%lx", + ((uint64_t *)key->iov_buf)[0]); + /* Fall through. */ + } } + + snprintf(tmp, ARRAY_SIZE(tmp), "bin(%lu):0x", key->iov_len); + + return ddb_iov_to_printable_buf(key, buf, buf_len, tmp); } void @@ -115,22 +137,22 @@ ddb_print_key(struct ddb_ctx *ctx, struct ddb_key *key, uint32_t indent) memset(buf, 0, buf_len); - ddb_iov_to_printable_buf(&key->ddbk_key, buf, buf_len); + ddb_key_to_printable_buf(&key->ddbk_key, key->ddbk_otype, buf, buf_len); print_indent(ctx, indent); - if (ddb_can_print(&key->ddbk_key)) { - ddb_printf(ctx, DF_IDX" '%s' (%lu)%s\n", - DP_IDX(key->ddbk_idx), - buf, - key->ddbk_key.iov_len, - key->ddbk_child_type == VOS_ITER_SINGLE ? " (SV)" : - key->ddbk_child_type == VOS_ITER_RECX ? " (ARRAY)" : ""); - return; - } - ddb_printf(ctx, DF_IDX" {%s}%s\n", DP_IDX(key->ddbk_idx), buf, - key->ddbk_child_type == VOS_ITER_SINGLE ? " (SV)" : - key->ddbk_child_type == VOS_ITER_RECX ? " (ARRAY)" : ""); + if (ddb_key_is_lexical(key->ddbk_otype) || + (!ddb_key_is_int(key->ddbk_otype) && ddb_can_print(&key->ddbk_key))) + ddb_printf(ctx, DF_IDX " '%s' (%lu)%s\n", DP_IDX(key->ddbk_idx), buf, + key->ddbk_key.iov_len, + key->ddbk_child_type == VOS_ITER_SINGLE ? " (SV)" + : key->ddbk_child_type == VOS_ITER_RECX ? " (ARRAY)" + : ""); + else + ddb_printf(ctx, DF_IDX " {%s}%s\n", DP_IDX(key->ddbk_idx), buf, + key->ddbk_child_type == VOS_ITER_SINGLE ? " (SV)" + : key->ddbk_child_type == VOS_ITER_RECX ? " (ARRAY)" + : ""); } void diff --git a/src/utils/ddb/ddb_printer.h b/src/utils/ddb/ddb_printer.h index 5c6cc5a7332..642b1fcf156 100644 --- a/src/utils/ddb/ddb_printer.h +++ b/src/utils/ddb/ddb_printer.h @@ -1,5 +1,6 @@ /** * (C) Copyright 2022 Intel Corporation. + * (C) Copyright 2025-2026 Hewlett Packard Enterprise Development LP * * SPDX-License-Identifier: BSD-2-Clause-Patent */ @@ -12,7 +13,10 @@ #define DF_IDX "[%d]" #define DP_IDX(idx) idx -int ddb_iov_to_printable_buf(d_iov_t *iov, char buf[], uint32_t buf_len); +uint32_t +ddb_iov_to_printable_buf(d_iov_t *iov, char buf[], uint32_t buf_len, const char *prefix); +uint32_t +ddb_key_to_printable_buf(daos_key_t *key, enum daos_otype_t otype, char buf[], uint32_t buf_len); void ddb_print_cont(struct ddb_ctx *ctx, struct ddb_cont *cont); void ddb_print_obj(struct ddb_ctx *ctx, struct ddb_obj *obj, uint32_t indent); void ddb_print_key(struct ddb_ctx *ctx, struct ddb_key *key, uint32_t indent); @@ -29,5 +33,16 @@ bool ddb_can_print(d_iov_t *iov); /* some utility functions helpful for printing */ void ddb_bytes_hr(uint64_t bytes, char *buf, uint32_t buf_len); +static inline bool +ddb_key_is_lexical(enum daos_otype_t otype) +{ + return daos_is_dkey_lexical_type(otype) || daos_is_akey_lexical_type(otype); +} + +static inline bool +ddb_key_is_int(enum daos_otype_t otype) +{ + return daos_is_dkey_uint64_type(otype) || daos_is_akey_uint64_type(otype); +} #endif /* DAOS_DDB_PRINTER_H */ diff --git a/src/utils/ddb/ddb_tree_path.c b/src/utils/ddb/ddb_tree_path.c index 3bfc104424e..20fa553f2bd 100644 --- a/src/utils/ddb/ddb_tree_path.c +++ b/src/utils/ddb/ddb_tree_path.c @@ -304,58 +304,55 @@ itp_parse(const char *path, struct dv_indexed_tree_path *itp) } bool -itp_part_set_cont(union itp_part_type *part, void *part_value) +itp_part_set_cont(struct indexed_tree_path_part *part, void *part_value) { const uint8_t *cont_uuid = part_value; if (cont_uuid == NULL || uuid_is_null(cont_uuid)) return false; - uuid_copy(part->itp_uuid, cont_uuid); + uuid_copy(part->itp_part_value.itp_uuid, cont_uuid); return true; } bool -itp_part_set_obj(union itp_part_type *part, void *part_value) +itp_part_set_obj(struct indexed_tree_path_part *part, void *part_value) { daos_unit_oid_t *oid = part_value; if (daos_unit_oid_is_null(*oid)) return false; - part->itp_oid = *oid; + part->itp_part_value.itp_oid = *oid; + part->itp_otype = daos_obj_id2type(oid->id_pub); return true; } bool -itp_part_set_key(union itp_part_type *part, void *part_value) +itp_part_set_key(struct indexed_tree_path_part *part, void *part_value) { daos_key_t *key = part_value; if (key->iov_len == 0) return false; - daos_iov_copy(&part->itp_key, key); + daos_iov_copy(&part->itp_part_value.itp_key, key); return true; } bool -itp_part_set_recx(union itp_part_type *part, void *part_value) +itp_part_set_recx(struct indexed_tree_path_part *part, void *part_value) { daos_recx_t *recx = part_value; if (recx->rx_nr == 0) return false; - part->itp_recx = *recx; + part->itp_part_value.itp_recx = *recx; return true; } -static bool (*part_set_fn[PATH_PART_END])(union itp_part_type *part, void *part_value) = { - itp_part_set_cont, - itp_part_set_obj, - itp_part_set_key, - itp_part_set_key, - itp_part_set_recx, +static bool (*part_set_fn[PATH_PART_END])(struct indexed_tree_path_part *part, void *part_value) = { + itp_part_set_cont, itp_part_set_obj, itp_part_set_key, itp_part_set_key, itp_part_set_recx, }; bool @@ -363,7 +360,7 @@ itp_part_value_set(struct dv_indexed_tree_path *itp, enum path_parts part_key, v { struct indexed_tree_path_part *p = &itp->itp_parts[part_key]; - if (part_set_fn[part_key](&p->itp_part_value, part_value)) { + if (part_set_fn[part_key](p, part_value)) { p->itp_has_part_value = true; return true; } @@ -779,15 +776,15 @@ dvp_is_empty(struct dv_tree_path *vtp) */ void -itp_print_part_cont(struct ddb_ctx *ctx, union itp_part_type *v) +itp_print_part_cont(struct ddb_ctx *ctx, struct indexed_tree_path_part *v) { - ddb_printf(ctx, DF_UUIDF, DP_UUID(v->itp_uuid)); + ddb_printf(ctx, DF_UUIDF, DP_UUID(v->itp_part_value.itp_uuid)); } void -itp_print_part_obj(struct ddb_ctx *ctx, union itp_part_type *v) +itp_print_part_obj(struct ddb_ctx *ctx, struct indexed_tree_path_part *v) { - ddb_printf(ctx, DF_UOID, DP_UOID(v->itp_oid)); + ddb_printf(ctx, DF_UOID, DP_UOID(v->itp_part_value.itp_oid)); } bool @@ -806,18 +803,26 @@ itp_key_safe_str(char *buf, size_t buf_len) int e; bool escaped = false; - if (tmp_idx + 1 >= tmp_end) { /* +1 for escape character if needed */ - D_ERROR("Buffer was too small to hold the escape characters"); - return false; - } for (e = 0; e < ARRAY_SIZE(escape_chars) && !escaped; ++e) { if (buf[i] == escape_chars[e]) { + if (tmp_idx + 1 >= tmp_end) { /* +1 for escape character */ + D_ERROR("Too small buffer (%ld) to hold escape character\n", + buf_len); + return false; + } + sprintf(tmp_idx, "\\%c", buf[i]); tmp_idx += 2; escaped = true; } } if (!escaped) { + if (tmp_idx >= tmp_end) { + D_ERROR("Too small buffer (%ld) because former escape characters\n", + buf_len); + return false; + } + sprintf(tmp_idx, "%c", buf[i]); tmp_idx++; } @@ -828,15 +833,16 @@ itp_key_safe_str(char *buf, size_t buf_len) } void -itp_print_part_key(struct ddb_ctx *ctx, union itp_part_type *key_part) +itp_print_part_key(struct ddb_ctx *ctx, struct indexed_tree_path_part *part) { - char buf[DDB_MAX_PRITABLE_KEY]; - d_iov_t *key_iov = &key_part->itp_key; + char buf[DDB_MAX_PRINTABLE_KEY]; + d_iov_t *key_iov = &part->itp_part_value.itp_key; - ddb_iov_to_printable_buf(key_iov, buf, ARRAY_SIZE(buf)); - if (ddb_can_print(key_iov)) { + ddb_key_to_printable_buf(key_iov, part->itp_otype, buf, ARRAY_SIZE(buf)); + if (ddb_key_is_lexical(part->itp_otype) || + (!ddb_key_is_int(part->itp_otype) && ddb_can_print(key_iov))) { /* +1 to make sure there's room for a null terminator */ - char key_str[key_part->itp_key.iov_len + 1]; + char key_str[key_iov->iov_len + 1]; memcpy(key_str, key_iov->iov_buf, key_iov->iov_len); key_str[key_iov->iov_len] = '\0'; @@ -859,17 +865,14 @@ itp_print_part_key(struct ddb_ctx *ctx, union itp_part_type *key_part) } void -itp_print_part_recx(struct ddb_ctx *ctx, union itp_part_type *v) +itp_print_part_recx(struct ddb_ctx *ctx, struct indexed_tree_path_part *v) { - ddb_printf(ctx, DF_DDB_RECX, DP_DDB_RECX(v->itp_recx)); + ddb_printf(ctx, DF_DDB_RECX, DP_DDB_RECX(v->itp_part_value.itp_recx)); } -static void (*print_fn[PATH_PART_END])(struct ddb_ctx *ctx, union itp_part_type *v) = { - itp_print_part_cont, - itp_print_part_obj, - itp_print_part_key, - itp_print_part_key, - itp_print_part_recx, +static void (*print_fn[PATH_PART_END])(struct ddb_ctx *ctx, struct indexed_tree_path_part *v) = { + itp_print_part_cont, itp_print_part_obj, itp_print_part_key, + itp_print_part_key, itp_print_part_recx, }; void @@ -886,7 +889,7 @@ itp_print_parts(struct ddb_ctx *ctx, struct dv_indexed_tree_path *itp) if (!itp->itp_parts[i].itp_has_part_value) break; ddb_print(ctx, "/"); - print_fn[i](ctx, &itp->itp_parts[i].itp_part_value); + print_fn[i](ctx, &itp->itp_parts[i]); } } diff --git a/src/utils/ddb/ddb_tree_path.h b/src/utils/ddb/ddb_tree_path.h index af8c3d92dcf..fb3fe9c10b8 100644 --- a/src/utils/ddb/ddb_tree_path.h +++ b/src/utils/ddb/ddb_tree_path.h @@ -1,5 +1,6 @@ /** * (C) Copyright 2023 Intel Corporation. + * (C) Copyright 2025-2026 Hewlett Packard Enterprise Development LP * * SPDX-License-Identifier: BSD-2-Clause-Patent */ @@ -12,9 +13,9 @@ #define DF_DDB_RECX "{"DF_U64"-"DF_U64"}" #define DP_DDB_RECX(r) (r).rx_idx, ((r).rx_idx + (r).rx_nr - 1) -#define INVALID_IDX (-1) -#define INVALID_PATH "INVALID PATH" -#define DDB_MAX_PRITABLE_KEY 1024 +#define INVALID_IDX (-1) +#define INVALID_PATH "INVALID PATH" +#define DDB_MAX_PRINTABLE_KEY 1024 #define ERROR_BASE 5000 enum ddb_parse_error { @@ -51,10 +52,11 @@ struct indexed_tree_path_part { daos_unit_oid_t itp_oid; daos_key_t itp_key; /* akey or dkey */ daos_recx_t itp_recx; - } itp_part_value; - uint32_t itp_part_idx; - bool itp_has_part_idx; - bool itp_has_part_value; + } itp_part_value; + uint32_t itp_part_idx; + enum daos_otype_t itp_otype; + bool itp_has_part_idx; + bool itp_has_part_value; }; struct dv_indexed_tree_path { @@ -93,10 +95,14 @@ bool itp_idx_set(struct dv_indexed_tree_path *itp, enum path_parts part_key, uint32_t idx); /* Functions for setting parts as a specific path part (i.e. container, object, ... */ -bool itp_part_set_cont(union itp_part_type *part, void *part_value); -bool itp_part_set_obj(union itp_part_type *part, void *part_value); -bool itp_part_set_key(union itp_part_type *part, void *part_value); -bool itp_part_set_recx(union itp_part_type *part, void *part_value); +bool +itp_part_set_cont(struct indexed_tree_path_part *part, void *part_value); +bool +itp_part_set_obj(struct indexed_tree_path_part *part, void *part_value); +bool +itp_part_set_key(struct indexed_tree_path_part *part, void *part_value); +bool + itp_part_set_recx(struct indexed_tree_path_part *part, void *part_value); /* Functions for setting the parts (cont, obj, ...) of a indexed tree path */ bool itp_set_cont(struct dv_indexed_tree_path *itp, uuid_t cont_uuid, uint32_t idx); @@ -171,7 +177,8 @@ int itp_recx_idx(struct dv_indexed_tree_path *itp); void itp_print_indexes(struct ddb_ctx *ctx, struct dv_indexed_tree_path *itp); void itp_print_parts(struct ddb_ctx *ctx, struct dv_indexed_tree_path *itp); void itp_print_full(struct ddb_ctx *ctx, struct dv_indexed_tree_path *itp); -void itp_print_part_key(struct ddb_ctx *ctx, union itp_part_type *key_part); +void + itp_print_part_key(struct ddb_ctx *ctx, struct indexed_tree_path_part *part); /* * This function is used when printing keys. It checks each character in the buffer and will diff --git a/src/utils/ddb/ddb_vos.c b/src/utils/ddb/ddb_vos.c index 4ee22fbe099..a8e65670553 100644 --- a/src/utils/ddb/ddb_vos.c +++ b/src/utils/ddb/ddb_vos.c @@ -404,19 +404,19 @@ vos_vtp_compare(struct dv_indexed_tree_path *vtp, vos_iter_entry_t *entry, enum } static void -set_oid(vos_iter_entry_t *entry, union itp_part_type *part) +set_oid(vos_iter_entry_t *entry, struct indexed_tree_path_part *part) { itp_part_set_obj(part, &entry->ie_oid); } static void -set_key(vos_iter_entry_t *entry, union itp_part_type *part) +set_key(vos_iter_entry_t *entry, struct indexed_tree_path_part *part) { itp_part_set_key(part, &entry->ie_key); } static void -set_recx(vos_iter_entry_t *entry, union itp_part_type *part) +set_recx(vos_iter_entry_t *entry, struct indexed_tree_path_part *part) { itp_part_set_recx(part, &entry->ie_orig_recx); } @@ -424,18 +424,16 @@ set_recx(vos_iter_entry_t *entry, union itp_part_type *part) static void vos_itp_set(struct dv_indexed_tree_path *itp, vos_iter_entry_t *entry, enum path_parts part_key) { - void (*set_fn[PATH_PART_END])(vos_iter_entry_t *entry, union itp_part_type *part) = { + void (*set_fn[PATH_PART_END])(vos_iter_entry_t *entry, + struct indexed_tree_path_part *part) = { NULL, /* Won't set containers */ - set_oid, - set_key, - set_key, - set_recx, + set_oid, set_key, set_key, set_recx, }; D_ASSERT(part_key < PATH_PART_END); D_ASSERT(set_fn[part_key] != NULL); - set_fn[part_key](entry, &itp->itp_parts[part_key].itp_part_value); + set_fn[part_key](entry, &itp->itp_parts[part_key]); itp->itp_parts[part_key].itp_has_part_value = true; } @@ -735,8 +733,7 @@ dv_oid_to_obj(daos_obj_id_t oid, struct ddb_obj *obj) * obj_class_fini(); */ - obj->ddbo_otype = daos_obj_id2type(oid); - get_object_type(obj->ddbo_otype, obj->ddbo_otype_str); + get_object_type(daos_obj_id2type(oid), obj->ddbo_otype_str); } static int @@ -772,9 +769,10 @@ handle_dkey(struct ddb_iter_ctx *ctx, vos_iter_entry_t *entry) itp_unset_dkey(&ctx->itp); /* make sure dkey is freed from any previous handle */ itp_set_dkey(&ctx->itp, &entry->ie_key, ctx->dkey_seen); - dkey.ddbk_path = &ctx->itp; - dkey.ddbk_idx = ctx->dkey_seen++; - dkey.ddbk_key = entry->ie_key; + dkey.ddbk_path = &ctx->itp; + dkey.ddbk_idx = ctx->dkey_seen++; + dkey.ddbk_key = entry->ie_key; + dkey.ddbk_otype = daos_obj_id2type(ctx->current_obj.id_pub); dkey.ddbk_child_type = entry->ie_child_type; ctx->current_dkey = entry->ie_key; @@ -798,9 +796,10 @@ handle_akey(struct ddb_iter_ctx *ctx, vos_iter_entry_t *entry) itp_set_akey(&ctx->itp, &entry->ie_key, ctx->akey_seen); itp_unset_recx(&ctx->itp); - akey.ddbk_path = &ctx->itp; - akey.ddbk_idx = ctx->akey_seen++; - akey.ddbk_key = entry->ie_key; + akey.ddbk_path = &ctx->itp; + akey.ddbk_idx = ctx->akey_seen++; + akey.ddbk_key = entry->ie_key; + akey.ddbk_otype = daos_obj_id2type(ctx->current_obj.id_pub); akey.ddbk_child_type = entry->ie_child_type; ctx->current_akey = entry->ie_key; @@ -921,9 +920,10 @@ dv_iterate(daos_handle_t poh, struct dv_tree_path *path, bool recursive, vos_iter_type_t type; struct ddb_iter_ctx ctx = {0}; - ctx.handlers = handlers; + ctx.handlers = handlers; ctx.handler_args = handler_args; - ctx.poh = poh; + ctx.poh = poh; + ctx.current_obj = path->vtp_oid; itp_copy(&ctx.itp, itp); param.ip_epr.epr_hi = DAOS_EPOCH_MAX; diff --git a/src/utils/ddb/ddb_vos.h b/src/utils/ddb/ddb_vos.h index 675c4ceb33b..3c1a48809b2 100644 --- a/src/utils/ddb/ddb_vos.h +++ b/src/utils/ddb/ddb_vos.h @@ -20,19 +20,19 @@ struct ddb_cont { }; struct ddb_obj { - daos_obj_id_t ddbo_oid; - uint32_t ddbo_idx; - enum daos_otype_t ddbo_otype; - char ddbo_otype_str[32]; - uint32_t ddbo_nr_grps; - struct dv_indexed_tree_path *ddbo_path; + daos_obj_id_t ddbo_oid; + uint32_t ddbo_idx; + uint32_t ddbo_nr_grps; + char ddbo_otype_str[32]; + struct dv_indexed_tree_path *ddbo_path; }; struct ddb_key { - daos_key_t ddbk_key; - uint32_t ddbk_idx; - vos_iter_type_t ddbk_child_type; - struct dv_indexed_tree_path *ddbk_path; + daos_key_t ddbk_key; + uint32_t ddbk_idx; + enum daos_otype_t ddbk_otype; + vos_iter_type_t ddbk_child_type; + struct dv_indexed_tree_path *ddbk_path; }; struct ddb_sv { diff --git a/src/utils/ddb/tests/ddb_commands_print_tests.c b/src/utils/ddb/tests/ddb_commands_print_tests.c index 9b60b070dd5..129568acbc9 100644 --- a/src/utils/ddb/tests/ddb_commands_print_tests.c +++ b/src/utils/ddb/tests/ddb_commands_print_tests.c @@ -1,6 +1,6 @@ /** * (C) Copyright 2022-2023 Intel Corporation. - * (C) Copyright 2025 Hewlett Packard Enterprise Development LP + * (C) Copyright 2025-2026 Hewlett Packard Enterprise Development LP * * SPDX-License-Identifier: BSD-2-Clause-Patent */ @@ -60,6 +60,7 @@ print_key_test(void **state) key.ddbk_idx = 4; d_iov_set(&key.ddbk_key, key_buf, ARRAY_SIZE(key_buf)); + key.ddbk_otype = DAOS_OT_MULTI_LEXICAL; ddb_print_key(&g_ctx, &key, 0); @@ -93,6 +94,7 @@ print_key_test(void **state) * If key length is a number type, then print as that. */ memset(key_buf, 0, ARRAY_SIZE(key_buf)); + key.ddbk_otype = DAOS_OT_MULTI_UINT64; /* char key */ key_buf[0] = 0xab; @@ -286,17 +288,17 @@ iov_to_printable_test(void **state) char buf[buf_len]; char input_buf[buf_len]; - assert_int_equal(0, ddb_iov_to_printable_buf(&iov, buf, buf_len)); + assert_int_equal(0, ddb_iov_to_printable_buf(&iov, buf, buf_len, NULL)); /* buf is plenty big */ sprintf(input_buf, "This is some text"); d_iov_set(&iov, input_buf, strlen(input_buf) + 1); - assert_int_equal(17, ddb_iov_to_printable_buf(&iov, buf, buf_len)); + assert_int_equal(17, ddb_iov_to_printable_buf(&iov, buf, buf_len, NULL)); assert_string_equal(input_buf, buf); /* buf is too small */ memset(buf, 0, buf_len); - assert_int_equal(17, ddb_iov_to_printable_buf(&iov, buf, 10)); + assert_int_equal(17, ddb_iov_to_printable_buf(&iov, buf, 10, NULL)); assert_string_equal("This is s", buf); /* Binary type - enough buffer*/ @@ -305,27 +307,27 @@ iov_to_printable_test(void **state) /* chars written to buffer is 30. For each byte, 2 are printed (10 bytes * 2) plus * the prefix of 'bin(10):' is 10 more chars. */ - assert_int_equal(30, ddb_iov_to_printable_buf(&iov, buf, buf_len)); + assert_int_equal(30, ddb_key_to_printable_buf(&iov, 0, buf, buf_len)); assert_string_equal("bin(10):0xabababababababababab", buf); /* Binary type - not enough buffer*/ - assert_int_equal(30, ddb_iov_to_printable_buf(&iov, buf, 20)); + assert_int_equal(30, ddb_key_to_printable_buf(&iov, 0, buf, 20)); assert_string_equal("bin(10):0xababab...", buf); /* Number types */ d_iov_set(&iov, input_buf, 8); /* uint64 */ - assert_int_equal(25, ddb_iov_to_printable_buf(&iov, buf, buf_len)); + assert_int_equal(25, ddb_key_to_printable_buf(&iov, DAOS_OT_MULTI_UINT64, buf, buf_len)); assert_string_equal("uint64:0xabababababababab", buf); - assert_int_equal(25, ddb_iov_to_printable_buf(&iov, buf, 10)); + assert_int_equal(25, ddb_key_to_printable_buf(&iov, DAOS_OT_MULTI_UINT64, buf, 10)); assert_string_equal("uint64:0x", buf); d_iov_set(&iov, input_buf, 4); /* uint32 */ - assert_int_equal(17, ddb_iov_to_printable_buf(&iov, buf, buf_len)); + assert_int_equal(17, ddb_key_to_printable_buf(&iov, DAOS_OT_ARRAY_BYTE, buf, buf_len)); assert_string_equal("uint32:0xabababab", buf); d_iov_set(&iov, input_buf, 1); /* uint8 */ - assert_int_equal(10, ddb_iov_to_printable_buf(&iov, buf, buf_len)); + assert_int_equal(10, ddb_key_to_printable_buf(&iov, DAOS_OT_ARRAY_BYTE, buf, buf_len)); assert_string_equal("uint8:0xab", buf); } diff --git a/src/utils/ddb/tests/ddb_path_tests.c b/src/utils/ddb/tests/ddb_path_tests.c index 7c0c7a3a421..5b8443d1b7d 100644 --- a/src/utils/ddb/tests/ddb_path_tests.c +++ b/src/utils/ddb/tests/ddb_path_tests.c @@ -1,5 +1,6 @@ /** * (C) Copyright 2023 Intel Corporation. + * (C) Copyright 2026 Hewlett Packard Enterprise Development LP * * SPDX-License-Identifier: BSD-2-Clause-Patent */ @@ -73,6 +74,7 @@ key_safe_str_tests(void **state) assert_string_equal(small_buf, "///////"); } +/* clang-format off */ static void key_printing_and_parsing_tests(void **state) { @@ -80,32 +82,36 @@ key_printing_and_parsing_tests(void **state) * These tests will parse the first argument, then print it. The printed * value will be compared to the second (expected) argument. */ -#define assert_key_parsed_printed(parsed, printed) do {\ - union itp_part_type __v = {0}; \ - assert_true(ddb_parse_key(parsed, &__v.itp_key) > 0); \ - itp_print_part_key(&g_ctx, &__v); \ - assert_printed_exact(printed); \ - dvt_fake_print_reset(); \ - daos_iov_free(&__v.itp_key); \ -} while (0) - - assert_key_parsed_printed("akey", "akey"); - assert_key_parsed_printed("akey{4}", "akey"); - assert_key_parsed_printed("akey{64}", "akey{64}"); +#define assert_key_parsed_printed(parsed, printed, int_key) \ + do { \ + struct indexed_tree_path_part __part = {0}; \ + assert_true(ddb_parse_key(parsed, &__part.itp_part_value.itp_key) > 0); \ + __part.itp_otype = int_key ? DAOS_OT_MULTI_UINT64 : 0; \ + itp_print_part_key(&g_ctx, &__part); \ + assert_printed_exact(printed); \ + dvt_fake_print_reset(); \ + daos_iov_free(&__part.itp_part_value.itp_key); \ + } while (0) + + assert_key_parsed_printed("akey", "akey", false); + assert_key_parsed_printed("akey{4}", "akey", false); + assert_key_parsed_printed("akey{64}", "akey{64}", false); /* binary should take size as input, but doesn't need it. It will always print it however */ - assert_key_parsed_printed("{bin:0xabcdef1234}", "{bin(5):0xabcdef1234}"); - assert_key_parsed_printed("{bin(5):0xabcdef1234}", "{bin(5):0xabcdef1234}"); + assert_key_parsed_printed("{bin:0xabcdef1234}", "{bin(5):0xabcdef1234}", false); + assert_key_parsed_printed("{bin(5):0xabcdef1234}", "{bin(5):0xabcdef1234}", false); /* Int types. Hex letters' case doesn't matter. Will always print as lower case */ - assert_key_parsed_printed("{uint64:0xABCDEF1234}", "{uint64:0xabcdef1234}"); - assert_key_parsed_printed("{uint32:0x12345678}", "{uint32:0x12345678}"); - assert_key_parsed_printed("{uint16:0x1234}", "{uint16:0x1234}"); - assert_key_parsed_printed("{uint8:0xAF}", "{uint8:0xaf}"); + assert_key_parsed_printed("{uint64:10}", "{uint64:0xa}", true); + assert_key_parsed_printed("{uint64:0xABCDEF1234}", "{uint64:0xabcdef1234}", true); + assert_key_parsed_printed("{uint32:0x12345678}", "{uint32:0x12345678}", true); + assert_key_parsed_printed("{uint16:0x1234}", "{uint16:0x1234}", true); + assert_key_parsed_printed("{uint8:0xAF}", "{uint8:0xaf}", true); /* Parsing doesn't handle too big of values yet, so will get truncated */ - assert_key_parsed_printed("{uint8:0xFFFAAA}", "{uint8:0xaa}"); - assert_key_parsed_printed("\\/", "\\/"); + assert_key_parsed_printed("{uint8:0xFFFAAA}", "{uint8:0xaa}", true); + assert_key_parsed_printed("\\/", "\\/", false); } +/* clang-format on */ /* Test setting and printing the full path given the path parts structure */ static void diff --git a/utils/cq/requirements.txt b/utils/cq/requirements.txt index ef9340fcb73..d96833d9599 100644 --- a/utils/cq/requirements.txt +++ b/utils/cq/requirements.txt @@ -6,7 +6,7 @@ flake8==7.3.0 isort==8.0.1 pylint==4.0.5 yamllint==1.38.0 -codespell==2.4.1 +codespell==2.4.2 # Used by ci/jira_query.py which pip installs it standalone. jira torch>=2.2.0 diff --git a/utils/rpms/mercury.changelog b/utils/rpms/mercury.changelog index c00f682eb61..2ec75cecb4c 100644 --- a/utils/rpms/mercury.changelog +++ b/utils/rpms/mercury.changelog @@ -1,4 +1,7 @@ %changelog +* Mon Mar 09 2026 Jerome Soumagne - 2.4.1-2 +- Require mercury-libfabric or mercury-ucx + * Mon Jan 26 2026 Jerome Soumagne - 2.4.1-1 - Update to 2.4.1 - Separate libfabric plugin from main build to align with ucx plugin diff --git a/utils/rpms/mercury.sh b/utils/rpms/mercury.sh index 5f30dd89372..1e6ea021983 100755 --- a/utils/rpms/mercury.sh +++ b/utils/rpms/mercury.sh @@ -43,7 +43,9 @@ clean_bin "${files[@]}" append_install_list "${files[@]}" ARCH="${isa}" +DEPENDS=("(mercury-libfabric or mercury-ucx)") build_package "mercury" +DEPENDS=() TARGET_PATH="${libdir}/mercury" list_files files "${SL_MERCURY_PREFIX}/lib64/mercury/libna_plugin_ofi.so" diff --git a/utils/rpms/package_info.sh b/utils/rpms/package_info.sh index ef3338124d4..5be9fb49ac8 100644 --- a/utils/rpms/package_info.sh +++ b/utils/rpms/package_info.sh @@ -45,7 +45,7 @@ export libfabric_version="1.22.0" export libfabric_release="5${distro_name}" export libfabric_full="${libfabric_version}-${libfabric_release}" export mercury_version="2.4.1" -export mercury_release="1${distro_name}" +export mercury_release="2${distro_name}" export mercury_full="${mercury_version}-${mercury_release}" export argobots_version="1.2" export argobots_release="4${distro_name}"