Repository: saltstack/salt
Pull request: Fix localhost detection in glusterfs.peers
### What does this PR do?

This fixes the detection of a localhost peer in cases where the host IP lookup returns a loopback address.

### What issues does this PR fix or reference?
Fixes: #62273

### Merge requirements satisfied?
**[NOTICE] Bug fixes or features added to Salt require tests.**
<!-- Please review the [test documentation](https://docs.saltproject.io/en/master/topics/tutorials/writing_tests.html) for details on how to implement tests into Salt's test suite. -->
- [x] Docs
- [X] Changelog - https://docs.saltproject.io/en/master/topics/development/changelog.html
- [x] Tests written/updated

### Commits signed with GPG?
No

Please review [Salt's Contributing Guide](https://docs.saltproject.io/en/master/topics/development/contributing.html) for best practices.

See GitHub's [page on GPG signing](https://help.github.com/articles/signing-commits-using-gpg/) for more information about signing commits with GPG.


Problem statement:
[BUG] Glusterfs `peered` doesn't understand loopback IP as localhost
**Description**
In some situations, `glusterfs.peered` fails to detect a peer as being localhost.

More concretely, in some cases `salt.utils.network.host_to_ips(name)` may return an address like `127.0.1.1`. This happens for instance if the host name is associated to a loopback address in `/etc/hosts`.

This loopback address obviously never matches the addresses returned by `salt.utils.network.ip_addrs()` or `salt.utils.network.ip_addrs6()`, hence localhost detection fails.

**Setup**
```
# salt 'minion1' cmd.run 'cat /etc/hosts'
minion1:
    # Static table lookup for hostnames.
    # See hosts(5) for details.
    127.0.1.1 minion1 minion1
```

```
$ cat glusterfs.sls
Add gluster peers:
  glusterfs.peered:
    - names:
      - minion1
      - minion2
```

Moreover, the minions must have glusterd installed and running.
  
**Steps to Reproduce the behavior**

```
# salt 'minion1' state.apply glusterfs
(...)
          ID: Add gluster peers
    Function: glusterfs.peered
        Name: minion1
      Result: False
     Comment: Host minion1 was successfully peered but did not appear in the list of peers
     Started: (...)
    Duration: (...)
     Changes:
----------
          ID: Add gluster peers
    Function: glusterfs.peered
        Name: minion2
      Result: True
     Comment: Host minion2 already peered
     Started: (...)
    Duration: (...)
     Changes:
(...)
```

**Expected behavior**

Peering minion1 shouldn't fail, and instead the comment should have been "Peering with localhost is not needed".

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Versions Report**
<details><summary>salt --versions-report</summary>
(Provided by running salt --versions-report. Please also mention any differences in master/minion versions.) 

```yaml
Salt Version:
          Salt: 3004.2

Dependency Versions:
          cffi: Not Installed
      cherrypy: Not Installed
      dateutil: Not Installed
     docker-py: Not Installed
         gitdb: Not Installed
     gitpython: Not Installed
        Jinja2: 3.1.2
       libgit2: Not Installed
      M2Crypto: 0.38.0
          Mako: Not Installed
       msgpack: 1.0.3
  msgpack-pure: Not Installed
  mysql-python: Not Installed
     pycparser: Not Installed
      pycrypto: Not Installed
  pycryptodome: 3.12.0
        pygit2: Not Installed
        Python: 3.10.5 (main, Jun  6 2022, 18:49:26) [GCC 12.1.0]
  python-gnupg: Not Installed
        PyYAML: 6.0
         PyZMQ: 23.2.0
         smmap: Not Installed
       timelib: Not Installed
       Tornado: 4.5.3
           ZMQ: 4.3.4

System Versions:
          dist: arch
        locale: utf-8
       machine: x86_64
       release: 5.18.7-arch1-1
        system: Linux
       version: Arch Linux
```
</details>

Hints from issue discussion:
Hi there! Welcome to the Salt Community! Thank you for making your first contribution. We have a lengthy process for issues and PRs. Someone from the Core Team will follow up as soon as possible. In the meantime, here’s some information that may help as you continue your Salt journey.
Please be sure to review our [Code of Conduct](https://github.com/saltstack/salt/blob/master/CODE_OF_CONDUCT.md). Also, check out some of our community resources including:

  - [Community Wiki](https://github.com/saltstack/community/wiki)
  - [Salt’s Contributor Guide](https://docs.saltproject.io/en/master/topics/development/contributing.html)
  - [Join our Community Slack](https://join.slack.com/t/saltstackcommunity/shared_invite/zt-3av8jjyf-oBQ2M0vhXOhJpNpRkPWBvg)
  - [IRC on LiberaChat](https://web.libera.chat/#salt)
  - [Salt Project YouTube channel](https://www.youtube.com/channel/UCpveTIucFx9ljGelW63-BWg)
  - [Salt Project Twitch channel](https://www.twitch.tv/saltprojectoss)

There are lots of ways to get involved in our community. Every month, there are around a dozen opportunities to meet with other contributors and the Salt Core team and collaborate in real time. The best way to keep track is by subscribing to the Salt Community Events Calendar.
If you have additional questions, email us at saltproject@vmware.com. We’re glad you’ve joined our community and look forward to doing awesome things with you!

Commit message: Merge branch 'master' into fix_glusterfs_peer_loopback

Patch to review:
diff --git a/changelog/62273.fixed b/changelog/62273.fixed
new file mode 100644
index 000000000000..1f4caad60855
--- /dev/null
+++ b/changelog/62273.fixed
@@ -0,0 +1 @@
+Fix localhost detection in glusterfs.peers
diff --git a/salt/states/glusterfs.py b/salt/states/glusterfs.py
index be7374b03408..baf625cf6f67 100644
--- a/salt/states/glusterfs.py
+++ b/salt/states/glusterfs.py
@@ -73,7 +73,8 @@ def peered(name):
         # it from the user.
         this_ips = set(salt.utils.network.ip_addrs())
         this_ips.update(salt.utils.network.ip_addrs6())
-        if this_ips.intersection(name_ips):
+        if (any(salt.utils.network.is_loopback(addr) for addr in name_ips)
+                or this_ips.intersection(name_ips)):
             ret["result"] = True
             ret["comment"] = "Peering with localhost is not needed"
             return ret
diff --git a/salt/utils/network.py b/salt/utils/network.py
index b7cf11230997..6cc73bad44fc 100644
--- a/salt/utils/network.py
+++ b/salt/utils/network.py
@@ -324,6 +324,16 @@ def is_ipv6(ip_addr):
         return False
 
 
+def is_loopback(ip_addr):
+    """
+    Returns a bool telling if the value passed to it is a loopback address
+    """
+    try:
+        return ipaddress.ip_address(ip_addr).is_loopback
+    except ValueError:
+        return False
+
+
 def is_subnet(cidr):
     """
     Returns a bool telling if the passed string is an IPv4 or IPv6 subnet
diff --git a/tests/unit/states/test_glusterfs.py b/tests/unit/states/test_glusterfs.py
index 24ff099d80dc..5204d3607aa2 100644
--- a/tests/unit/states/test_glusterfs.py
+++ b/tests/unit/states/test_glusterfs.py
@@ -46,6 +46,11 @@ def test_peered(self):
                 ret.update({"comment": comt})
                 self.assertDictEqual(glusterfs.peered(name), ret)
 
+                mock_host_ips.return_value = ["127.0.1.1"]
+                comt = "Peering with localhost is not needed"
+                ret.update({"comment": comt})
+                self.assertDictEqual(glusterfs.peered(name), ret)
+
                 mock_host_ips.return_value = ["2001:db8::1"]
                 self.assertDictEqual(glusterfs.peered(name), ret)
 
diff --git a/tests/unit/utils/test_network.py b/tests/unit/utils/test_network.py
index 7e0ada71e18d..3270fcbc2ab0 100644
--- a/tests/unit/utils/test_network.py
+++ b/tests/unit/utils/test_network.py
@@ -234,6 +234,15 @@ def test_ipv6(self):
         self.assertTrue(network.ipv6("2001:0db8:85a3::8a2e:0370:7334"))
         self.assertTrue(network.ipv6("2001:67c:2e8::/48"))
 
+    def test_is_loopback(self):
+        self.assertTrue(network.is_loopback("127.0.1.1"))
+        self.assertTrue(network.is_loopback("::1"))
+        self.assertFalse(network.is_loopback("10.0.1.2"))
+        self.assertFalse(network.is_loopback("2001:db8:0:1:1:1:1:1"))
+        # Check 16-char-long unicode string
+        # https://github.com/saltstack/salt/issues/51258
+        self.assertFalse(network.is_ipv6("sixteen-char-str"))
+
     def test_parse_host_port(self):
         _ip = ipaddress.ip_address
         good_host_ports = {


Code review comments:
diff_hunk: @@ -73,7 +73,8 @@ def peered(name):
         # it from the user.
         this_ips = set(salt.utils.network.ip_addrs())
         this_ips.update(salt.utils.network.ip_addrs6())
-        if this_ips.intersection(name_ips):
+        if (any(salt.utils.network.is_loopback(addr) for addr in name_ips)
+                or this_ips.intersection(name_ips)):
path: salt/states/glusterfs.py
text: @user1:
```suggestion
        if any(
            salt.utils.network.is_loopback(addr) for addr in name_ips
        ) or this_ips.intersection(name_ips):
```