Skip to content

Commit c7b8ea2

Browse files
committed
Added utilities definitions for nvme sed functionality
Includes following 1. Initilaize SED on nvme disk. 2. Revert SED on nvme disk. 3. SED lock on nvme disk with and without key. 4. SED unlock on nvme disk with and without key. 5. Changes SED password. 6. Added pexpect definition to handle multiple inputs to same command. 7. Added dd definition to disk.py which return bool based of disk read. 8. Added new values to spell check file. Applied pep8 style cding to file Signed-off-by: Maram Srimannarayana Murthy <msmurthy@linux.vnet.ibm.com>
1 parent ffe8d84 commit c7b8ea2

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)