Skip to content

Commit

Permalink
Fallback to default sapi endpoint if metadata api is down
Browse files Browse the repository at this point in the history
  • Loading branch information
randomir committed Oct 12, 2021
1 parent 943edb0 commit 1f3e318
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 12 deletions.
3 changes: 1 addition & 2 deletions dwave/cloud/api/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ class ResourceBadResponseError(RequestError):
"""Unexpected resource response"""

class InternalServerError(RequestError):
pass

"""internal server error occurred while request handling."""

class RequestTimeout(RequestError):
"""API request timed out"""
43 changes: 33 additions & 10 deletions dwave/cloud/client/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,34 @@ def from_config(cls, config_file=None, profile=None, client=None, **kwargs):
logger.debug("Creating %s.Client() with: %r", _client, config)
return _clients[_client](**config)

def _resolve_region_endpoint(self, *, region: str = None, endpoint: str = None):
"""For a region/endpoint pair from config, return the Solver API
endpoint to use (and the matching region).
Explicit endpoint will override the region (i.e. region extension is
backwards-compatible).
Regional endpoint is fetched from Metadata API. If Metadata API is not
available, default global endpoint is used.
"""
if endpoint:
return (region, endpoint)

if not region:
return (self.DEFAULT_API_REGION, self.DEFAULT_API_ENDPOINT)

try:
regions = self.get_regions()
except (api.exceptions.RequestError, ValueError) as exc:
logger.warning("Failed to fetch available regions: %r. "
"Using the default solver API endpoint.", exc)
return (self.DEFAULT_API_REGION, self.DEFAULT_API_ENDPOINT)

if region not in regions:
raise ValueError(f"Region {region!r} unknown. "
f"Try one of {list(regions.keys())!r}.")
return (region, regions[region]['endpoint'])

@dispatches_events('client_init')
def __init__(self, endpoint=None, token=None, solver=None, **kwargs):
# for (reasonable) backwards compatibility, accept only the first few
Expand Down Expand Up @@ -393,17 +421,12 @@ def __init__(self, endpoint=None, token=None, solver=None, **kwargs):

logger.debug("Client options with defaults: %r", options)

# resolve endpoint using region if necessary
self.metadata_api_endpoint = options['metadata_api_endpoint'] # for .get_regions()
# configure MetadataAPI access -- needed by Client.get_regions()
self.metadata_api_endpoint = options['metadata_api_endpoint']

region = options['region']
endpoint = options['endpoint']
if not endpoint:
regions = self.get_regions()
if region not in regions:
raise ValueError(f"Region {region!r} unknown. "
f"Try one of {list(regions.keys())!r}.")
endpoint = regions[region]['endpoint']
# resolve endpoint using region
region, endpoint = self._resolve_region_endpoint(
region=options['region'], endpoint=options['endpoint'])

# sanity check
if not endpoint:
Expand Down
45 changes: 45 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,51 @@ def get(url, **kwargs):
self.assertEqual(client.region, selected_region)
self.assertEqual(client.endpoint, selected_endpoint)

def test_region_endpoint_fallback_when_no_metadata_api(self):
"""Given region in config, and endpoint omitted,
when Client is initialized from config and MetadataAPI is down,
then the client is initialized to use the default (old) endpoint and region.
"""
# test Client._resolve_region_endpoint

conf = dict(token='token', metadata_api_endpoint='invalid')
region = Client.DEFAULT_API_REGION

with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: conf):
with dwave.cloud.Client.from_config(region=region) as client:
self.assertEqual(client.region, Client.DEFAULT_API_REGION)
self.assertEqual(client.endpoint, Client.DEFAULT_API_ENDPOINT)

@mock.patch.multiple(Client, get_regions=get_default_regions)
def test_region_endpoint_fallback_when_region_unknown(self):
"""Given invalid region in config, and endpoint omitted,
when Client is initialized from config,
then ValueError is raised.
"""
# test Client._resolve_region_endpoint

conf = dict(token='token')
region = 'invalid-region-code'

with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: conf):
with self.assertRaises(ValueError):
Client.from_config(region=region)

@mock.patch.multiple(Client, get_regions=get_default_regions)
def test_region_endpoint_null_case(self):
"""Given region as None, and endpoint as None,
when Client is initialized from config,
then the client is initialized to use the default (old) endpoint and region.
"""
# test Client._resolve_region_endpoint

conf = dict(token='token', region='')

with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: conf):
with dwave.cloud.Client.from_config() as client:
self.assertEqual(client.region, Client.DEFAULT_API_REGION)
self.assertEqual(client.endpoint, Client.DEFAULT_API_ENDPOINT)

@unittest.skipUnless(config, "No live server configuration available.")
@mock.patch.multiple(Client._fetch_available_regions._cached, cache={})
def test_region_selection_live_end_to_end(self):
Expand Down

0 comments on commit 1f3e318

Please sign in to comment.