bakery_client=None,
loop=None,
max_frame_size=None,
+ retries=3,
+ retry_backoff=10,
):
"""Connect to the websocket.
If uuid is None, the connection will be to the controller. Otherwise it
will be to the model.
- :param str endpoint The hostname:port of the controller to connect to.
- :param str uuid The model UUID to connect to (None for a
+
+ :param str endpoint: The hostname:port of the controller to connect to.
+ :param str uuid: The model UUID to connect to (None for a
controller-only connection).
- :param str username The username for controller-local users (or None
+ :param str username: The username for controller-local users (or None
to use macaroon-based login.)
- :param str password The password for controller-local users.
- :param str cacert The CA certificate of the controller (PEM formatted).
- :param httpbakery.Client bakery_client The macaroon bakery client to
+ :param str password: The password for controller-local users.
+ :param str cacert: The CA certificate of the controller
+ (PEM formatted).
+ :param httpbakery.Client bakery_client: The macaroon bakery client to
to use when performing macaroon-based login. Macaroon tokens
acquired when logging will be saved to bakery_client.cookies.
If this is None, a default bakery_client will be used.
- :param loop asyncio.BaseEventLoop The event loop to use for async
+ :param asyncio.BaseEventLoop loop: The event loop to use for async
operations.
- :param max_frame_size The maximum websocket frame size to allow.
+ :param int max_frame_size: The maximum websocket frame size to allow.
+ :param int retries: When connecting or reconnecting, and all endpoints
+ fail, how many times to retry the connection before giving up.
+ :param int retry_backoff: Number of seconds to increase the wait
+ between connection retry attempts (a backoff of 10 with 3 retries
+ would wait 10s, 20s, and 30s).
"""
self = cls()
if endpoint is None:
self._pinger_task = _Task(self._pinger, self.loop)
self._receiver_task = _Task(self._receiver, self.loop)
+ self._retries = retries
+ self._retry_backoff = retry_backoff
+
self.facades = {}
self.messages = IdQueue(loop=self.loop)
self.monitor = Monitor(connection=self)
tasks = [self.loop.create_task(_try_endpoint(endpoint, cacert,
0.1 * i))
for i, (endpoint, cacert) in enumerate(endpoints)]
- for task in asyncio.as_completed(tasks, loop=self.loop):
- try:
- result = await task
- break
- except ConnectionError:
- continue # ignore; try another endpoint
- else:
- raise errors.JujuConnectionError(
- 'Unable to connect to any endpoint: {}'.format(', '.join([
- endpoint for endpoint, cacert in endpoints])))
+ for attempt in range(self._retries + 1):
+ for task in asyncio.as_completed(tasks, loop=self.loop):
+ try:
+ result = await task
+ break
+ except ConnectionError:
+ continue # ignore; try another endpoint
+ else:
+ _endpoints_str = ', '.join([endpoint
+ for endpoint, cacert in endpoints])
+ if attempt < self._retries:
+ log.debug('Retrying connection to endpoints: {}; '
+ 'attempt {} of {}'.format(_endpoints_str,
+ attempt + 1,
+ self._retries + 1))
+ await asyncio.sleep((attempt + 1) * self._retry_backoff)
+ continue
+ else:
+ raise errors.JujuConnectionError(
+ 'Unable to connect to any endpoint: '
+ '{}'.format(_endpoints_str))
+ # only executed if inner loop's else did not continue
+ # (i.e., inner loop did break due to successful connection)
+ break
for task in tasks:
task.cancel()
self.ws = result[0]
self.endpoint = result[2]
self.cacert = result[3]
self._receiver_task.start()
- log.info("Driver connected to juju %s", self.addr)
+ log.debug("Driver connected to juju %s", self.addr)
self.monitor.close_called.clear()
async def _connect_with_login(self, endpoints):
async def login(self):
params = {}
+ params['auth-tag'] = self.usertag
if self.password:
- params['auth-tag'] = self.usertag
params['credentials'] = self.password
else:
macaroons = _macaroons_for_domain(self.bakery_client.cookies,
def start(self):
async def run():
try:
- return await(self.task())
+ return await self.task()
finally:
self.stopped.set()
self.stopped.clear()