Skip to content

Commit d9706dd

Browse files
committed
Add endpoint option
This implements ADR-119[1], which specifies the client connection options to update requests to the endpoints implemented as part of ADR-042[2]. The endpoint may be one of the following: * a routing policy name (such as main) * a nonprod routing policy name (such as nonprod:sandbox) * a FQDN such as foo.example.com The endpoint option is not valid with any of environment, restHost or realtimeHost, but we still intend to support the legacy options. If the client has been configured to use any of these legacy options, then they should continue to work in the same way, using the same primary and fallback hostnames. If the client has not been explicitly configured, then the hostnames will change to the new ably.net domain when the package is upgraded. [1] https://ably.atlassian.net/wiki/spaces/ENG/pages/3428810778/ADR-119+ClientOptions+for+new+DNS+structure [2] https://ably.atlassian.net/wiki/spaces/ENG/pages/1791754276/ADR-042+DNS+Restructure
1 parent 3b383e4 commit d9706dd

File tree

6 files changed

+82
-63
lines changed

6 files changed

+82
-63
lines changed

ably/transport/defaults.py

+31-17
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,8 @@
11
class Defaults:
22
protocol_version = "2"
3-
fallback_hosts = [
4-
"a.ably-realtime.com",
5-
"b.ably-realtime.com",
6-
"c.ably-realtime.com",
7-
"d.ably-realtime.com",
8-
"e.ably-realtime.com",
9-
]
10-
11-
rest_host = "rest.ably.io"
12-
realtime_host = "realtime.ably.io" # RTN2
3+
134
connectivity_check_url = "https://internet-up.ably-realtime.com/is-the-internet-up.txt"
14-
environment = 'production'
5+
endpoint = 'main'
156

167
port = 80
178
tls_port = 443
@@ -53,11 +44,34 @@ def get_scheme(options):
5344
return "http"
5445

5546
@staticmethod
56-
def get_environment_fallback_hosts(environment):
47+
def get_hostname(endpoint):
48+
if "." in endpoint or "::" in endpoint or "localhost" in endpoint:
49+
return endpoint
50+
51+
if endpoint.startswith("nonprod:"):
52+
return endpoint[len("nonprod:"):] + ".realtime.ably-nonprod.net"
53+
54+
if endpoint == "main":
55+
return "main.realtime.ably.net"
56+
57+
return endpoint + ".realtime.ably.net"
58+
59+
@staticmethod
60+
def get_fallback_hosts(endpoint="main"):
61+
if endpoint.startswith("nonprod:"):
62+
root = endpoint.replace("nonprod:", "")
63+
return [
64+
root + ".a.fallback.ably-realtime-nonprod.com",
65+
root + ".b.fallback.ably-realtime-nonprod.com",
66+
root + ".c.fallback.ably-realtime-nonprod.com",
67+
root + ".d.fallback.ably-realtime-nonprod.com",
68+
root + ".e.fallback.ably-realtime-nonprod.com",
69+
]
70+
5771
return [
58-
environment + "-a-fallback.ably-realtime.com",
59-
environment + "-b-fallback.ably-realtime.com",
60-
environment + "-c-fallback.ably-realtime.com",
61-
environment + "-d-fallback.ably-realtime.com",
62-
environment + "-e-fallback.ably-realtime.com",
72+
endpoint + ".a.fallback.ably-realtime.com",
73+
endpoint + ".b.fallback.ably-realtime.com",
74+
endpoint + ".c.fallback.ably-realtime.com",
75+
endpoint + ".d.fallback.ably-realtime.com",
76+
endpoint + ".e.fallback.ably-realtime.com",
6377
]

ably/types/options.py

+25-26
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@
99

1010
class Options(AuthOptions):
1111
def __init__(self, client_id=None, log_level=0, tls=True, rest_host=None, realtime_host=None, port=0,
12-
tls_port=0, use_binary_protocol=True, queue_messages=False, recover=False, environment=None,
13-
http_open_timeout=None, http_request_timeout=None, realtime_request_timeout=None,
14-
http_max_retry_count=None, http_max_retry_duration=None, fallback_hosts=None,
15-
fallback_retry_timeout=None, disconnected_retry_timeout=None, idempotent_rest_publishing=None,
16-
loop=None, auto_connect=True, suspended_retry_timeout=None, connectivity_check_url=None,
12+
tls_port=0, use_binary_protocol=True, queue_messages=False, recover=False, endpoint=None,
13+
environment=None, http_open_timeout=None, http_request_timeout=None,
14+
realtime_request_timeout=None, http_max_retry_count=None, http_max_retry_duration=None,
15+
fallback_hosts=None, fallback_retry_timeout=None, disconnected_retry_timeout=None,
16+
idempotent_rest_publishing=None, loop=None, auto_connect=True,
17+
suspended_retry_timeout=None, connectivity_check_url=None,
1718
channel_retry_timeout=Defaults.channel_retry_timeout, add_request_ids=False, **kwargs):
1819

1920
super().__init__(**kwargs)
@@ -42,12 +43,21 @@ def __init__(self, client_id=None, log_level=0, tls=True, rest_host=None, realti
4243
if environment is not None and realtime_host is not None:
4344
raise ValueError('specify realtime_host or environment, not both')
4445

46+
if environment is not None and endpoint is not None:
47+
raise ValueError('specify endpoint or environment, not both')
48+
4549
if idempotent_rest_publishing is None:
4650
from ably import api_version
4751
idempotent_rest_publishing = api_version >= '1.2'
4852

49-
if environment is None:
50-
environment = Defaults.environment
53+
if environment == "production":
54+
endpoint = Defaults.endpoint
55+
56+
if environment is not None and endpoint is None:
57+
endpoint = environment
58+
59+
if endpoint is None:
60+
endpoint = Defaults.endpoint
5161

5262
self.__client_id = client_id
5363
self.__log_level = log_level
@@ -59,7 +69,7 @@ def __init__(self, client_id=None, log_level=0, tls=True, rest_host=None, realti
5969
self.__use_binary_protocol = use_binary_protocol
6070
self.__queue_messages = queue_messages
6171
self.__recover = recover
62-
self.__environment = environment
72+
self.__endpoint = endpoint
6373
self.__http_open_timeout = http_open_timeout
6474
self.__http_request_timeout = http_request_timeout
6575
self.__realtime_request_timeout = realtime_request_timeout
@@ -163,8 +173,8 @@ def recover(self, value):
163173
self.__recover = value
164174

165175
@property
166-
def environment(self):
167-
return self.__environment
176+
def endpoint(self):
177+
return self.__endpoint
168178

169179
@property
170180
def http_open_timeout(self):
@@ -268,27 +278,19 @@ def __get_rest_hosts(self):
268278
# Defaults
269279
host = self.rest_host
270280
if host is None:
271-
host = Defaults.rest_host
272-
273-
environment = self.environment
281+
host = Defaults.get_hostname(self.endpoint)
274282

275283
http_max_retry_count = self.http_max_retry_count
276284
if http_max_retry_count is None:
277285
http_max_retry_count = Defaults.http_max_retry_count
278286

279-
# Prepend environment
280-
if environment != 'production':
281-
host = '%s-%s' % (environment, host)
282-
283287
# Fallback hosts
284288
fallback_hosts = self.fallback_hosts
285289
if fallback_hosts is None:
286-
if host == Defaults.rest_host:
287-
fallback_hosts = Defaults.fallback_hosts
288-
elif environment != 'production':
289-
fallback_hosts = Defaults.get_environment_fallback_hosts(environment)
290-
else:
290+
if self.rest_host:
291291
fallback_hosts = []
292+
else:
293+
fallback_hosts = Defaults.get_fallback_hosts(self.endpoint)
292294

293295
# Shuffle
294296
fallback_hosts = list(fallback_hosts)
@@ -304,11 +306,8 @@ def __get_realtime_hosts(self):
304306
if self.realtime_host is not None:
305307
host = self.realtime_host
306308
return [host]
307-
elif self.environment != "production":
308-
host = f'{self.environment}-{Defaults.realtime_host}'
309-
else:
310-
host = Defaults.realtime_host
311309

310+
host = Defaults.get_hostname(self.endpoint)
312311
return [host] + self.__fallback_hosts
313312

314313
def get_rest_hosts(self):

test/ably/rest/restinit_test.py

+15-9
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,12 @@ def test_rest_host_and_environment(self):
7676
# environment: production
7777
ably = AblyRest(token='foo', environment="production")
7878
host = ably.options.get_rest_host()
79-
assert "rest.ably.io" == host, "Unexpected host mismatch %s" % host
79+
assert "main.realtime.ably.net" == host, "Unexpected host mismatch %s" % host
8080

8181
# environment: other
82-
ably = AblyRest(token='foo', environment="sandbox")
82+
ably = AblyRest(token='foo', environment="nonprod:sandbox")
8383
host = ably.options.get_rest_host()
84-
assert "sandbox-rest.ably.io" == host, "Unexpected host mismatch %s" % host
84+
assert "sandbox.realtime.ably-nonprod.net" == host, "Unexpected host mismatch %s" % host
8585

8686
# both, as per #TO3k2
8787
with pytest.raises(ValueError):
@@ -103,13 +103,13 @@ def test_fallback_hosts(self):
103103
assert sorted(aux) == sorted(ably.options.get_fallback_rest_hosts())
104104

105105
# Specify environment (RSC15g2)
106-
ably = AblyRest(token='foo', environment='sandbox', http_max_retry_count=10)
107-
assert sorted(Defaults.get_environment_fallback_hosts('sandbox')) == sorted(
106+
ably = AblyRest(token='foo', environment='nonprod:sandbox', http_max_retry_count=10)
107+
assert sorted(Defaults.get_fallback_hosts('nonprod:sandbox')) == sorted(
108108
ably.options.get_fallback_rest_hosts())
109109

110110
# Fallback hosts and environment not specified (RSC15g3)
111111
ably = AblyRest(token='foo', http_max_retry_count=10)
112-
assert sorted(Defaults.fallback_hosts) == sorted(ably.options.get_fallback_rest_hosts())
112+
assert sorted(Defaults.get_fallback_hosts()) == sorted(ably.options.get_fallback_rest_hosts())
113113

114114
# RSC15f
115115
ably = AblyRest(token='foo')
@@ -182,13 +182,19 @@ async def test_query_time_param(self):
182182
@dont_vary_protocol
183183
def test_requests_over_https_production(self):
184184
ably = AblyRest(token='token')
185-
assert 'https://rest.ably.io' == '{0}://{1}'.format(ably.http.preferred_scheme, ably.http.preferred_host)
185+
assert 'https://main.realtime.ably.net' == '{0}://{1}'.format(
186+
ably.http.preferred_scheme, ably.http.preferred_host
187+
)
188+
186189
assert ably.http.preferred_port == 443
187190

188191
@dont_vary_protocol
189192
def test_requests_over_http_production(self):
190193
ably = AblyRest(token='token', tls=False)
191-
assert 'http://rest.ably.io' == '{0}://{1}'.format(ably.http.preferred_scheme, ably.http.preferred_host)
194+
assert 'http://main.realtime.ably.net' == '{0}://{1}'.format(
195+
ably.http.preferred_scheme, ably.http.preferred_host
196+
)
197+
192198
assert ably.http.preferred_port == 80
193199

194200
@dont_vary_protocol
@@ -211,7 +217,7 @@ async def test_environment(self):
211217
except AblyException:
212218
pass
213219
request = get_mock.call_args_list[0][0][0]
214-
assert request.url == 'https://custom-rest.ably.io:443/time'
220+
assert request.url == 'https://custom.realtime.ably.net:443/time'
215221

216222
await ably.close()
217223

test/ably/rest/restpaginatedresult_test.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ async def asyncSetUp(self):
3131
self.ably = await TestApp.get_ably_rest(use_binary_protocol=False)
3232
# Mocked responses
3333
# without specific headers
34-
self.mocked_api = respx.mock(base_url='http://rest.ably.io')
34+
self.mocked_api = respx.mock(base_url='http://main.realtime.ably.net')
3535
self.ch1_route = self.mocked_api.get('/channels/channel_name/ch1')
3636
self.ch1_route.return_value = Response(
3737
headers={'content-type': 'application/json'},
@@ -44,8 +44,8 @@ async def asyncSetUp(self):
4444
headers={
4545
'content-type': 'application/json',
4646
'link':
47-
'<http://rest.ably.io/channels/channel_name/ch2?page=1>; rel="first",'
48-
' <http://rest.ably.io/channels/channel_name/ch2?page=2>; rel="next"'
47+
'<http://main.realtime.ably.net/channels/channel_name/ch2?page=1>; rel="first",'
48+
' <http://main.realtime.ably.net/channels/channel_name/ch2?page=2>; rel="next"'
4949
},
5050
body='[{"id": 0}, {"id": 1}]',
5151
status=200
@@ -55,11 +55,11 @@ async def asyncSetUp(self):
5555

5656
self.paginated_result = await PaginatedResult.paginated_query(
5757
self.ably.http,
58-
url='http://rest.ably.io/channels/channel_name/ch1',
58+
url='http://main.realtime.ably.net/channels/channel_name/ch1',
5959
response_processor=lambda response: response.to_native())
6060
self.paginated_result_with_headers = await PaginatedResult.paginated_query(
6161
self.ably.http,
62-
url='http://rest.ably.io/channels/channel_name/ch2',
62+
url='http://main.realtime.ably.net/channels/channel_name/ch2',
6363
response_processor=lambda response: response.to_native())
6464

6565
async def asyncTearDown(self):

test/ably/rest/restrequest_test.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ async def test_timeout(self):
101101
await ably.request('GET', '/time', version=Defaults.protocol_version)
102102
await ably.close()
103103

104-
default_endpoint = 'https://sandbox-rest.ably.io/time'
105-
fallback_host = 'sandbox-a-fallback.ably-realtime.com'
104+
default_endpoint = 'https://sandbox.realtime.ably-nonprod.net/time'
105+
fallback_host = 'sandbox.a.fallback.ably-realtime-nonprod.com'
106106
fallback_endpoint = f'https://{fallback_host}/time'
107107
ably = await TestApp.get_ably_rest(fallback_hosts=[fallback_host])
108108
with respx.mock:

test/ably/testapp.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@
1414
app_spec_local = json.loads(f.read())
1515

1616
tls = (os.environ.get('ABLY_TLS') or "true").lower() == "true"
17-
rest_host = os.environ.get('ABLY_REST_HOST', 'sandbox-rest.ably.io')
18-
realtime_host = os.environ.get('ABLY_REALTIME_HOST', 'sandbox-realtime.ably.io')
17+
rest_host = os.environ.get('ABLY_REST_HOST', 'sandbox.realtime.ably-nonprod.net')
18+
realtime_host = os.environ.get('ABLY_REALTIME_HOST', 'sandbox.realtime.ably-nonprod.net')
1919

20-
environment = os.environ.get('ABLY_ENV', 'sandbox')
20+
environment = os.environ.get('ABLY_ENV', 'nonprod:sandbox')
2121

2222
port = 80
2323
tls_port = 443
2424

25-
if rest_host and not rest_host.endswith("rest.ably.io"):
25+
if rest_host and not rest_host.endswith("realtime.ably-nonprod.net"):
2626
tls = tls and rest_host != "localhost"
2727
port = 8080
2828
tls_port = 8081

0 commit comments

Comments
 (0)