Repository: celery/kombu

Issue:
New 4.3.0 release breaks Redis Sentinel connection
The new 4.3.0 breaks the Redis Sentinel connection, triggering an error like when queueing a task:
```
redis.sentinel.MasterNotFoundError: No master found for 'celery-master'
```
While starting a celery worker gives this error
```
 ERROR - celery.worker.consumer.consumer - consumer: Cannot connect to sentinel://localhost:26379/0: No master found for 'celery-master'.
```
`kombu==4.2.2-post1` did not have this issue.

Redis library = 3.0.1


Discussion:
can you help to bisect the commit causing this?
Looks like it happened in this commit: https://github.com/celery/kombu/commit/a59b44e9fb8d00758dd6a3c560e2f158e672580d

When running though a debugger, `self.connection.client.alt` is empty on this code line https://github.com/celery/kombu/blob/b4be5cf2e7e5ca538246e254bc6ebac5d6c5f477/kombu/transport/redis.py#L1104

This causes the next part to connect to an empty list of sentinels:
https://github.com/celery/kombu/blob/b4be5cf2e7e5ca538246e254bc6ebac5d6c5f477/kombu/transport/redis.py#L1107-L1111
Celery (Django) setting:

```python
CELERY_BROKER_URL = "sentinel://localhost:26379/0"
CELERY_BROKER_TRANSPORT_OPTIONS = {
    'master_name': 'celery-master',
}
```
@badcure could you check?
I'm thinking that maybe something like this ought to fix it. But it's just guesses.

```diff
-        connection_list = []
+        connection_list = [(connparams['host'], connparams['port'])]
         for url in self.connection.client.alt:
             url = _parse_url(url)
             if url.scheme == 'sentinel':
                 connection_list.append([url.hostname, str(url.port)])

```

Or:

```diff
         connection_list = []
         for url in self.connection.client.alt:
             url = _parse_url(url)
             if url.scheme == 'sentinel':
                 connection_list.append([url.hostname, str(url.port)])
+        if not connection_list:
+            connection_list = [(connparams['host'], connparams['port'])]
```

Resolving patch:
diff --git a/kombu/transport/redis.py b/kombu/transport/redis.py
--- a/kombu/transport/redis.py
+++ b/kombu/transport/redis.py
@@ -1100,13 +1100,19 @@ def _sentinel_managed_pool(self, asynchronous=False):
 
         additional_params.pop('host', None)
         additional_params.pop('port', None)
-        connection_list = []
+
+        sentinels = []
         for url in self.connection.client.alt:
             url = _parse_url(url)
             if url.scheme == 'sentinel':
-                connection_list.append([url.hostname, str(url.port)])
+                sentinels.append((url.hostname, url.port))
+
+        # Fallback for when only one sentinel is provided.
+        if not sentinels:
+            sentinels.append((connparams['host'], connparams['port']))
+
         sentinel_inst = sentinel.Sentinel(
-            connection_list,
+            sentinels,
             min_other_sentinels=getattr(self, 'min_other_sentinels', 0),
             sentinel_kwargs=getattr(self, 'sentinel_kwargs', None),
             **additional_params)


Test patch:
diff --git a/t/unit/transport/test_redis.py b/t/unit/transport/test_redis.py
--- a/t/unit/transport/test_redis.py
+++ b/t/unit/transport/test_redis.py
@@ -1356,10 +1356,10 @@ def test_getting_master_from_sentinel(self):
             connection.channel()
             patched.assert_called_once_with(
                 [
-                    [u'localhost', u'65532'],
-                    [u'localhost', u'65533'],
-                    [u'localhost', u'65534'],
-                    [u'localhost', u'65535'],
+                    (u'localhost', 65532),
+                    (u'localhost', 65533),
+                    (u'localhost', 65534),
+                    (u'localhost', 65535),
                 ],
                 connection_class=mock.ANY, db=0, max_connections=10,
                 min_other_sentinels=0, password=None, sentinel_kwargs=None,
@@ -1371,6 +1371,28 @@ def test_getting_master_from_sentinel(self):
             master_for.assert_called_with('not_important', ANY)
             master_for().connection_pool.get_connection.assert_called()
 
+    def test_getting_master_from_sentinel_single_node(self):
+        with patch('redis.sentinel.Sentinel') as patched:
+            connection = Connection(
+                'sentinel://localhost:65532/',
+                transport_options={
+                    'master_name': 'not_important',
+                },
+            )
+
+            connection.channel()
+            patched.assert_called_once_with(
+                [(u'localhost', 65532)],
+                connection_class=mock.ANY, db=0, max_connections=10,
+                min_other_sentinels=0, password=None, sentinel_kwargs=None,
+                socket_connect_timeout=None, socket_keepalive=None,
+                socket_keepalive_options=None, socket_timeout=None)
+
+            master_for = patched.return_value.master_for
+            master_for.assert_called()
+            master_for.assert_called_with('not_important', ANY)
+            master_for().connection_pool.get_connection.assert_called()
+
     def test_can_create_connection(self):
         from redis.exceptions import ConnectionError