|
15 | 15 | # |
16 | 16 | # Copyright: 2022 IBM |
17 | 17 | # Authors : Naresh Bannoth <nbannoth@linux.vnet.ibm.com> |
| 18 | +# Maram Srimannarayana Murthy<msmurthy@linux.ibm.com> |
18 | 19 |
|
19 | 20 |
|
20 | 21 | """ |
|
26 | 27 | import logging |
27 | 28 | import os |
28 | 29 | import re |
| 30 | +import sys |
29 | 31 | import time |
30 | 32 |
|
| 33 | +import pexpect # pylint: disable=E0401 |
| 34 | + |
31 | 35 | from avocado.utils import pci, process |
32 | 36 |
|
33 | 37 | LOGGER = logging.getLogger(__name__) |
@@ -125,7 +129,8 @@ def get_current_ns_list(controller_name, shared_ns=False): |
125 | 129 | namespaces_ids = get_current_ns_ids(controller_name) |
126 | 130 | if shared_ns: |
127 | 131 | 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 | + |
129 | 134 | for ns_id in namespaces_ids: |
130 | 135 | namespace_list.append(f"/dev/{controller_name}n{ns_id}") |
131 | 136 | return namespace_list |
@@ -529,3 +534,206 @@ def get_subsystem_using_ctrl_name(ctrl): |
529 | 534 | if ctrl in ctrls: |
530 | 535 | return get_subsys_name_with_nqn(device_nqn) |
531 | 536 | 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 |
0 commit comments