Repository: ansible/ansible
Pull request: Get serial number of NVMe device without sg_inq
##### SUMMARY
We can get NVMe device's serial number from `/sys/block/<name>/device/serial` file. We don't need to have `sg_inq`, which requires root.

Fixes #66663

##### ISSUE TYPE
<!--- Pick one below and delete the rest -->
- Bugfix Pull Request

##### COMPONENT NAME
Facts collection, linux.py


Problem statement:
device serial number not retrieved without sudo

##### SUMMARY
NVMe devices can have their serial number read by reading (eg) /sys/block/nvme1n1/device/serial, without having to try and run sg_inq, which requires root.

##### ISSUE TYPE
- Bug Report

##### COMPONENT NAME
Facts collection, linux.py

##### ANSIBLE VERSION
```paste below
$ ansible --version
ansible 2.10.0.dev0
  config file = None
  configured module search path = ['/local/apps/egsadmin/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /local/apps/egsadmin/randy/ansible-src-current/lib/ansible
  executable location = /local/apps/egsadmin/randy/ansible-src-current/bin/ansible
  python version = 3.6.8 (default, Jun 11 2019, 15:15:01) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)]

```

##### CONFIGURATION
<!--- Paste verbatim output from "ansible-config dump --only-changed" between quotes -->
```paste below
$ ansible-config dump --only-changed
<nothing>
```

##### OS / ENVIRONMENT
<!--- Provide all relevant information below, e.g. target OS versions, network device firmware, etc. -->
AWS NVMe-based systems

##### STEPS TO REPRODUCE
<!--- Describe exactly how to reproduce the problem, using a minimal test-case -->
Run setup task with and without "become" on a system or VM with NVMe and observe that "serial" only appears with "become"

<!--- HINT: You can paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- Describe what you expected to happen when running the steps above -->
ansible_devices[*].serial is populated for NVMe, regardless of become

##### ACTUAL RESULTS
<!--- Describe what actually happened. If possible run with extra verbosity (-vvvv) -->
ansible_devices[*].serial is populated for NVMe, regardless of become

Hints from issue discussion:
Files identified in the description:
* [`lib/ansible/module_utils/facts/hardware/linux.py`](https://github.com/ansible/ansible/blob/devel/lib/ansible/module_utils/facts/hardware/linux.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->

Commit message: Add changlelog for #70284

Patch to review:
diff --git a/changelogs/fragments/70284-facts-get-nvme-serial-from-file.yml b/changelogs/fragments/70284-facts-get-nvme-serial-from-file.yml
new file mode 100644
index 00000000000000..9e89436c039aca
--- /dev/null
+++ b/changelogs/fragments/70284-facts-get-nvme-serial-from-file.yml
@@ -0,0 +1,2 @@
+bugfixes:
+  - Facts collection - get serial number of NVMe device without sg_inq (https://github.com/ansible/ansible/issues/66663).
diff --git a/lib/ansible/module_utils/facts/hardware/linux.py b/lib/ansible/module_utils/facts/hardware/linux.py
index c468e685120954..3a7dcdeb4dc683 100644
--- a/lib/ansible/module_utils/facts/hardware/linux.py
+++ b/lib/ansible/module_utils/facts/hardware/linux.py
@@ -700,6 +700,9 @@ def get_device_facts(self):
 
             sg_inq = self.module.get_bin_path('sg_inq')
 
+            # we can get NVMe device's serial number from /sys/block/<name>/device/serial
+            serial_path = "/sys/block/%s/device/serial" % (block)
+
             if sg_inq:
                 device = "/dev/%s" % (block)
                 rc, drivedata, err = self.module.run_command([sg_inq, device])
@@ -707,6 +710,10 @@ def get_device_facts(self):
                     serial = re.search(r"Unit serial number:\s+(\w+)", drivedata)
                     if serial:
                         d['serial'] = serial.group(1)
+            elif os.path.exists(serial_path):
+                with open(serial_path, 'r') as f:
+                    serial = f.read()
+                    d['serial'] = serial.strip('\n')
 
             for key, test in [('removable', '/removable'),
                               ('support_discard', '/queue/discard_granularity'),


Code review comments:
diff_hunk: @@ -700,13 +700,20 @@ def get_device_facts(self):
 
             sg_inq = self.module.get_bin_path('sg_inq')
 
+            # we can get NVMe device's serial number from /sys/block/<name>/device/serial
+            serial_path = "/sys/block/%s/device/serial" % (block)
+
             if sg_inq:
                 device = "/dev/%s" % (block)
                 rc, drivedata, err = self.module.run_command([sg_inq, device])
                 if rc == 0:
                     serial = re.search(r"Unit serial number:\s+(\w+)", drivedata)
                     if serial:
                         d['serial'] = serial.group(1)
+            elif os.path.exists(serial_path):
+                with open(serial_path, 'r') as f:
path: lib/ansible/module_utils/facts/hardware/linux.py
text: @user1:
Prefer `get_file_content(serial_path)` here -- which will also handle the `.strip()` for you.

diff_hunk: @@ -700,13 +700,18 @@ def get_device_facts(self):
 
             sg_inq = self.module.get_bin_path('sg_inq')
 
+            # we can get NVMe device's serial number from /sys/block/<name>/device/serial
+            serial_path = "/sys/block/%s/device/serial" % (block)
+
             if sg_inq:
                 device = "/dev/%s" % (block)
                 rc, drivedata, err = self.module.run_command([sg_inq, device])
                 if rc == 0:
                     serial = re.search(r"Unit serial number:\s+(\w+)", drivedata)
                     if serial:
                         d['serial'] = serial.group(1)
+            elif os.path.exists(serial_path):
+                d['serial'] = get_file_content(serial_path)
path: lib/ansible/module_utils/facts/hardware/linux.py
text: @user1:
```suggestion
            else:
                serial = get_file_content(serial_path)
                if serial:
                    d['serial'] = serial
```