Skip to content

Commit 2d58369

Browse files
Merge pull request #6196 from maramsmurthy/smaram_sed_utils
Added utilities definitions for nvme sed functionality
2 parents e37d544 + c7b8ea2 commit 2d58369

File tree

3 files changed

+233
-7
lines changed

3 files changed

+233
-7
lines changed

avocado/utils/disk.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,19 @@ def delete_loop_device(device):
108108
return True
109109

110110

111+
def dd_read_records_device(disk, read_records="1", out_file="/tmp/data"):
112+
"""
113+
Reads mentioned number of blocks from device to destination
114+
115+
:param disk: disk absolute path
116+
:param read_records: Numbers of blocks to read from disk
117+
:param out_file: Destination file
118+
:rtype: boolean
119+
"""
120+
cmd = f"dd count={read_records} if={disk} of={out_file}"
121+
return not process.system(cmd, ignore_status=True, sudo=True)
122+
123+
111124
def get_disks():
112125
"""
113126
Returns the physical "hard drives" available on this system
@@ -130,11 +143,11 @@ def get_disks():
130143
except json.JSONDecodeError as je:
131144
raise DiskError(f"Error occurred while parsing JSON data: {je}") from je
132145
disks = []
133-
to_process = json_data.get("blockdevices", [])
134-
for device in to_process:
135-
disks.append(device.get("name"))
146+
for device in json_data["blockdevices"]:
147+
disks.append(device["name"])
136148
if "children" in device:
137-
to_process.extend(device["children"])
149+
for child in device["children"]:
150+
disks.append(child["name"])
138151
return disks
139152

140153

@@ -460,8 +473,9 @@ def get_io_scheduler_list(device_name):
460473
:param device_name: Device name example like sda
461474
:return: list of IO scheduler
462475
"""
463-
with open(__sched_path(device_name), "r", encoding="utf-8") as fl:
464-
return fl.read().translate(str.maketrans("[]", " ")).split()
476+
with open(__sched_path(device_name), "r", encoding="utf-8") as f:
477+
names = f.read()
478+
return names.translate(str.maketrans("[]", " ")).split()
465479

466480

467481
def get_io_scheduler(device_name):

avocado/utils/nvme.py

Lines changed: 209 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#
1616
# Copyright: 2022 IBM
1717
# Authors : Naresh Bannoth <nbannoth@linux.vnet.ibm.com>
18+
# Maram Srimannarayana Murthy<msmurthy@linux.ibm.com>
1819

1920

2021
"""
@@ -26,8 +27,11 @@
2627
import logging
2728
import os
2829
import re
30+
import sys
2931
import time
3032

33+
import pexpect # pylint: disable=E0401
34+
3135
from avocado.utils import pci, process
3236

3337
LOGGER = logging.getLogger(__name__)
@@ -125,7 +129,8 @@ def get_current_ns_list(controller_name, shared_ns=False):
125129
namespaces_ids = get_current_ns_ids(controller_name)
126130
if shared_ns:
127131
subsys = get_subsystem_using_ctrl_name(controller_name)
128-
controller_name = f"nvme{subsys[len('nvme-subsy'):]}"
132+
controller_name = f"nvme{subsys[len('nvme-subsys'):]}"
133+
129134
for ns_id in namespaces_ids:
130135
namespace_list.append(f"/dev/{controller_name}n{ns_id}")
131136
return namespace_list
@@ -529,3 +534,206 @@ def get_subsystem_using_ctrl_name(ctrl):
529534
if ctrl in ctrls:
530535
return get_subsys_name_with_nqn(device_nqn)
531536
return ""
537+
538+
539+
def get_nvme_sed_discover_parameters(namespace):
540+
"""
541+
Fetches values from nvme SED discover command
542+
543+
:param namespace: NVMe namespace path
544+
:rtype: dictionary
545+
:raises: NvmeException on command failure
546+
:rtype: dictionary
547+
"""
548+
cmd = f"nvme sed discover {namespace}"
549+
data = process.run(cmd, ignore_status=True, sudo=True, shell=True).stdout_text
550+
pattern = r"\tLocking Supported:\s*(.*)\n\tLocking Feature Enabled:\s*(.*)\n\tLocked:\s*(.*)"
551+
match = re.search(pattern, data, re.MULTILINE)
552+
if match:
553+
locking_features = {
554+
"Locking Supported": match.group(1).strip(),
555+
"Locking Feature Enabled": match.group(2).strip(),
556+
"Locked": match.group(3).strip(),
557+
}
558+
return locking_features
559+
return {}
560+
561+
562+
def is_lockdown_supported(namespace):
563+
"""
564+
Fetches information based on namespace
565+
Checks if SED locking is supported for the given namespace
566+
567+
:param namespace: NVMe namespace path
568+
:rtype: boolean
569+
"""
570+
lockdown_attr = get_nvme_sed_discover_parameters(namespace)
571+
return lockdown_attr.get("Locking Supported") == "Yes"
572+
573+
574+
def is_lockdown_enabled(namespace):
575+
"""
576+
Fetches information based on namespace
577+
Checks if SED locking feature is enabled for the given namespace
578+
579+
:param namespace: NVMe namespace path
580+
:rtype: boolean
581+
"""
582+
lockdown_attr = get_nvme_sed_discover_parameters(namespace)
583+
return lockdown_attr.get("Locking Feature Enabled") == "Yes"
584+
585+
586+
def is_drive_locked(namespace):
587+
"""
588+
Fetches information based on namespace
589+
Checks if the drive is currently locked for the given namespace
590+
591+
:param namespace: NVMe namespace path
592+
:rtype: boolean
593+
"""
594+
lockdown_attr = get_nvme_sed_discover_parameters(namespace)
595+
return lockdown_attr.get("Locked") == "Yes"
596+
597+
598+
def initialize_sed_locking(namespace, password):
599+
"""
600+
Enables and initializes SED feature on nvme disk
601+
602+
:param namespace: NVMe namespace path
603+
:param password: SED password
604+
"""
605+
if not is_lockdown_supported(namespace):
606+
raise NvmeException(f"SED initialize not supported on {namespace}")
607+
if is_lockdown_enabled(namespace):
608+
raise NvmeException(
609+
f"nvme drive {namespace} locking is enabled, can't initialize it"
610+
)
611+
pexpect_cmd_execution(
612+
f"nvme sed initialize {namespace}",
613+
[("New Password:", password), ("Re-enter New Password:", password)],
614+
)
615+
if not is_lockdown_enabled(namespace):
616+
raise NvmeException(f"Failed to initialize nvme disk {namespace}")
617+
618+
619+
def revert_sed_locking(namespace, password, destructive=False):
620+
"""
621+
Reverts SED locking state to factory defaults
622+
623+
:param namespace: NVMe namespace path
624+
:param password: Current SED password
625+
:raises: NvmeException if revert is not supported, drive is not initialized,
626+
drive is locked, or revert operation fails
627+
"""
628+
if not is_lockdown_supported(namespace):
629+
raise NvmeException(f"Revert not supported on {namespace}")
630+
if not is_lockdown_enabled(namespace):
631+
raise NvmeException(
632+
f"nvme drive {namespace} locking is not enabled, can't revert it"
633+
)
634+
if is_drive_locked(namespace):
635+
raise NvmeException(f"Reverting not valid when drive is locked {namespace}")
636+
if destructive:
637+
pexpect_cmd_execution(
638+
f"nvme sed revert -e {namespace}",
639+
[
640+
("Destructive revert erases drive data. Continue (y/n)?", "y"),
641+
("Are you sure (y/n)?", "y"),
642+
("Password:", password),
643+
],
644+
)
645+
else:
646+
pexpect_cmd_execution(f"nvme sed revert {namespace}", [("Password:", password)])
647+
if is_lockdown_enabled(namespace):
648+
raise NvmeException(f"Failed to revert {namespace}")
649+
650+
651+
def unlock_drive(namespace, with_pass_key=""):
652+
"""
653+
Unlocks SED locked driver
654+
655+
:param namespace: NVMe namespace path
656+
:param with_pass_key: Password for unlocking (if empty, no password prompt)
657+
"""
658+
if not is_drive_locked(namespace):
659+
raise NvmeException(f"Drive is not locked, unlock failed for {namespace}")
660+
cmd = f"nvme sed unlock {namespace}"
661+
if with_pass_key:
662+
cmd = f"{cmd} -k"
663+
pexpect_cmd_execution(cmd, [("Password:", with_pass_key)])
664+
elif process.system(cmd, shell=True, ignore_status=True):
665+
raise NvmeException(f"namespace {namespace} unlock failed")
666+
if is_drive_locked(namespace):
667+
raise NvmeException(f"Unlock failed for {namespace}")
668+
669+
670+
def lock_drive(namespace, with_pass_key=""):
671+
"""
672+
SED lock enables on nvme drive
673+
674+
:param namespace: NVMe namespace path
675+
:param with_pass_key: Password for locking (if empty, no password prompt)
676+
"""
677+
if is_drive_locked(namespace):
678+
raise NvmeException(f"namespace {namespace} already in locked state")
679+
cmd = f"nvme sed lock {namespace}"
680+
if with_pass_key:
681+
cmd = f"{cmd} -k"
682+
pexpect_cmd_execution(cmd, [("Password:", with_pass_key)])
683+
elif process.system(cmd, shell=True, ignore_status=True):
684+
raise NvmeException(f"namespace {namespace} lock failed")
685+
if not is_drive_locked(namespace):
686+
raise NvmeException(f"locking failed for {namespace}")
687+
688+
689+
def change_sed_password(namespace, pwd1, pwd2):
690+
"""
691+
Changes the SED password for the specified namespace
692+
693+
:param namespace: NVMe namespace path
694+
:param pwd1: Current SED password
695+
:param pwd2: New SED password
696+
:raises: NvmeException if password change is not supported or drive is not initialized
697+
"""
698+
if not is_lockdown_supported(namespace):
699+
raise NvmeException(f"Change password not supported on {namespace}")
700+
if not is_lockdown_enabled(namespace):
701+
raise NvmeException(
702+
f"nvme drive {namespace} is not initialized, can't change password"
703+
)
704+
pexpect_cmd_execution(
705+
f"nvme sed password {namespace}",
706+
[
707+
("Password:", pwd1),
708+
("New Password:", pwd2),
709+
("Re-enter New Password:", pwd2),
710+
],
711+
)
712+
713+
714+
def pexpect_cmd_execution(cmd, list_of_expect_sendline):
715+
"""
716+
Execute command using pexpect with multiple expect/sendline interactions
717+
718+
:param cmd: Command to execute
719+
:param list_of_expect_sendline: List of (expect_pattern, sendline_value) tuples
720+
:raises: NvmeException on command failures
721+
"""
722+
try:
723+
LOGGER.info("Executing command using pexpect: %s", cmd)
724+
pexpect_handle = pexpect.spawn(cmd)
725+
pexpect_handle.log_read = sys.stdout
726+
for expect, value in list_of_expect_sendline:
727+
pexpect_handle.expect(expect, timeout=30)
728+
pexpect_handle.sendline(value)
729+
LOGGER.debug("Matched String: %s", pexpect_handle.after.strip())
730+
LOGGER.debug("Pexpect output: %s", pexpect_handle.before.strip())
731+
time.sleep(3)
732+
pexpect_handle.close()
733+
LOGGER.info("%s command executed successfully", cmd)
734+
except pexpect.exceptions.TIMEOUT as e:
735+
LOGGER.error("Command timed out: %s", cmd)
736+
raise NvmeException(f"Command timeout: {cmd}") from e
737+
except pexpect.exceptions.EOF as e:
738+
LOGGER.error("Command ended unexpectedly: %s", cmd)
739+
raise NvmeException(f"Command failed unexpectedly: {cmd}") from e

spell.ignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
msmurthy
2+
sendline
3+
SED
4+
pexpect
15
nvme
26
nbannoth
37
Naresh

0 commit comments

Comments
 (0)