Skip to content

DataSourceVMware: fix var use before init#1674

Merged
blackboxsw merged 1 commit into
canonical:mainfrom
akutz:fix/lp-1987005
Aug 22, 2022
Merged

DataSourceVMware: fix var use before init#1674
blackboxsw merged 1 commit into
canonical:mainfrom
akutz:fix/lp-1987005

Conversation

@akutz

@akutz akutz commented Aug 19, 2022

Copy link
Copy Markdown
Contributor

Proposed Commit Message

DataSourceVMware: fix var use before init

This patch fixes an issue in the DataSourceVMware code where the
variable ipv6_ready was used in a logging statement before it was
initialized. Now the variable is initialized, avoiding the panic.

LP: #1987005

Additional Context

Test Steps

I reproduced the bug by:

  • applying the following patch to move the logging statement into a position where it would always be executed:

    diff --git a/cloudinit/sources/DataSourceVMware.py b/cloudinit/sources/DataSourceVMware.py
    index 80a01e89..b2a54d0b 100644
    --- a/cloudinit/sources/DataSourceVMware.py
    +++ b/cloudinit/sources/DataSourceVMware.py
    @@ -822,6 +822,14 @@ def wait_on_network(metadata):
                 if not ipv6_ready:
                     host_info = None
     
    +        LOG.debug(
    +            "waiting on network: wait4=%s, ready4=%s, wait6=%s, ready6=%s",
    +            wait_on_ipv4,
    +            ipv4_ready,
    +            wait_on_ipv6,
    +            ipv6_ready,
    +        )
    +
             if host_info is None:
                 LOG.debug(
                     "waiting on network: wait4=%s, ready4=%s, wait6=%s, ready6=%s",
  • and then running the following command locally:

    $ PYTHONPATH="$(pwd)" python3 cloudinit/sources/DataSourceVMware.py
    Traceback (most recent call last):
      File "cloudinit/sources/DataSourceVMware.py", line 865, in <module>
        main()
      File "cloudinit/sources/DataSourceVMware.py", line 859, in main
        host_info = wait_on_network(metadata)
      File "cloudinit/sources/DataSourceVMware.py", line 830, in wait_on_network
        ipv6_ready,
    UnboundLocalError: local variable 'ipv6_ready' referenced before assignment
  • then I applied the following patch on top of the one in this PR:

    diff --git a/cloudinit/sources/DataSourceVMware.py b/cloudinit/sources/DataSourceVMware.py
    index cf5f9f52..7cef6eaf 100644
    --- a/cloudinit/sources/DataSourceVMware.py
    +++ b/cloudinit/sources/DataSourceVMware.py
    @@ -822,6 +822,14 @@ def wait_on_network(metadata):
                 if not ipv6_ready:
                     host_info = None
     
    +        LOG.debug(
    +            "waiting on network: wait4=%s, ready4=%s, wait6=%s, ready6=%s",
    +            wait_on_ipv4,
    +            ipv4_ready,
    +            wait_on_ipv6,
    +            ipv6_ready,
    +        )
    +
             if host_info is None:
                 LOG.debug(
                     "waiting on network: wait4=%s, ready4=%s, wait6=%s, ready6=%s",
  • and tried to reproduce the issue once more:

    $ PYTHONPATH="$(pwd)" python3 cloudinit/sources/DataSourceVMware.py
    2022-08-19 13:48:51,762 - DataSourceVMware.py[DEBUG]: waiting on network: wait4=True, ready4=True, wait6=False, ready6=None
    2022-08-19 13:48:51,763 - DataSourceVMware.py[DEBUG]: waiting on network complete
    {
     "hostname": "akutz-a03.vmware.com",
     "local-hostname": "akutz-a03.vmware.com",
     "local-ipv4": "192.168.0.188",
     "local-ipv6": "fe80::10ec:eca5:84a2:eb15%en7",
     "local_hostname": "akutz-a03.vmware.com",
     "network": {
      "config": {
       "dhcp": true
      },
      "interfaces": {
       "by-ipv4": {
        "10.25.121.155": {
         "netmask": "255.255.255.255",
         "peer": "10.25.121.155"
        },
        "192.168.0.188": {
         "broadcast": "192.168.0.255",
         "mac": "64:4b:f0:18:9a:21",
         "netmask": "255.255.255.0"
        }
       },
       "by-ipv6": {},
       "by-mac": {
        "64:4b:f0:18:9a:21": {
         "ipv4": [
          {
           "addr": "192.168.0.188",
           "broadcast": "192.168.0.255",
           "netmask": "255.255.255.0"
          }
         ],
         "ipv6": []
        },
        "8a:9f:e4:1b:3c:ac": {
         "ipv6": []
        },
        "8a:9f:e4:1b:3c:ad": {
         "ipv6": []
        },
        "8a:9f:e4:1b:3c:ae": {
         "ipv6": []
        }
       }
      }
     },
     "wait-on-network": {
      "ipv4": true,
      "ipv6": false
     }
    }

    Everything worked as expected!

I also added a unit test, tests/unittests/sources/test_vmware.py::TestDataSourceVMware::test_wait_on_network:

$ make clean_pyc && \
  PYTHONPATH="$(pwd)" \
  python3 -m pytest -v tests/unittests/sources/test_vmware.py
=================================================================================================================================== test session starts ===================================================================================================================================
platform darwin -- Python 3.8.9, pytest-7.1.2, pluggy-1.0.0 -- /Library/Developer/CommandLineTools/usr/bin/python3
cachedir: .pytest_cache
rootdir: /Users/akutz/Projects/cloud-init, configfile: tox.ini
plugins: mock-3.8.2, cov-3.0.0
collected 26 items                                                                                                                                                                                                                                                                        

tests/unittests/sources/test_vmware.py::TestDataSourceVMware::test_get_host_info_dual PASSED                                                                                                                                                                                        [  3%]
tests/unittests/sources/test_vmware.py::TestDataSourceVMware::test_get_host_info_ipv4 PASSED                                                                                                                                                                                        [  7%]
tests/unittests/sources/test_vmware.py::TestDataSourceVMware::test_get_host_info_ipv6 PASSED                                                                                                                                                                                        [ 11%]
tests/unittests/sources/test_vmware.py::TestDataSourceVMware::test_no_data_access_method PASSED                                                                                                                                                                                     [ 15%]
tests/unittests/sources/test_vmware.py::TestDataSourceVMware::test_wait_on_network PASSED                                                                                                                                                                                           [ 19%]
tests/unittests/sources/test_vmware.py::TestDataSourceVMwareEnvVars::test_get_data_metadata_b64 PASSED                                                                                                                                                                              [ 23%]
tests/unittests/sources/test_vmware.py::TestDataSourceVMwareEnvVars::test_get_data_metadata_base64 PASSED                                                                                                                                                                           [ 26%]
tests/unittests/sources/test_vmware.py::TestDataSourceVMwareEnvVars::test_get_data_metadata_gz_b64 PASSED                                                                                                                                                                           [ 30%]
tests/unittests/sources/test_vmware.py::TestDataSourceVMwareEnvVars::test_get_data_metadata_gzip_base64 PASSED                                                                                                                                                                      [ 34%]
tests/unittests/sources/test_vmware.py::TestDataSourceVMwareEnvVars::test_get_data_metadata_only PASSED                                                                                                                                                                             [ 38%]
tests/unittests/sources/test_vmware.py::TestDataSourceVMwareEnvVars::test_get_data_userdata_only PASSED                                                                                                                                                                             [ 42%]
tests/unittests/sources/test_vmware.py::TestDataSourceVMwareEnvVars::test_get_data_vendordata_only PASSED                                                                                                                                                                           [ 46%]
tests/unittests/sources/test_vmware.py::TestDataSourceVMwareEnvVars::test_get_subplatform PASSED                                                                                                                                                                                    [ 50%]
tests/unittests/sources/test_vmware.py::TestDataSourceVMwareEnvVars::test_metadata_multiple_ssh_keys PASSED                                                                                                                                                                         [ 53%]
tests/unittests/sources/test_vmware.py::TestDataSourceVMwareEnvVars::test_metadata_single_ssh_key PASSED                                                                                                                                                                            [ 57%]
tests/unittests/sources/test_vmware.py::TestDataSourceVMwareGuestInfo::test_ds_valid_on_vmware_platform PASSED                                                                                                                                                                      [ 61%]
tests/unittests/sources/test_vmware.py::TestDataSourceVMwareGuestInfo::test_get_data_metadata_b64 PASSED                                                                                                                                                                            [ 65%]
tests/unittests/sources/test_vmware.py::TestDataSourceVMwareGuestInfo::test_get_data_metadata_base64 PASSED                                                                                                                                                                         [ 69%]
tests/unittests/sources/test_vmware.py::TestDataSourceVMwareGuestInfo::test_get_data_metadata_gz_b64 PASSED                                                                                                                                                                         [ 73%]
tests/unittests/sources/test_vmware.py::TestDataSourceVMwareGuestInfo::test_get_data_metadata_gzip_base64 PASSED                                                                                                                                                                    [ 76%]
tests/unittests/sources/test_vmware.py::TestDataSourceVMwareGuestInfo::test_get_data_userdata_only PASSED                                                                                                                                                                           [ 80%]
tests/unittests/sources/test_vmware.py::TestDataSourceVMwareGuestInfo::test_get_data_vendordata_only PASSED                                                                                                                                                                         [ 84%]
tests/unittests/sources/test_vmware.py::TestDataSourceVMwareGuestInfo::test_get_subplatform PASSED                                                                                                                                                                                  [ 88%]
tests/unittests/sources/test_vmware.py::TestDataSourceVMwareGuestInfo::test_metadata_multiple_ssh_keys PASSED                                                                                                                                                                       [ 92%]
tests/unittests/sources/test_vmware.py::TestDataSourceVMwareGuestInfo::test_metadata_single_ssh_key PASSED                                                                                                                                                                          [ 96%]
tests/unittests/sources/test_vmware.py::TestDataSourceVMwareGuestInfo_InvalidPlatform::test_ds_invalid_on_non_vmware_platform PASSED                                                                                                                                                [100%]

Checklist:

  • My code follows the process laid out in the documentation
  • I have updated or added any unit tests accordingly
  • I have updated or added any documentation accordingly

@akutz akutz force-pushed the fix/lp-1987005 branch 2 times, most recently from da38cb2 to 9c916ba Compare August 19, 2022 13:56

@blackboxsw blackboxsw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Ideally we'd want a unittest exercising the logic that lead to exposing this missed variable initialization failure,

@akutz

akutz commented Aug 22, 2022

Copy link
Copy Markdown
Contributor Author

LGTM. Ideally we'd want a unittest exercising the logic that lead to exposing this missed variable initialization failure,

Np. I thought about it, but given that it would be proving a panic no longer exists, I was not sure if it was worth it beyond the manual demonstration. Honestly, tests to validate some error no longer occurs are always something that strike me as odd, and I'm never sure how to best represent/surface the absence of something.

This patch fixes an issue in the DataSourceVMware code where the
variable ipv6_ready was used in a logging statement before it was
initialized. Now the variable is initialized, avoiding the panic.

Fixes https://bugs.launchpad.net/cloud-init/+bug/1987005
@akutz

akutz commented Aug 22, 2022

Copy link
Copy Markdown
Contributor Author

Okay @blackboxsw, the unit test has been added :)

@blackboxsw blackboxsw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a suggestion for some more unittest coverage.

diff --git a/tests/unittests/sources/test_vmware.py b/tests/unittests/sources/test_vmware.py
index 37a1f259d..57dfceff2 100644
--- a/tests/unittests/sources/test_vmware.py
+++ b/tests/unittests/sources/test_vmware.py
@@ -57,6 +57,8 @@ runcmd:
 - echo "Hello, world."
 """
 
+M_PATH = "cloudinit.sources.DataSourceVMware."
+
 
 @pytest.fixture(autouse=True)
 def common_patches():
@@ -67,14 +69,8 @@ def common_patches():
             is_container=mock.Mock(return_value=False),
             is_FreeBSD=mock.Mock(return_value=False),
         ),
-        mock.patch(
-            "cloudinit.sources.DataSourceVMware.netifaces.interfaces",
-            return_value=[],
-        ),
-        mock.patch(
-            "cloudinit.sources.DataSourceVMware.getfqdn",
-            return_value="host.cloudinit.test",
-        ),
+        mock.patch(M_PATH + "netifaces.interfaces", return_value=[]),
+        mock.patch(M_PATH + "getfqdn", return_value="host.cloudinit.test"),
     ]
     with ExitStack() as stack:
         for some_mock in mocks:
@@ -97,7 +93,7 @@ class TestDataSourceVMware(CiTestCase):
         ret = ds.get_data()
         self.assertFalse(ret)
 
-    @mock.patch("cloudinit.sources.DataSourceVMware.get_default_ip_addrs")
+    @mock.patch(M_PATH + "get_default_ip_addrs")
     def test_get_host_info_ipv4(self, m_fn_ipaddr):
         m_fn_ipaddr.return_value = ("10.10.10.1", None)
         host_info = DataSourceVMware.get_host_info()
@@ -110,7 +106,7 @@ class TestDataSourceVMware(CiTestCase):
         self.assertTrue(host_info[DataSourceVMware.LOCAL_IPV4] == "10.10.10.1")
         self.assertFalse(host_info.get(DataSourceVMware.LOCAL_IPV6))
 
-    @mock.patch("cloudinit.sources.DataSourceVMware.get_default_ip_addrs")
+    @mock.patch(M_PATH + "get_default_ip_addrs")
     def test_get_host_info_ipv6(self, m_fn_ipaddr):
         m_fn_ipaddr.return_value = (None, "2001:db8::::::8888")
         host_info = DataSourceVMware.get_host_info()
@@ -125,7 +121,7 @@ class TestDataSourceVMware(CiTestCase):
         )
         self.assertFalse(host_info.get(DataSourceVMware.LOCAL_IPV4))
 
-    @mock.patch("cloudinit.sources.DataSourceVMware.get_default_ip_addrs")
+    @mock.patch(M_PATH + "get_default_ip_addrs")
     def test_get_host_info_dual(self, m_fn_ipaddr):
         m_fn_ipaddr.return_value = ("10.10.10.1", "2001:db8::::::8888")
         host_info = DataSourceVMware.get_host_info()
@@ -182,9 +178,7 @@ class TestDataSourceVMwareEnvVars(FilesystemMockingTestCase):
         ds = self.assert_get_data_ok(m_fn, m_fn_call_count)
         assert_metadata(self, ds, metadata)
 
-    @mock.patch(
-        "cloudinit.sources.DataSourceVMware.guestinfo_envvar_get_value"
-    )
+    @mock.patch(M_PATH + "guestinfo_envvar_get_value")
     def test_get_subplatform(self, m_fn):
         m_fn.side_effect = [VMW_METADATA_YAML, "", "", "", "", ""]
         ds = self.assert_get_data_ok(m_fn, m_fn_call_count=4)
@@ -197,46 +191,34 @@ class TestDataSourceVMwareEnvVars(FilesystemMockingTestCase):
             ),
         )
 
-    @mock.patch(
-        "cloudinit.sources.DataSourceVMware.guestinfo_envvar_get_value"
-    )
+    @mock.patch(M_PATH + "guestinfo_envvar_get_value")
     def test_get_data_metadata_only(self, m_fn):
         m_fn.side_effect = [VMW_METADATA_YAML, "", "", "", "", ""]
         self.assert_get_data_ok(m_fn, m_fn_call_count=4)
 
-    @mock.patch(
-        "cloudinit.sources.DataSourceVMware.guestinfo_envvar_get_value"
-    )
+    @mock.patch(M_PATH + "guestinfo_envvar_get_value")
     def test_get_data_userdata_only(self, m_fn):
         m_fn.side_effect = ["", VMW_USERDATA_YAML, "", ""]
         self.assert_get_data_ok(m_fn, m_fn_call_count=4)
 
-    @mock.patch(
-        "cloudinit.sources.DataSourceVMware.guestinfo_envvar_get_value"
-    )
+    @mock.patch(M_PATH + "guestinfo_envvar_get_value")
     def test_get_data_vendordata_only(self, m_fn):
         m_fn.side_effect = ["", "", VMW_VENDORDATA_YAML, ""]
         self.assert_get_data_ok(m_fn, m_fn_call_count=4)
 
-    @mock.patch(
-        "cloudinit.sources.DataSourceVMware.guestinfo_envvar_get_value"
-    )
+    @mock.patch(M_PATH + "guestinfo_envvar_get_value")
     def test_get_data_metadata_base64(self, m_fn):
         data = base64.b64encode(VMW_METADATA_YAML.encode("utf-8"))
         m_fn.side_effect = [data, "base64", "", ""]
         self.assert_get_data_ok(m_fn, m_fn_call_count=4)
 
-    @mock.patch(
-        "cloudinit.sources.DataSourceVMware.guestinfo_envvar_get_value"
-    )
+    @mock.patch(M_PATH + "guestinfo_envvar_get_value")
     def test_get_data_metadata_b64(self, m_fn):
         data = base64.b64encode(VMW_METADATA_YAML.encode("utf-8"))
         m_fn.side_effect = [data, "b64", "", ""]
         self.assert_get_data_ok(m_fn, m_fn_call_count=4)
 
-    @mock.patch(
-        "cloudinit.sources.DataSourceVMware.guestinfo_envvar_get_value"
-    )
+    @mock.patch(M_PATH + "guestinfo_envvar_get_value")
     def test_get_data_metadata_gzip_base64(self, m_fn):
         data = VMW_METADATA_YAML.encode("utf-8")
         data = gzip.compress(data)
@@ -244,9 +226,7 @@ class TestDataSourceVMwareEnvVars(FilesystemMockingTestCase):
         m_fn.side_effect = [data, "gzip+base64", "", ""]
         self.assert_get_data_ok(m_fn, m_fn_call_count=4)
 
-    @mock.patch(
-        "cloudinit.sources.DataSourceVMware.guestinfo_envvar_get_value"
-    )
+    @mock.patch(M_PATH + "guestinfo_envvar_get_value")
     def test_get_data_metadata_gz_b64(self, m_fn):
         data = VMW_METADATA_YAML.encode("utf-8")
         data = gzip.compress(data)
@@ -254,9 +234,7 @@ class TestDataSourceVMwareEnvVars(FilesystemMockingTestCase):
         m_fn.side_effect = [data, "gz+b64", "", ""]
         self.assert_get_data_ok(m_fn, m_fn_call_count=4)
 
-    @mock.patch(
-        "cloudinit.sources.DataSourceVMware.guestinfo_envvar_get_value"
-    )
+    @mock.patch(M_PATH + "guestinfo_envvar_get_value")
     def test_metadata_single_ssh_key(self, m_fn):
         metadata = DataSourceVMware.load_json_or_yaml(VMW_METADATA_YAML)
         metadata["public_keys"] = VMW_SINGLE_KEY
@@ -264,9 +242,7 @@ class TestDataSourceVMwareEnvVars(FilesystemMockingTestCase):
         m_fn.side_effect = [metadata_yaml, "", "", ""]
         self.assert_metadata(metadata, m_fn, m_fn_call_count=4)
 
-    @mock.patch(
-        "cloudinit.sources.DataSourceVMware.guestinfo_envvar_get_value"
-    )
+    @mock.patch(M_PATH + "guestinfo_envvar_get_value")
     def test_metadata_multiple_ssh_keys(self, m_fn):
         metadata = DataSourceVMware.load_json_or_yaml(VMW_METADATA_YAML)
         metadata["public_keys"] = VMW_MULTIPLE_KEYS
@@ -316,7 +292,7 @@ class TestDataSourceVMwareGuestInfo(FilesystemMockingTestCase):
         system_type = dmi.read_dmi_data("system-product-name")
         self.assertEqual(system_type, PRODUCT_NAME)
 
-    @mock.patch("cloudinit.sources.DataSourceVMware.guestinfo_get_value")
+    @mock.patch(M_PATH + "guestinfo_get_value")
     def test_get_subplatform(self, m_fn):
         m_fn.side_effect = [VMW_METADATA_YAML, "", "", "", "", ""]
         ds = self.assert_get_data_ok(m_fn, m_fn_call_count=4)
@@ -329,17 +305,17 @@ class TestDataSourceVMwareGuestInfo(FilesystemMockingTestCase):
             ),
         )
 
-    @mock.patch("cloudinit.sources.DataSourceVMware.guestinfo_get_value")
+    @mock.patch(M_PATH + "guestinfo_get_value")
     def test_get_data_userdata_only(self, m_fn):
         m_fn.side_effect = ["", VMW_USERDATA_YAML, "", ""]
         self.assert_get_data_ok(m_fn, m_fn_call_count=4)
 
-    @mock.patch("cloudinit.sources.DataSourceVMware.guestinfo_get_value")
+    @mock.patch(M_PATH + "guestinfo_get_value")
     def test_get_data_vendordata_only(self, m_fn):
         m_fn.side_effect = ["", "", VMW_VENDORDATA_YAML, ""]
         self.assert_get_data_ok(m_fn, m_fn_call_count=4)
 
-    @mock.patch("cloudinit.sources.DataSourceVMware.guestinfo_get_value")
+    @mock.patch(M_PATH + "guestinfo_get_value")
     def test_metadata_single_ssh_key(self, m_fn):
         metadata = DataSourceVMware.load_json_or_yaml(VMW_METADATA_YAML)
         metadata["public_keys"] = VMW_SINGLE_KEY
@@ -347,7 +323,7 @@ class TestDataSourceVMwareGuestInfo(FilesystemMockingTestCase):
         m_fn.side_effect = [metadata_yaml, "", "", ""]
         self.assert_metadata(metadata, m_fn, m_fn_call_count=4)
 
-    @mock.patch("cloudinit.sources.DataSourceVMware.guestinfo_get_value")
+    @mock.patch(M_PATH + "guestinfo_get_value")
     def test_metadata_multiple_ssh_keys(self, m_fn):
         metadata = DataSourceVMware.load_json_or_yaml(VMW_METADATA_YAML)
         metadata["public_keys"] = VMW_MULTIPLE_KEYS
@@ -355,19 +331,19 @@ class TestDataSourceVMwareGuestInfo(FilesystemMockingTestCase):
         m_fn.side_effect = [metadata_yaml, "", "", ""]
         self.assert_metadata(metadata, m_fn, m_fn_call_count=4)
 
-    @mock.patch("cloudinit.sources.DataSourceVMware.guestinfo_get_value")
+    @mock.patch(M_PATH + "guestinfo_get_value")
     def test_get_data_metadata_base64(self, m_fn):
         data = base64.b64encode(VMW_METADATA_YAML.encode("utf-8"))
         m_fn.side_effect = [data, "base64", "", ""]
         self.assert_get_data_ok(m_fn, m_fn_call_count=4)
 
-    @mock.patch("cloudinit.sources.DataSourceVMware.guestinfo_get_value")
+    @mock.patch(M_PATH + "guestinfo_get_value")
     def test_get_data_metadata_b64(self, m_fn):
         data = base64.b64encode(VMW_METADATA_YAML.encode("utf-8"))
         m_fn.side_effect = [data, "b64", "", ""]
         self.assert_get_data_ok(m_fn, m_fn_call_count=4)
 
-    @mock.patch("cloudinit.sources.DataSourceVMware.guestinfo_get_value")
+    @mock.patch(M_PATH + "guestinfo_get_value")
     def test_get_data_metadata_gzip_base64(self, m_fn):
         data = VMW_METADATA_YAML.encode("utf-8")
         data = gzip.compress(data)
@@ -375,7 +351,7 @@ class TestDataSourceVMwareGuestInfo(FilesystemMockingTestCase):
         m_fn.side_effect = [data, "gzip+base64", "", ""]
         self.assert_get_data_ok(m_fn, m_fn_call_count=4)
 
-    @mock.patch("cloudinit.sources.DataSourceVMware.guestinfo_get_value")
+    @mock.patch(M_PATH + "guestinfo_get_value")
     def test_get_data_metadata_gz_b64(self, m_fn):
         data = VMW_METADATA_YAML.encode("utf-8")
         data = gzip.compress(data)
@@ -404,7 +380,7 @@ class TestDataSourceVMwareGuestInfo_InvalidPlatform(FilesystemMockingTestCase):
         )
         self.assertTrue(self.reRoot(rootd))
 
-    @mock.patch("cloudinit.sources.DataSourceVMware.guestinfo_get_value")
+    @mock.patch(M_PATH + "guestinfo_get_value")
     def test_ds_invalid_on_non_vmware_platform(self, m_fn):
         system_type = dmi.read_dmi_data("system-product-name")
         self.assertEqual(system_type, None)
@@ -438,4 +414,105 @@ def get_ds(temp_dir):
     return ds
 
 
+HOST_INFO_NO_IPS = {
+    "network": {"interfaces": {"by-mac": {}, "by-ipv4": {}, "by-ipv6": {}}},
+    "hostname": "host.cloudinit.test",
+    "local-hostname": "host.cloudinit.test",
+    "local_hostname": "host.cloudinit.test",
+    "local-ipv6": "2001:db8::::::8888",
+}
+
+HOST_INFO_IPV4 = {
+    "network": {
+        "interfaces": {
+            "by-mac": {},
+            "by-ipv4": {
+                "192.168.1.201": {
+                    "netmask": "255.255.255.0",
+                    "broadcast": "192.168.1.255",
+                    "mac": "50:7b:9d:2c:af:91",
+                }
+            },
+            "by-ipv6": {},
+        }
+    },
+    "hostname": "host.cloudinit.test",
+    "local-hostname": "host.cloudinit.test",
+    "local_hostname": "host.cloudinit.test",
+    "local-ipv6": "2001:db8::::::8888",
+}
+
+HOST_INFO_IPV6 = {
+    "network": {
+        "interfaces": {
+            "by-mac": {},
+            "by-ipv4": {},
+            "by-ipv6": {
+                "fd42:fc73:a2ea:bdfb::1": {
+                    "netmask": "ffff:ffff:ffff:ffff::",
+                    "mac": "00:16:3e:62:b5:68",
+                },
+            },
+        }
+    },
+    "hostname": "host.cloudinit.test",
+    "local-hostname": "host.cloudinit.test",
+    "local_hostname": "host.cloudinit.test",
+    "local-ipv6": "2001:db8::::::8888",
+}
+
+
+@mock.patch(M_PATH + "time.sleep")
+@mock.patch(M_PATH + "get_host_info")
+class TestWaitOnNetwork:
+    @pytest.mark.parametrize(
+        "metadata,host_info_values,expected",
+        (
+            pytest.param(  # No wait-on-network, no interfaces up, no retry
+                {},
+                (HOST_INFO_NO_IPS,),
+                HOST_INFO_NO_IPS,
+                id="no_wait_on_network_returns_host_info_no_retries",
+            ),
+            pytest.param(  # wait-on-network, no ipv4/6 no retry
+                {"wait-on-network": {}},
+                (HOST_INFO_NO_IPS,),
+                HOST_INFO_NO_IPS,
+                id="wait_on_network_no_ipv46_returns_host_info_no_retries",
+            ),
+            pytest.param(  # wait-on-network, ipv4/6 don't wait
+                {"wait-on-network": {"ipv4": False, "ipv6": False}},
+                (HOST_INFO_NO_IPS,),
+                HOST_INFO_NO_IPS,
+                id="wait_on_network_ipv46_false_returns_host_info_no_retries",
+            ),
+            pytest.param(  # wait-on-network, ipv4, retry
+                {"wait-on-network": {"ipv4": True, "ipv6": False}},
+                (HOST_INFO_NO_IPS, HOST_INFO_IPV4),
+                HOST_INFO_IPV4,
+                id="no_wait_on_network_ipv4_true_returns_host_info_1_retry",
+            ),
+            pytest.param(  # wait-on-network, ipv6, retry
+                {"wait-on-network": {"ipv4": False, "ipv6": True}},
+                (HOST_INFO_NO_IPS, HOST_INFO_IPV4, HOST_INFO_IPV6),
+                HOST_INFO_IPV6,
+                id="no_wait_on_network_ipv6_true_returns_host_info_2_retries",
+            ),
+        ),
+    )
+    def test_wait_on_network_loops_on_ipv4_ipv6(
+        self, get_host_info, sleep, metadata, host_info_values, expected
+    ):
+        if len(host_info_values) == 1:
+            get_host_info.return_value = host_info_values[0]
+            sleep_count = 0
+        else:
+            get_host_info.side_effect = host_info_values
+            sleep_count = len(host_info_values) - 1
+        assert expected == DataSourceVMware.wait_on_network(metadata)
+        # Only loop and call get_host_info until expected wait success
+        assert get_host_info.call_count == len(host_info_values)
+        assert sleep.call_count == sleep_count
+
+
 # vi: ts=4 expandtab

@blackboxsw

blackboxsw commented Aug 22, 2022

Copy link
Copy Markdown
Collaborator

Okay @blackboxsw, the unit test has been added :)

@akutz you beat me too it. I felt 'dirty' suggesting something like unit test coverage for this. The only concern I had was that we had no current unittest coverage at all VMware.wait_on_network which would have shown us this type of error earlier

@blackboxsw blackboxsw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

your changes are great and cover the use case I was looking for. At least that we properly get through wait_on_network calls without falling into the panic/exception path. We can build on this in the future as new logic is added and at least get coverage of that function to assert we didn't botch something that'll only show up in runtime.

@blackboxsw blackboxsw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works for me Thanks @akutz for fast turnaround here on unittests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants