Repository: sonic-net/sonic-utilities

Issue:
[GCU] Handle non-compliant leaf-list with string values
#### Description
sonic_yang_mgmt follows these [guidelines](https://github.com/Azure/SONiC/blob/master/doc/mgmt/SONiC_YANG_Model_Guidelines.md) in order to convert from `config_db` to `config_yang` and vice versa.
in GCU we rely on the same [guidelines](https://github.com/Azure/SONiC/blob/master/doc/mgmt/SONiC_YANG_Model_Guidelines.md) to convert from `config_db path` to `config_yang xpath` and vice versa.

There were modifications to how sonic_yang_mgmt converts data recently, and these changes are not supported by gcu, the changes are the following:
- Supporting `Non compliant leaf list in config_db schema` https://github.com/Azure/sonic-buildimage/pull/10291

## Steps to reproduce the issue
1.
2.
3.

#### Describe the results you received


#### Describe the results you expected


#### Additional information you deem important (e.g. issue happens only occasionally)


#### Output of `show version`

```
(paste your output here)
```

<!--
     Also attach debug file produced by `sudo generate_dump`
-->




Discussion:

Resolving patch:
diff --git a/generic_config_updater/gu_common.py b/generic_config_updater/gu_common.py
--- a/generic_config_updater/gu_common.py
+++ b/generic_config_updater/gu_common.py
@@ -750,6 +750,19 @@ def _get_path_tokens_from_leaf(self, model, token_index, xpath_tokens, config):
             # leaf_list_name = match.group(1)
             leaf_list_value = match.group(1)
             list_config = config[leaf_list_name]
+            # Workaround for those fields who is defined as leaf-list in YANG model but have string value in config DB
+            # No need to lookup the item index in ConfigDb since the list is represented as a string, return path to string immediately
+            # Example:
+            #   xpath: /sonic-buffer-port-egress-profile-list:sonic-buffer-port-egress-profile-list/BUFFER_PORT_EGRESS_PROFILE_LIST/BUFFER_PORT_EGRESS_PROFILE_LIST_LIST[port='Ethernet9']/profile_list[.='egress_lossy_profile']
+            #   path: /BUFFER_PORT_EGRESS_PROFILE_LIST/Ethernet9/profile_list
+            if isinstance(list_config, str):
+                return [leaf_list_name]
+
+            if not isinstance(list_config, list):
+                raise ValueError(f"list_config is expected to be of type list or string. Found {type(list_config)}.\n  " + \
+                                 f"model: {model}\n  token_index: {token_index}\n  " + \
+                                 f"xpath_tokens: {xpath_tokens}\n  config: {config}")
+
             list_idx = list_config.index(leaf_list_value)
             return [leaf_list_name, list_idx]
 


Test patch:
diff --git a/tests/generic_config_updater/files/config_db_with_profile_list.json b/tests/generic_config_updater/files/config_db_with_profile_list.json
new file mode 100644
--- /dev/null
+++ b/tests/generic_config_updater/files/config_db_with_profile_list.json
@@ -0,0 +1,42 @@
+{
+    "BUFFER_POOL": {
+        "egress_lossless_pool": {
+           "mode": "static",
+           "size": "33004032",
+           "xoff": "196608",
+           "type": "egress"
+        },
+        "egress_lossy_pool": {
+            "size": "12766208",
+            "type": "egress",
+            "mode": "dynamic"
+        }
+    },
+    "BUFFER_PROFILE": {
+        "egress_lossless_profile": {
+            "pool":"egress_lossless_pool",
+            "size":"1518",
+            "dynamic_th":"3"
+        },
+        "egress_lossy_profile": {
+            "pool":"egress_lossy_pool",
+            "size":"1518",
+            "dynamic_th":"3"
+        }
+    },
+    "BUFFER_PORT_EGRESS_PROFILE_LIST": {
+        "Ethernet9": {
+            "profile_list": "egress_lossless_profile,egress_lossy_profile"
+        }
+    },
+    "PORT": {
+        "Ethernet9": {
+            "alias": "Eth3/2",
+            "lanes": "74",
+            "description": "",
+            "speed": "11100",
+            "tpid": "0x8100",
+            "admin_status": "up"
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/generic_config_updater/gu_common_test.py b/tests/generic_config_updater/gu_common_test.py
--- a/tests/generic_config_updater/gu_common_test.py
+++ b/tests/generic_config_updater/gu_common_test.py
@@ -779,6 +779,19 @@ def test_find_ref_paths__does_not_remove_tables_without_yang(self):
         # Assert
         self.assertEqual(expected_config, config)
 
+    def test_find_ref_paths__ref_path_is_leaflist_in_yang_but_string_in_config_db__path_to_string_returned(self):
+        # Arrange
+        path = "/BUFFER_PROFILE/egress_lossless_profile"
+        expected = [
+            "/BUFFER_PORT_EGRESS_PROFILE_LIST/Ethernet9/profile_list",
+        ]
+
+        # Act
+        actual = self.path_addressing.find_ref_paths(path, Files.CONFIG_DB_WITH_PROFILE_LIST)
+
+        # Assert
+        self.assertEqual(expected, actual)
+
     def test_convert_path_to_xpath(self):
         def check(path, xpath, config=None):
             if not config:
@@ -839,6 +852,9 @@ def check(path, xpath, config=None):
         check(path="/LLDP/GLOBAL/mode",
               xpath="/sonic-lldp:sonic-lldp/LLDP/GLOBAL/mode",
               config=Files.CONFIG_DB_WITH_LLDP)
+        check(path="/BUFFER_PORT_EGRESS_PROFILE_LIST/Ethernet9/profile_list",
+              xpath="/sonic-buffer-port-egress-profile-list:sonic-buffer-port-egress-profile-list/BUFFER_PORT_EGRESS_PROFILE_LIST/BUFFER_PORT_EGRESS_PROFILE_LIST_LIST[port='Ethernet9']/profile_list",
+              config=Files.CONFIG_DB_WITH_PROFILE_LIST)
 
     def test_convert_xpath_to_path(self):
         def check(xpath, path, config=None):
@@ -914,6 +930,12 @@ def check(xpath, path, config=None):
         check(xpath="/sonic-lldp:sonic-lldp/LLDP/GLOBAL/mode",
               path="/LLDP/GLOBAL/mode",
               config=Files.CONFIG_DB_WITH_LLDP)
+        check(xpath="/sonic-buffer-port-egress-profile-list:sonic-buffer-port-egress-profile-list/BUFFER_PORT_EGRESS_PROFILE_LIST/BUFFER_PORT_EGRESS_PROFILE_LIST_LIST[port='Ethernet9']/profile_list",
+              path="/BUFFER_PORT_EGRESS_PROFILE_LIST/Ethernet9/profile_list",
+              config=Files.CONFIG_DB_WITH_PROFILE_LIST)
+        check(xpath="/sonic-buffer-port-egress-profile-list:sonic-buffer-port-egress-profile-list/BUFFER_PORT_EGRESS_PROFILE_LIST/BUFFER_PORT_EGRESS_PROFILE_LIST_LIST[port='Ethernet9']/profile_list[.='egress_lossy_profile']",
+              path="/BUFFER_PORT_EGRESS_PROFILE_LIST/Ethernet9/profile_list",
+              config=Files.CONFIG_DB_WITH_PROFILE_LIST)
 
     def test_has_path(self):
         def check(config, path, expected):