Usage¶
When running as a standalone process, Sidecar can be called by sending an HTTP request to localhost. For example, to access the feature flags, you can use cURL:
curl http://localhost:12000/feature
Sidecar can also be used as a library in Python 3 apps to provide Service Discovery and Fault tolerance when making requests to other services.
Service Discovery Example¶
Currently, Sidecar provides service discovery through configuration files, though additional mechanisms may be added in the future. Here, then, is an example of using service discovery in your app:
# /my/config/path/discovery.json
{
"coral": [
"localhost:8771"
]
}
# myapp.py
import aiohttp
from sidecar import services, service_discovery
async def get_user(self):
services_instance = services.Services(path="/my/config/path/discovery.json")
await services_instance.start(loop=loop)
services_instance = service_discovery.ServiceDiscovery(services_instance)
scheme, netloc = services_instance.service_load_balancer("coral")
url = "{}://{}/v2/ops/user/test@example.com".format(scheme, netloc)
with aiohttp.ClientSession() as session:
async with session.get(url,
params={"expand": "group"},
headers=headers,
allow_redirects=True) as response:
data = await response.json() or {}
Fault Tolerance Example¶
The sidecar.call.fault_tolerant() decorator is the public API for adding fault tolerance to a service request with
Sidecar.
The decorator accepts some values as arguments for import-time configuration, but configuration that requires state,
such as a Sidecar-provided load balancing function or a connected Statsd protocol instance, can be provided as
instance attributes on the object that contains the decorated method. sidecar.decorator.find_property() will
detect and use these attributes.
Example:
# myapp.py
import os
from aiohttp import web
from aiohttp.web_exceptions import HTTPNotFound, HTTPBadGateway, HTTPServiceUnavailable
from aiohttp.web_reqrep import json_response
import asyncio
from sidecar import services, service_discovery
from sidecar.call import fault_tolerant
from sidecar.statsd import StatsD
class UserApi:
"""Public API for viewing existing users."""
def __init__(self, service_discovery: ServiceDiscovery, statsd: StatsD):
self.load_balancer = service_discovery.service_load_balancer("coral")
self.statsd = statsd
async def get_user_with_group(self, request):
id_or_email = request.match_info["id_or_email"]
try:
user = await self._fetch_user_from_coral(id_or_email=id_or_email)
except (SidecarTimeoutError, SidecarCircuitOpenError, SidecarRateLimitedError) as exc:
error_message = "Request to internal HipChat service failed: {}".format(exc)
log.error(error_message)
raise HTTPBadGateway(reason=error_message)
except SidecarRetryExhaustedError as exc:
log.error("Sidecar retries exhausted: %s", exc.message)
if exc.code == 503:
raise HTTPServiceUnavailable(reason=exc.message)
raise HTTPBadGateway(reason=exc.message)
return json_response(data=user)
@fault_tolerant("ops-user", retry_on_timeout=True, expected_in=3)
async def _fetch_user_from_coral(self, call: Call, id_or_email: str):
url = "{}://{}/v2/ops/user/{}".format(call.scheme, call.netloc, id_or_email)
headers = {"X-HIPCHAT-GROUP": "0"}
async with call.http_session.get(url,
params={"expand": "group"},
headers=headers,
allow_redirects=True) as response:
response_text = await response.text() or ''
if response.status == 404:
raise HTTPNotFound(reason="User {} does not exist".format(id_or_email))
if response.status == 503:
error_message = "Received 503 Service Unavailable from internal API {} with message {}".format(
url, response_text)
log.error(error_message)
raise SidecarRetryRequestedError(code=503, message=error_message)
if response.status != 200:
error_message = "Received error from internal API {} with message {}".format(url, response_text)
log.error(error_message)
raise SidecarRetryRequestedError(code=response.status, message=error_message)
try:
user = json.loads(response_text)
except json.JSONDecodeError as exc:
error_message = "Error decoding JSON from success response from internal API {}: {}".format(url, exc)
log.exception(error_message)
raise HTTPBadGateway(reason=error_message)
return user
def init(loop):
webapp = web.Application(loop=loop)
services_instance = services.Services(path="/my/config/path/discovery.json")
await services_instance.start(loop=loop)
services_instance = service_discovery.ServiceDiscovery(services_instance)
statsd_instance = StatsD(
loop, hostname=os.environ.get('HOSTNAME', ''), host='localhost', port='333333', prefix='myapp')
await statsd_instance.start()
UserApi(services_instance, statsd_instance)
webapp.router.add_route('GET', '/user/{id_or_email}/', name='get_user')
return webapp
if __name__ == "__main__":
loop = asyncio.get_event_loop()
app = loop.run_until_complete(init(loop))