diff --git a/fixtures/dell/r6515.go b/fixtures/dell/r6515.go index aa1381f1..4a30a913 100644 --- a/fixtures/dell/r6515.go +++ b/fixtures/dell/r6515.go @@ -1782,20 +1782,16 @@ var ( } nvmeDriveCapabilities = []*common.Capability{ - {Name: "sanicap", Description: "Sanitize Support"}, - {Name: "ammasocsind", Description: "Additional media modification after sanitize operation completes successfully is not defined"}, - {Name: "nasbiscs", Description: "No-Deallocate After Sanitize bit in Sanitize command Supported"}, - {Name: "osons", Description: "Overwrite Sanitize Operation Not Supported"}, - {Name: "besons", Description: "Block Erase Sanitize Operation Not Supported"}, - {Name: "cesons", Description: "Crypto Erase Sanitize Operation Not Supported"}, - {Name: "fna", Description: "Crypto Erase Support", Enabled: true}, - {Name: "cesapose", Description: "Crypto Erase Supported as part of Secure Erase", Enabled: true}, - {Name: "ceatsn", Description: "Crypto Erase Applies to Single Namespace(s)"}, - {Name: "fatsn", Description: "Format Applies to Single Namespace(s)"}, + {Name: "fmns", Description: "Format Applies to All/Single Namespace(s) (t:All, f:Single)"}, + {Name: "cens", Description: "Crypto Erase Applies to All/Single Namespace(s) (t:All, f:Single)"}, + {Name: "cese", Description: "Crypto Erase Supported as part of Secure Erase", Enabled: true}, + {Name: "cer", Description: "Crypto Erase Sanitize Operation Supported"}, + {Name: "ber", Description: "Block Erase Sanitize Operation Supported"}, + {Name: "owr", Description: "Overwrite Sanitize Operation Supported"}, + {Name: "ndi", Description: "No-Deallocate After Sanitize bit in Sanitize command Supported"}, } - // - // // r6515 inventory taken with lshw, merged with data from smartctl + // r6515 inventory taken with lshw, merged with data from smartctl R6515_inventory_lshw_smartctl = &common.Device{ Common: common.Common{ Oem: false, diff --git a/fixtures/utils/nvme/nvmecli-id-ctrl b/fixtures/utils/nvme/nvmecli-id-ctrl index 6167e448..75f1b173 100644 --- a/fixtures/utils/nvme/nvmecli-id-ctrl +++ b/fixtures/utils/nvme/nvmecli-id-ctrl @@ -1,204 +1,170 @@ -NVME Identify Controller: -vid : 0x1344 -ssvid : 0x1344 -sn : 201327AA90B6 -mn : Micron_9300_MTFDHAL3T8TDP -fr : 11300DN0 -rab : 0 -ieee : 00a075 -cmic : 0 - [3:3] : 0 ANA not supported - [2:2] : 0 PCI - [1:1] : 0 Single Controller - [0:0] : 0 Single Port - -mdts : 5 -cntlid : 0x1 -ver : 0x10200 -rtd3r : 0xe4e1c0 -rtd3e : 0x989680 -oaes : 0x300 -[14:14] : 0 Endurance Group Event Aggregate Log Page Change Notice Not Supported -[13:13] : 0 LBA Status Information Notices Not Supported -[12:12] : 0 Predictable Latency Event Aggregate Log Change Notices Not Supported -[11:11] : 0 Asymmetric Namespace Access Change Notices Not Supported - [9:9] : 0x1 Firmware Activation Notices Supported - [8:8] : 0x1 Namespace Attribute Changed Event Supported - -ctratt : 0 - [9:9] : 0 UUID List Not Supported - [7:7] : 0 Namespace Granularity Not Supported - [5:5] : 0 Predictable Latency Mode Not Supported - [4:4] : 0 Endurance Groups Not Supported - [3:3] : 0 Read Recovery Levels Not Supported - [2:2] : 0 NVM Sets Not Supported - [1:1] : 0 Non-Operational Power State Permissive Not Supported - [0:0] : 0 128-bit Host Identifier Not Supported - -rrls : 0 -crdt1 : 0 -crdt2 : 0 -crdt3 : 0 -oacs : 0xe - [9:9] : 0 Get LBA Status Capability Not Supported - [8:8] : 0 Doorbell Buffer Config Not Supported - [7:7] : 0 Virtualization Management Not Supported - [6:6] : 0 NVMe-MI Send and Receive Not Supported - [5:5] : 0 Directives Not Supported - [4:4] : 0 Device Self-test Not Supported - [3:3] : 0x1 NS Management and Attachment Supported - [2:2] : 0x1 FW Commit and Download Supported - [1:1] : 0x1 Format NVM Supported - [0:0] : 0 Security Send and Receive Not Supported - -acl : 4 -aerl : 5 -frmw : 0x17 - [4:4] : 0x1 Firmware Activate Without Reset Supported - [3:1] : 0x3 Number of Firmware Slots - [0:0] : 0x1 Firmware Slot 1 Read-Only - -lpa : 0x2 - [3:3] : 0 Telemetry host/controller initiated log page Not Supported - [2:2] : 0 Extended data for Get Log Page Not Supported - [1:1] : 0x1 Command Effects Log Page Supported - [0:0] : 0 SMART/Health Log Page per NS Not Supported - -elpe : 62 -npss : 15 -avscc : 0x1 - [0:0] : 0x1 Admin Vendor Specific Commands uses NVMe Format - -apsta : 0 - [0:0] : 0 Autonomous Power State Transitions Not Supported - -wctemp : 348 -cctemp : 353 -mtfa : 50 -hmpre : 0 -hmmin : 0 -tnvmcap : 3840755982336 -unvmcap : 0 -rpmbs : 0 - [31:24]: 0 Access Size - [23:16]: 0 Total Size - [5:3] : 0 Authentication Method - [2:0] : 0 Number of RPMB Units - -edstt : 0 -dsto : 0 -fwug : 0 -kas : 0 -hctma : 0 - [0:0] : 0 Host Controlled Thermal Management Not Supported - -mntmt : 0 -mxtmt : 0 -sanicap : 0 - [31:30] : 0 Additional media modification after sanitize operation completes successfully is not defined - [29:29] : 0 No-Deallocate After Sanitize bit in Sanitize command Supported - [2:2] : 0 Overwrite Sanitize Operation Not Supported - [1:1] : 0 Block Erase Sanitize Operation Not Supported - [0:0] : 0 Crypto Erase Sanitize Operation Not Supported - -hmminds : 0 -hmmaxd : 0 -nsetidmax : 0 -anatt : 0 -anacap : 0 - [7:7] : 0 Non-zero group ID Not Supported - [6:6] : 0 Group ID does not change - [4:4] : 0 ANA Change state Not Supported - [3:3] : 0 ANA Persistent Loss state Not Supported - [2:2] : 0 ANA Inaccessible state Not Supported - [1:1] : 0 ANA Non-optimized state Not Supported - [0:0] : 0 ANA Optimized state Not Supported - -anagrpmax : 0 -nanagrpid : 0 -sqes : 0x66 - [7:4] : 0x6 Max SQ Entry Size (64) - [3:0] : 0x6 Min SQ Entry Size (64) - -cqes : 0x44 - [7:4] : 0x4 Max CQ Entry Size (16) - [3:0] : 0x4 Min CQ Entry Size (16) - -maxcmd : 0 -nn : 32 -oncs : 0x14 - [7:7] : 0 Verify Not Supported - [6:6] : 0 Timestamp Not Supported - [5:5] : 0 Reservations Not Supported - [4:4] : 0x1 Save and Select Supported - [3:3] : 0 Write Zeroes Not Supported - [2:2] : 0x1 Data Set Management Supported - [1:1] : 0 Write Uncorrectable Not Supported - [0:0] : 0 Compare Not Supported - -fuses : 0 - [0:0] : 0 Fused Compare and Write Not Supported - -fna : 0x4 - [2:2] : 0x1 Crypto Erase Supported as part of Secure Erase - [1:1] : 0 Crypto Erase Applies to Single Namespace(s) - [0:0] : 0 Format Applies to Single Namespace(s) - -vwc : 0 - [0:0] : 0 Volatile Write Cache Not Present - -awun : 0 -awupf : 0 -nvscc : 1 - [0:0] : 0x1 NVM Vendor Specific Commands uses NVMe Format - -nwpc : 0 - [2:2] : 0 Permanent Write Protect Not Supported - [1:1] : 0 Write Protect Until Power Supply Not Supported - [0:0] : 0 No Write Protect and Write Protect Namespace Not Supported - -acwu : 0 -sgls : 0 - [1:0] : 0 Scatter-Gather Lists Not Supported - -mnan : 0 -subnqn : nqn.2016-08.com.micron:nvme:nvm-subsystem-sn-201327AA90B6 -ioccsz : 0 -iorcsz : 0 -icdoff : 0 -ctrattr : 0 - [0:0] : 0 Dynamic Controller Model - -msdbd : 0 -ps 0 : mp:25.00W operational enlat:100 exlat:100 rrt:0 rrl:0 - rwt:0 rwl:0 idle_power:- active_power:- -ps 1 : mp:24.00W operational enlat:115 exlat:115 rrt:1 rrl:1 - rwt:1 rwl:1 idle_power:- active_power:- -ps 2 : mp:23.00W operational enlat:130 exlat:130 rrt:2 rrl:2 - rwt:2 rwl:2 idle_power:- active_power:- -ps 3 : mp:22.00W operational enlat:145 exlat:145 rrt:3 rrl:3 - rwt:3 rwl:3 idle_power:- active_power:- -ps 4 : mp:21.00W operational enlat:160 exlat:160 rrt:4 rrl:4 - rwt:4 rwl:4 idle_power:- active_power:- -ps 5 : mp:20.00W operational enlat:175 exlat:175 rrt:5 rrl:5 - rwt:5 rwl:5 idle_power:- active_power:- -ps 6 : mp:19.00W operational enlat:190 exlat:190 rrt:6 rrl:6 - rwt:6 rwl:6 idle_power:- active_power:- -ps 7 : mp:18.00W operational enlat:205 exlat:205 rrt:7 rrl:7 - rwt:7 rwl:7 idle_power:- active_power:- -ps 8 : mp:17.00W operational enlat:220 exlat:220 rrt:8 rrl:8 - rwt:8 rwl:8 idle_power:- active_power:- -ps 9 : mp:16.00W operational enlat:235 exlat:235 rrt:9 rrl:9 - rwt:9 rwl:9 idle_power:- active_power:- -ps 10 : mp:15.00W operational enlat:250 exlat:250 rrt:10 rrl:10 - rwt:10 rwl:10 idle_power:- active_power:- -ps 11 : mp:14.00W operational enlat:265 exlat:265 rrt:11 rrl:11 - rwt:11 rwl:11 idle_power:- active_power:- -ps 12 : mp:13.00W operational enlat:280 exlat:280 rrt:12 rrl:12 - rwt:12 rwl:12 idle_power:- active_power:- -ps 13 : mp:12.00W operational enlat:295 exlat:295 rrt:13 rrl:13 - rwt:13 rwl:13 idle_power:- active_power:- -ps 14 : mp:11.00W operational enlat:310 exlat:310 rrt:14 rrl:14 - rwt:14 rwl:14 idle_power:- active_power:- -ps 15 : mp:10.00W operational enlat:325 exlat:325 rrt:15 rrl:15 - rwt:15 rwl:15 idle_power:- active_power:- +{ + "acl": 7, + "acwu": 0, + "aerl": 3, + "anacap": 0, + "anagrpmax": 0, + "anatt": 0, + "apsta": 1, + "avscc": 1, + "awun": 1023, + "awupf": 0, + "cctemp": 358, + "cmic": 0, + "cntlid": 6, + "cntrltype": 0, + "cqes": 68, + "crdt1": 0, + "crdt2": 0, + "crdt3": 0, + "ctratt": 16, + "domainid": 0, + "dsto": 0, + "edstt": 35, + "elpe": 63, + "endgidmax": 1, + "fcatt": 0, + "fguid": "00000000-0000-0000-0000-000000000000", + "fna": 4, + "fr": "3B2QGXA7", + "frmw": 22, + "fuses": 0, + "fwug": 0, + "hctma": 1, + "hmmaxd": 0, + "hmmin": 0, + "hmminds": 0, + "hmpre": 0, + "icdoff": 0, + "icsvscc": 1, + "ieee": 9528, + "ioccsz": 0, + "iorcsz": 0, + "kas": 0, + "lpa": 15, + "maxcmd": 256, + "maxcna": 0, + "maxdna": 0, + "mdts": 7, + "mec": 0, + "megcap": 0, + "mn": "Samsung SSD 980 PRO 1TB ", + "mnan": 0, + "mntmt": 318, + "msdbd": 0, + "mtfa": 0, + "mxtmt": 356, + "nanagrpid": 0, + "nn": 1, + "npss": 4, + "nsetidmax": 0, + "nvmsr": 0, + "nwpc": 0, + "oacs": 23, + "oaes": 512, + "oaqd": 0, + "ocfs": 0, + "ofcs": 0, + "oncs": 87, + "pels": 0, + "psds": [ + { + "active_power": 0, + "active_power_work": 0, + "active_scale": 0, + "entry_lat": 0, + "exit_lat": 0, + "idle_power": 0, + "idle_scale": 0, + "max_power": 849, + "max_power_scale": 0, + "non-operational_state": 0, + "read_lat": 0, + "read_tput": 0, + "write_lat": 0, + "write_tput": 0 + }, + { + "active_power": 0, + "active_power_work": 0, + "active_scale": 0, + "entry_lat": 0, + "exit_lat": 200, + "idle_power": 0, + "idle_scale": 0, + "max_power": 448, + "max_power_scale": 0, + "non-operational_state": 0, + "read_lat": 1, + "read_tput": 1, + "write_lat": 1, + "write_tput": 1 + }, + { + "active_power": 0, + "active_power_work": 0, + "active_scale": 0, + "entry_lat": 0, + "exit_lat": 1000, + "idle_power": 0, + "idle_scale": 0, + "max_power": 318, + "max_power_scale": 0, + "non-operational_state": 0, + "read_lat": 2, + "read_tput": 2, + "write_lat": 2, + "write_tput": 2 + }, + { + "active_power": 0, + "active_power_work": 0, + "active_scale": 0, + "entry_lat": 500, + "exit_lat": 9500, + "idle_power": 0, + "idle_scale": 0, + "max_power": 50, + "max_power_scale": 1, + "non-operational_state": 1, + "read_lat": 4, + "read_tput": 4, + "write_lat": 4, + "write_tput": 4 + }, + { + "active_power": 0, + "active_power_work": 0, + "active_scale": 0, + "entry_lat": 2000, + "exit_lat": 1200, + "idle_power": 0, + "idle_scale": 0, + "max_power": 400, + "max_power_scale": 1, + "non-operational_state": 1, + "read_lat": 3, + "read_tput": 3, + "write_lat": 3, + "write_tput": 3 + } + ], + "rab": 2, + "rpmbs": 0, + "rrls": 0, + "rtd3e": 10000000, + "rtd3r": 200000, + "sanicap": 0, + "sgls": 0, + "sn": "S5P2NG0R824326R ", + "sqes": 102, + "ssvid": 5197, + "subnqn": "nqn.1994-11.com.samsung:nvme:980PRO:M.2:S5P2NG0R824326R ", + "tnvmcap": 1000204886016, + "unvmcap": 0, + "ver": 66304, + "vid": 5197, + "vwc": 7, + "vwci": 0, + "wctemp": 355 +} diff --git a/utils/nvme.go b/utils/nvme.go index 38227493..536c484e 100644 --- a/utils/nvme.go +++ b/utils/nvme.go @@ -1,11 +1,10 @@ package utils import ( - "bufio" "context" "encoding/json" + "errors" "os" - "regexp" "strconv" "strings" @@ -15,6 +14,8 @@ import ( const EnvNvmeUtility = "IRONLIB_UTIL_NVME" +var errSanicapNODMMASReserved = errors.New("sanicap nodmmas reserved bits set, not sure what to do with them") + type Nvme struct { Executor Executor } @@ -86,7 +87,6 @@ func (n *Nvme) Drives(ctx context.Context) ([]*common.Drive, error) { if len(modelTokens) > 1 { vendor = modelTokens[1] } - drive := &common.Drive{ Common: common.Common{ Serial: d.SerialNumber, @@ -130,9 +130,8 @@ func (n *Nvme) list(ctx context.Context) ([]byte, error) { } func (n *Nvme) cmdListCapabilities(ctx context.Context, logicalPath string) ([]byte, error) { - // nvme id-ctrl -H devicepath - n.Executor.SetArgs([]string{"id-ctrl", "-H", logicalPath}) - + // nvme id-ctrl --output-format=json devicepath + n.Executor.SetArgs([]string{"id-ctrl", "--output-format=json", logicalPath}) result, err := n.Executor.ExecWithContext(ctx) if err != nil { return nil, err @@ -141,161 +140,113 @@ func (n *Nvme) cmdListCapabilities(ctx context.Context, logicalPath string) ([]b return result.Stdout, nil } -// DriveCapabilities returns the drive capability attributes obtained through hdparm +// DriveCapabilities returns the drive capability attributes obtained through nvme // // The logicalName is the kernel/OS assigned drive name - /dev/nvmeX // // This method implements the actions.DriveCapabilityCollector interface. -// -// nolint:gocyclo // line parsing is cyclomatic func (n *Nvme) DriveCapabilities(ctx context.Context, logicalName string) ([]*common.Capability, error) { out, err := n.cmdListCapabilities(ctx, logicalName) if err != nil { return nil, err } - var capabilitiesFound []*common.Capability - - var lines []string - - s := string(out) - - scanner := bufio.NewScanner(strings.NewReader(s)) - for scanner.Scan() { - lines = append(lines, scanner.Text()) + var caps struct { + FNA uint `json:"fna"` + SANICAP uint `json:"sanicap"` } - err = scanner.Err() + err = json.Unmarshal(out, &caps) if err != nil { return nil, err } - // Delimiters - reFnaStart := regexp.MustCompile(`(?s)^fna\s`) - reFnaEnd := regexp.MustCompile(`(?s)^vwc\s`) - reSaniStart := regexp.MustCompile(`(?s)^sanicap\s`) - reSaniEnd := regexp.MustCompile(`(?s)^hmminds\s`) - reBlank := regexp.MustCompile(`(?m)^\s*$`) - - var fnaBool, saniBool bool - - for _, line := range lines { - line = strings.TrimSpace(line) - fnaStart := reFnaStart.MatchString(line) - fnaEnd := reFnaEnd.MatchString(line) - saniStart := reSaniStart.MatchString(line) - saniEnd := reSaniEnd.MatchString(line) - isBlank := reBlank.MatchString(line) - - // start/end match specific block delimiters - // bools are toggled to indicate lines within a given block - switch { - case fnaStart: - fnaBool = true - case fnaEnd: - fnaBool = false - case saniStart: - saniBool = true - case saniEnd: - saniBool = false - } - - switch { - case (fnaStart || saniStart): - capability := new(common.Capability) - - partsLen := 2 - - parts := strings.Split(line, ":") - if len(parts) != partsLen { - continue - } - - key, value := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) - capability.Name = key - - if value != "0" { - capability.Enabled = true - } - - if fnaStart { - capability.Description = "Crypto Erase Support" - } else { - capability.Description = "Sanitize Support" - } - - if value != "0" { - capability.Enabled = true - } - - capabilitiesFound = append(capabilitiesFound, capability) - - // crypto erase - case (fnaBool && !fnaEnd && !isBlank): - capability := new(common.Capability) - - partsLen := 3 - - parts := strings.Split(line, ":") - if len(parts) != partsLen { - continue - } - - data := strings.Split(parts[2], "\t") - enabled := strings.TrimSpace(data[0]) - - if enabled != "0" { - capability.Enabled = true - } - - // Generate short flag identifier - for _, word := range strings.Fields(data[1]) { - capability.Name += strings.ToLower(word[0:1]) - } - - capability.Description = data[1] - - if enabled != "0" { - capability.Enabled = true - } - - capability.Description = data[1] - capabilitiesFound = append(capabilitiesFound, capability) - // sanitize - case (saniBool && !saniEnd && !isBlank): - capability := new(common.Capability) - - partsLen := 3 - - parts := strings.Split(line, ":") - if len(parts) != partsLen { - continue - } - - data := strings.Split(parts[2], "\t") - enabled := strings.TrimSpace(data[0]) + var capabilitiesFound []*common.Capability + capabilitiesFound = append(capabilitiesFound, parseFna(caps.FNA)...) - if enabled != "0" { - capability.Enabled = true - } + var parsedCaps []*common.Capability + parsedCaps, err = parseSanicap(caps.SANICAP) + if err != nil { + return nil, err + } + capabilitiesFound = append(capabilitiesFound, parsedCaps...) - // Generate short flag identifier - for _, word := range strings.Fields(data[1]) { - capability.Name += strings.ToLower(word[0:1]) - } + return capabilitiesFound, nil +} - capability.Description = data[1] +func parseFna(fna uint) []*common.Capability { + // Bit masks values came from nvme-cli repo + // All names come from internal nvme-cli names + // We will *not* keep in sync as these names form our API + // https: // github.com/linux-nvme/nvme-cli/blob/v2.8/nvme-print-stdout.c#L2199-L2217 + + return []*common.Capability{ + { + Name: "fmns", + Description: "Format Applies to All/Single Namespace(s) (t:All, f:Single)", + Enabled: (fna&(0b1<<0))>>0 != 0, + }, + { + Name: "cens", + Description: "Crypto Erase Applies to All/Single Namespace(s) (t:All, f:Single)", + Enabled: (fna&(0b1<<1))>>1 != 0, + }, + { + Name: "cese", + Description: "Crypto Erase Supported as part of Secure Erase", + Enabled: (fna&(0b1<<2))>>2 != 0, + }, + } +} - if enabled != "0" { - capability.Enabled = true - } +func parseSanicap(sanicap uint) ([]*common.Capability, error) { + // Bit masks values came from nvme-cli repo + // All names come from internal nvme-cli names + // We will *not* keep in sync as these names form our API + // https://github.com/linux-nvme/nvme-cli/blob/v2.8/nvme-print-stdout.c#L2064-L2093 + + caps := []*common.Capability{ + { + Name: "cer", + Description: "Crypto Erase Sanitize Operation Supported", + Enabled: (sanicap&(0b1<<0))>>0 != 0, + }, + { + Name: "ber", + Description: "Block Erase Sanitize Operation Supported", + Enabled: (sanicap&(0b1<<1))>>1 != 0, + }, + { + Name: "owr", + Description: "Overwrite Sanitize Operation Supported", + Enabled: (sanicap&(0b1<<2))>>2 != 0, + }, + { + Name: "ndi", + Description: "No-Deallocate After Sanitize bit in Sanitize command Supported", + Enabled: (sanicap&(0b1<<29))>>29 != 0, + }, + } - capability.Description = data[1] - capabilitiesFound = append(capabilitiesFound, capability) - } + switch (sanicap & (0b11 << 30)) >> 30 { + case 0b00: + // nvme prints this out for 0b00: + // "Additional media modification after sanitize operation completes successfully is not defined" + // So I'm taking "not defined" literally since we can't really represent 2 bits in a bool + // If we ever want this as a bool we could maybe call it "dmmas" maybe? + case 0b01, 0b10: + caps = append(caps, &common.Capability{ + Name: "nodmmas", + Description: "Media is additionally modified after sanitize operation completes successfully", + Enabled: (sanicap&(0b11<<30))>>30 == 0b10, + }) + case 0b11: + return nil, errSanicapNODMMASReserved + default: + panic("unreachable") } - return capabilitiesFound, err + return caps, nil } // NewFakeNvme returns a mock nvme collector that returns mock data for use in tests. diff --git a/utils/nvme_test.go b/utils/nvme_test.go index a69e295c..0382de28 100644 --- a/utils/nvme_test.go +++ b/utils/nvme_test.go @@ -2,10 +2,12 @@ package utils import ( "context" + "strconv" "testing" "github.com/bmc-toolbox/common" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func Test_NvmeComponents(t *testing.T) { @@ -13,31 +15,25 @@ func Test_NvmeComponents(t *testing.T) { {Common: common.Common{ Serial: "Z9DF70I8FY3L", Vendor: "TOSHIBA", Model: "KXG60ZNV256G TOSHIBA", Description: "KXG60ZNV256G TOSHIBA", Firmware: &common.Firmware{Installed: "AGGA4104"}, ProductName: "NULL", Metadata: map[string]string{ - "Additional media modification after sanitize operation completes successfully is not defined": "false", - "Block Erase Sanitize Operation Not Supported": "false", - "Crypto Erase Applies to Single Namespace(s)": "false", - "Crypto Erase Sanitize Operation Not Supported": "false", - "Crypto Erase Support": "true", - "Crypto Erase Supported as part of Secure Erase": "true", - "Format Applies to Single Namespace(s)": "false", - "No-Deallocate After Sanitize bit in Sanitize command Supported": "false", - "Overwrite Sanitize Operation Not Supported": "false", - "Sanitize Support": "false", + "Block Erase Sanitize Operation Supported": "false", + "Crypto Erase Applies to All/Single Namespace(s) (t:All, f:Single)": "false", + "Crypto Erase Sanitize Operation Supported": "false", + "Crypto Erase Supported as part of Secure Erase": "true", + "Format Applies to All/Single Namespace(s) (t:All, f:Single)": "false", + "No-Deallocate After Sanitize bit in Sanitize command Supported": "false", + "Overwrite Sanitize Operation Supported": "false", }, }}, {Common: common.Common{ Serial: "Z9DF70I9FY3L", Vendor: "TOSHIBA", Model: "KXG60ZNV256G TOSHIBA", Description: "KXG60ZNV256G TOSHIBA", Firmware: &common.Firmware{Installed: "AGGA4104"}, ProductName: "NULL", Metadata: map[string]string{ - "Additional media modification after sanitize operation completes successfully is not defined": "false", - "Block Erase Sanitize Operation Not Supported": "false", - "Crypto Erase Applies to Single Namespace(s)": "false", - "Crypto Erase Sanitize Operation Not Supported": "false", - "Crypto Erase Support": "true", - "Crypto Erase Supported as part of Secure Erase": "true", - "Format Applies to Single Namespace(s)": "false", - "No-Deallocate After Sanitize bit in Sanitize command Supported": "false", - "Overwrite Sanitize Operation Not Supported": "false", - "Sanitize Support": "false", + "Block Erase Sanitize Operation Supported": "false", + "Crypto Erase Applies to All/Single Namespace(s) (t:All, f:Single)": "false", + "Crypto Erase Sanitize Operation Supported": "false", + "Crypto Erase Supported as part of Secure Erase": "true", + "Format Applies to All/Single Namespace(s) (t:All, f:Single)": "false", + "No-Deallocate After Sanitize bit in Sanitize command Supported": "false", + "Overwrite Sanitize Operation Supported": "false", }, }}, } @@ -66,54 +62,98 @@ func Test_NvmeDriveCapabilities(t *testing.T) { } var fixtureNvmeDeviceCapabilities = []*common.Capability{ - { - Name: "sanicap", - Description: "Sanitize Support", - Enabled: false, - }, - { - Name: "ammasocsind", - Description: "Additional media modification after sanitize operation completes successfully is not defined", - Enabled: false, - }, - { - Name: "nasbiscs", - Description: "No-Deallocate After Sanitize bit in Sanitize command Supported", - Enabled: false, - }, - { - Name: "osons", - Description: "Overwrite Sanitize Operation Not Supported", - Enabled: false, - }, - { - Name: "besons", - Description: "Block Erase Sanitize Operation Not Supported", - Enabled: false, - }, - { - Name: "cesons", - Description: "Crypto Erase Sanitize Operation Not Supported", - Enabled: false, - }, - { - Name: "fna", - Description: "Crypto Erase Support", - Enabled: true, - }, - { - Name: "cesapose", - Description: "Crypto Erase Supported as part of Secure Erase", - Enabled: true, - }, - { - Name: "ceatsn", - Description: "Crypto Erase Applies to Single Namespace(s)", - Enabled: false, - }, - { - Name: "fatsn", - Description: "Format Applies to Single Namespace(s)", - Enabled: false, - }, + {Name: "fmns", Description: "Format Applies to All/Single Namespace(s) (t:All, f:Single)", Enabled: false}, + {Name: "cens", Description: "Crypto Erase Applies to All/Single Namespace(s) (t:All, f:Single)", Enabled: false}, + {Name: "cese", Description: "Crypto Erase Supported as part of Secure Erase", Enabled: true}, + {Name: "cer", Description: "Crypto Erase Sanitize Operation Supported", Enabled: false}, + {Name: "ber", Description: "Block Erase Sanitize Operation Supported", Enabled: false}, + {Name: "owr", Description: "Overwrite Sanitize Operation Supported", Enabled: false}, + {Name: "ndi", Description: "No-Deallocate After Sanitize bit in Sanitize command Supported", Enabled: false}, +} + +func Test_NvmeParseFna(t *testing.T) { + // These are laid out so if you squint and pretend false/true are 0/1 they match the bit pattern of the int + // Its a map so order doesn't matter but I think it makes it easier to match a broken test to the code + wants := []map[string]bool{ + {"cese": false, "cens": false, "fmns": false}, + {"cese": false, "cens": false, "fmns": true}, + {"cese": false, "cens": true, "fmns": false}, + {"cese": false, "cens": true, "fmns": true}, + {"cese": true, "cens": false, "fmns": false}, + {"cese": true, "cens": false, "fmns": true}, + {"cese": true, "cens": true, "fmns": false}, + {"cese": true, "cens": true, "fmns": true}, + } + for i, want := range wants { + t.Run(strconv.Itoa(i), func(t *testing.T) { + caps := parseFna(uint(i)) + require.Len(t, caps, len(want)) + for _, cap := range caps { + require.Equal(t, want[cap.Name], cap.Enabled) + } + }) + } +} + +func Test_NvmeParseSanicap(t *testing.T) { + // These are laid out so if you squint and pretend false/true are 0/1 they match the bit pattern of the int + // Its a map so order doesn't matter but I think it makes it easier to match a broken test to the code + + // lower bits only + wants := []map[string]bool{ + {"owr": false, "ber": false, "cer": false}, + {"owr": false, "ber": false, "cer": true}, + {"owr": false, "ber": true, "cer": false}, + {"owr": false, "ber": true, "cer": true}, + {"owr": true, "ber": false, "cer": false}, + {"owr": true, "ber": false, "cer": true}, + {"owr": true, "ber": true, "cer": false}, + {"owr": true, "ber": true, "cer": true}, + } + for i, want := range wants { + // not testing ndi yet but its being returned + // don't want to add it above to avoid noise + want["ndi"] = false + t.Run(strconv.Itoa(i), func(t *testing.T) { + caps, err := parseSanicap(uint(i)) + require.NoError(t, err) + require.Len(t, caps, len(want)) + for _, cap := range caps { + require.Equal(t, want[cap.Name], cap.Enabled) + } + }) + } + + // higher bits only + wants = []map[string]bool{ + {"ndi": false}, + {"ndi": true}, + {"nodmmas": false, "ndi": false}, + {"nodmmas": false, "ndi": true}, + {"nodmmas": true, "ndi": false}, + {"nodmmas": true, "ndi": true}, + } + for i, want := range wants { + // not testing these now but they are being returned + // don't want to add them above to avoid noise + want["owr"] = false + want["ber"] = false + want["cer"] = false + i = (i << 29) + t.Run(strconv.Itoa(i), func(t *testing.T) { + caps, err := parseSanicap(uint(i)) + require.NoError(t, err) + require.Len(t, caps, len(want)) + for _, cap := range caps { + require.Equal(t, want[cap.Name], cap.Enabled) + } + }) + } + + i := 0b11 << 30 + t.Run(strconv.Itoa(i), func(t *testing.T) { + caps, err := parseSanicap(uint(i)) + require.Error(t, err) + require.Nil(t, caps) + }) }