+++ /dev/null
-import asyncio
-import logging
-import copy
-
-import macaroonbakery.httpbakery as httpbakery
-from juju.client.connection import Connection
-from juju.client.gocookies import go_to_py_cookie, GoCookieJar
-from juju.client.jujudata import FileJujuData
-from juju.errors import JujuConnectionError, JujuError
-
-log = logging.getLogger('connector')
-
-
-class NoConnectionException(Exception):
- '''Raised by Connector when the connection method is called
- and there is no current connection.'''
- pass
-
-
-class Connector:
- '''This class abstracts out a reconnectable client that can connect
- to controllers and models found in the Juju data files.
- '''
- def __init__(
- self,
- loop=None,
- max_frame_size=None,
- bakery_client=None,
- jujudata=None,
- ):
- '''Initialize a connector that will use the given parameters
- by default when making a new connection'''
- self.max_frame_size = max_frame_size
- self.loop = loop or asyncio.get_event_loop()
- self.bakery_client = bakery_client
- self._connection = None
- self.controller_name = None
- self.model_name = None
- self.jujudata = jujudata or FileJujuData()
-
- def is_connected(self):
- '''Report whether there is a currently connected controller or not'''
- return self._connection is not None
-
- def connection(self):
- '''Return the current connection; raises an exception if there
- is no current connection.'''
- if not self.is_connected():
- raise NoConnectionException('not connected')
- return self._connection
-
- async def connect(self, **kwargs):
- """Connect to an arbitrary Juju model.
-
- kwargs are passed through to Connection.connect()
- """
- kwargs.setdefault('loop', self.loop)
- kwargs.setdefault('max_frame_size', self.max_frame_size)
- kwargs.setdefault('bakery_client', self.bakery_client)
- if 'macaroons' in kwargs:
- if not kwargs['bakery_client']:
- kwargs['bakery_client'] = httpbakery.Client()
- if not kwargs['bakery_client'].cookies:
- kwargs['bakery_client'].cookies = GoCookieJar()
- jar = kwargs['bakery_client'].cookies
- for macaroon in kwargs.pop('macaroons'):
- jar.set_cookie(go_to_py_cookie(macaroon))
- self._connection = await Connection.connect(**kwargs)
-
- async def disconnect(self):
- """Shut down the watcher task and close websockets.
- """
- if self._connection:
- log.debug('Closing model connection')
- await self._connection.close()
- self._connection = None
-
- async def connect_controller(self, controller_name=None):
- """Connect to a controller by name. If the name is empty, it
- connect to the current controller.
- """
- if not controller_name:
- controller_name = self.jujudata.current_controller()
- if not controller_name:
- raise JujuConnectionError('No current controller')
-
- controller = self.jujudata.controllers()[controller_name]
- # TODO change Connection so we can pass all the endpoints
- # instead of just the first.
- endpoint = controller['api-endpoints'][0]
- accounts = self.jujudata.accounts().get(controller_name, {})
-
- await self.connect(
- endpoint=endpoint,
- uuid=None,
- username=accounts.get('user'),
- password=accounts.get('password'),
- cacert=controller.get('ca-cert'),
- bakery_client=self.bakery_client_for_controller(controller_name),
- )
- self.controller_name = controller_name
-
- async def connect_model(self, model_name=None):
- """Connect to a model by name. If either controller or model
- parts of the name are empty, the current controller and/or model
- will be used.
-
- :param str model: <controller>:<model>
- """
-
- try:
- controller_name, model_name = self.jujudata.parse_model(model_name)
- controller = self.jujudata.controllers().get(controller_name)
- except JujuError as e:
- raise JujuConnectionError(e.message) from e
- if controller is None:
- raise JujuConnectionError('Controller {} not found'.format(
- controller_name))
- # TODO change Connection so we can pass all the endpoints
- # instead of just the first one.
- endpoint = controller['api-endpoints'][0]
- account = self.jujudata.accounts().get(controller_name, {})
- models = self.jujudata.models().get(controller_name, {}).get('models',
- {})
- if model_name not in models:
- raise JujuConnectionError('Model not found: {}'.format(model_name))
-
- # TODO if there's no record for the required model name, connect
- # to the controller to find out the model's uuid, then connect
- # to that. This will let connect_model work with models that
- # haven't necessarily synced with the local juju data,
- # and also remove the need for base.CleanModel to
- # subclass JujuData.
- await self.connect(
- endpoint=endpoint,
- uuid=models[model_name]['uuid'],
- username=account.get('user'),
- password=account.get('password'),
- cacert=controller.get('ca-cert'),
- bakery_client=self.bakery_client_for_controller(controller_name),
- )
- self.controller_name = controller_name
- self.model_name = controller_name + ':' + model_name
-
- def bakery_client_for_controller(self, controller_name):
- '''Make a copy of the bakery client with a the appropriate controller's
- cookiejar in it.
- '''
- bakery_client = self.bakery_client
- if bakery_client:
- bakery_client = copy.copy(bakery_client)
- else:
- bakery_client = httpbakery.Client()
- bakery_client.cookies = self.jujudata.cookies_for_controller(
- controller_name)
- return bakery_client