Blob Blame History Raw
From e3ad16e0f5bd0dfa172647b4fc3213b9e119fdb3 Mon Sep 17 00:00:00 2001
From: Vitaly Kuznetsov <vkuznets@redhat.com>
Date: Fri, 7 Feb 2020 12:19:12 +0100
Subject: [PATCH] Add support for Gen2 VM resource disks (#1654)

RH-Author: Vitaly Kuznetsov <vkuznets@redhat.com>
Message-id: <20200207121912.720071-1-vkuznets@redhat.com>
Patchwork-id: 93767
O-Subject: [RHEL7 WALinuxAgent PATCH] Add support for Gen2 VM resource disks (#1654)
Bugzilla: 1714167
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Acked-by: Eduardo Otubo <otubo@redhat.com>

Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1714167
Brew: https://brewweb.engineering.redhat.com/brew/taskinfo?taskID=26270155
Branch: rhel7/master-2.2.38
Tested: Gen1 by me, Gen2 by QE

Upstream commit besides fixing the issue does a lot of unrelated
changes and these don't backport cleanly to 2.2.38, let's just drop
them for now. In case we decide to rebase WALinuxAgent later in RHEL7
lifetime this backport can just be dropped.

commit 93ecdc451d74165c25e40985a20a8fcfbfe55d57
Author: Thomas Stringer <thstring@microsoft.com>
Date:   Mon Oct 21 14:20:27 2019 -0400

    Add support for Gen2 VM resource disks (#1654)

    * (wip) add gen2 resource disk support

    * remove device names

    * move device id constant

    * add debian testing and unstable support

    * add tests for resource disk discovery

    * remove unnecessary patch for test

    * refactor checking for both gen devices

    * rename debian util classes to reflect current

    * catch file not found errors for older python versions

    * refactor out filesystem search for device

    * fix typo

    * remove newline from input string

    * refactor device enumeration to own method

    * fix bug with breaking from loop

Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>

Conflicts:
	azurelinuxagent/common/osutil/debian.py
	azurelinuxagent/common/osutil/factory.py
	tests/common/osutil/test_default.py
	tests/common/osutil/test_factory.py

Drop all changes unrelated to the resource disk issue. Drop test hunks
as they don't apply cleanly and to simplify future (possible) rebase (and
because we don't run these tests anyway).

Signed-off-by: Vitaly Kuznetsov <vkuznets@redhat.com>
---
 azurelinuxagent/common/osutil/default.py | 91 ++++++++++++++++++++++++--------
 1 file changed, 70 insertions(+), 21 deletions(-)

diff --git a/azurelinuxagent/common/osutil/default.py b/azurelinuxagent/common/osutil/default.py
index 189d8fe..daf1b70 100644
--- a/azurelinuxagent/common/osutil/default.py
+++ b/azurelinuxagent/common/osutil/default.py
@@ -97,6 +97,8 @@ IP_COMMAND_OUTPUT = re.compile('^\d+:\s+(\w+):\s+(.*)$')
 
 BASE_CGROUPS = '/sys/fs/cgroup'
 
+STORAGE_DEVICE_PATH = '/sys/bus/vmbus/devices/'
+GEN2_DEVICE_ID = 'f8b3781a-1e82-4818-a1c3-63d806ec15bb'
 
 class DefaultOSUtil(object):
     def __init__(self):
@@ -1167,6 +1169,67 @@ class DefaultOSUtil(object):
                     return tokens[2] if len(tokens) > 2 else None
         return None
 
+    @staticmethod
+    def _enumerate_device_id():
+        """
+        Enumerate all storage device IDs.
+
+        Args:
+            None
+
+        Returns:
+            Iterator[Tuple[str, str]]: VmBus and storage devices.
+        """
+
+        if os.path.exists(STORAGE_DEVICE_PATH):
+            for vmbus in os.listdir(STORAGE_DEVICE_PATH):
+                deviceid = fileutil.read_file(os.path.join(STORAGE_DEVICE_PATH, vmbus, "device_id"))
+                guid = deviceid.strip('{}\n')
+                yield vmbus, guid
+
+    @staticmethod
+    def search_for_resource_disk(gen1_device_prefix, gen2_device_id):
+        """
+        Search the filesystem for a device by ID or prefix.
+
+        Args:
+            gen1_device_prefix (str): Gen1 resource disk prefix.
+            gen2_device_id (str): Gen2 resource device ID.
+
+        Returns:
+            str: The found device.
+        """
+
+        device = None
+        # We have to try device IDs for both Gen1 and Gen2 VMs.
+        logger.info('Searching gen1 prefix {0} or gen2 {1}'.format(gen1_device_prefix, gen2_device_id))
+        try:
+            for vmbus, guid in DefaultOSUtil._enumerate_device_id():
+                if guid.startswith(gen1_device_prefix) or guid == gen2_device_id:
+                    for root, dirs, files in os.walk(STORAGE_DEVICE_PATH + vmbus):
+                        root_path_parts = root.split('/')
+                        # For Gen1 VMs we only have to check for the block dir in the
+                        # current device. But for Gen2 VMs all of the disks (sda, sdb,
+                        # sr0) are presented in this device on the same SCSI controller.
+                        # Because of that we need to also read the LUN. It will be:
+                        #   0 - OS disk
+                        #   1 - Resource disk
+                        #   2 - CDROM
+                        if root_path_parts[-1] == 'block' and (
+                                guid != gen2_device_id or
+                                root_path_parts[-2].split(':')[-1] == '1'):
+                            device = dirs[0]
+                            return device
+                        else:
+                            # older distros
+                            for d in dirs:
+                                if ':' in d and "block" == d.split(':')[0]:
+                                    device = d.split(':')[1]
+                                    return device
+        except (OSError, IOError) as exc:
+            logger.warn('Error getting device for {0} or {1}: {2}', gen1_device_prefix, gen2_device_id, ustr(exc))
+        return None
+
     def device_for_ide_port(self, port_id):
         """
         Return device name attached to ide port 'n'.
@@ -1177,27 +1240,13 @@ class DefaultOSUtil(object):
         if port_id > 1:
             g0 = "00000001"
             port_id = port_id - 2
-        device = None
-        path = "/sys/bus/vmbus/devices/"
-        if os.path.exists(path):
-            try:
-                for vmbus in os.listdir(path):
-                    deviceid = fileutil.read_file(os.path.join(path, vmbus, "device_id"))
-                    guid = deviceid.lstrip('{').split('-')
-                    if guid[0] == g0 and guid[1] == "000" + ustr(port_id):
-                        for root, dirs, files in os.walk(path + vmbus):
-                            if root.endswith("/block"):
-                                device = dirs[0]
-                                break
-                            else:
-                                # older distros
-                                for d in dirs:
-                                    if ':' in d and "block" == d.split(':')[0]:
-                                        device = d.split(':')[1]
-                                        break
-                        break
-            except OSError as oe:
-                logger.warn('Could not obtain device for IDE port {0}: {1}', port_id, ustr(oe))
+
+        gen1_device_prefix = '{0}-000{1}'.format(g0, port_id)
+        device = DefaultOSUtil.search_for_resource_disk(
+            gen1_device_prefix=gen1_device_prefix,
+            gen2_device_id=GEN2_DEVICE_ID
+        )
+        logger.info('Found device: {0}'.format(device))
         return device
 
     def set_hostname_record(self, hostname):
-- 
1.8.3.1