Exemple #1
0
    async def _connect_with_login(self, endpoints):
        """Connect to the websocket.

        If uuid is None, the connection will be to the controller. Otherwise it
        will be to the model.
        :return: The response field of login response JSON object.
        """
        success = False
        try:
            await self._connect(endpoints)
            # It's possible that we may get several discharge-required errors,
            # corresponding to different levels of authentication, so retry
            # a few times.
            for i in range(0, 2):
                result = (await self.login())['response']
                macaroonJSON = result.get('discharge-required')
                if macaroonJSON is None:
                    self.info = result
                    success = True
                    return result
                macaroon = bakery.Macaroon.from_dict(macaroonJSON)
                self.bakery_client.handle_error(
                    httpbakery.Error(
                        code=httpbakery.ERR_DISCHARGE_REQUIRED,
                        message=result.get('discharge-required-error'),
                        version=macaroon.version,
                        info=httpbakery.ErrorInfo(
                            macaroon=macaroon,
                            macaroon_path=result.get('macaroon-path'),
                        ),
                    ),
                    # note: remove the port number.
                    'https://' + self.endpoint + '/',
                )
            raise errors.JujuAuthError('failed to authenticate '
                                       'after several attempts')
        finally:
            if not success:
                await self.close()
            else:
                self._pinger_task.start()
Exemple #2
0
    async def connect(
        cls,
        endpoint=None,
        uuid=None,
        username=None,
        password=None,
        cacert=None,
        bakery_client=None,
        loop=None,
        max_frame_size=None,
    ):
        """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
            controller-only connection).
        :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
            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 asyncio.BaseEventLoop loop: The event loop to use for async
            operations.
        :param int max_frame_size: The maximum websocket frame size to allow.
        """
        self = cls()
        if endpoint is None:
            raise ValueError('no endpoint provided')
        self.uuid = uuid
        if bakery_client is None:
            bakery_client = httpbakery.Client()
        self.bakery_client = bakery_client
        if username and '@' in username and not username.endswith('@local'):
            # We're trying to log in as an external user - we need to use
            # macaroon authentication with no username or password.
            if password is not None:
                raise errors.JujuAuthError('cannot log in as external '
                                           'user with a password')
            username = None
        self.usertag = tag.user(username)
        self.password = password
        self.loop = loop or asyncio.get_event_loop()

        self.__request_id__ = 0

        # The following instance variables are initialized by the
        # _connect_with_redirect method, but create them here
        # as a reminder that they will exist.
        self.addr = None
        self.ws = None
        self.endpoint = None
        self.cacert = None
        self.info = None

        # Create that _Task objects but don't start the tasks yet.
        self._pinger_task = _Task(self._pinger, self.loop)
        self._receiver_task = _Task(self._receiver, self.loop)

        self.facades = {}
        self.messages = IdQueue(loop=self.loop)
        self.monitor = Monitor(connection=self)
        if max_frame_size is None:
            max_frame_size = self.MAX_FRAME_SIZE
        self.max_frame_size = max_frame_size
        await self._connect_with_redirect([(endpoint, cacert)])
        return self
Exemple #3
0
    async def connect(
        cls,
        endpoint=None,
        uuid=None,
        username=None,
        password=None,
        cacert=None,
        bakery_client=None,
        loop=None,
        max_frame_size=None,
        retries=3,
        retry_backoff=10,
        specified_facades=None,
    ):
        """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 (or list of strings).
        :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
            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
            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 asyncio.BaseEventLoop loop: The event loop to use for async
            operations.
        :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).
        :param specified_facades: Define a series of facade versions you wish to override
            to prevent using the conservative client pinning with in the client.
        """
        self = cls()
        if endpoint is None:
            raise ValueError('no endpoint provided')
        if not isinstance(endpoint, str) and not isinstance(endpoint, list):
            raise TypeError("Endpoint should be either str or list")
        self.uuid = uuid
        if bakery_client is None:
            bakery_client = httpbakery.Client()
        self.bakery_client = bakery_client
        if username and '@' in username and not username.endswith('@local'):
            # We're trying to log in as an external user - we need to use
            # macaroon authentication with no username or password.
            if password is not None:
                raise errors.JujuAuthError('cannot log in as external '
                                           'user with a password')
            username = None
        self.usertag = tag.user(username)
        self.password = password
        self.loop = loop or asyncio.get_event_loop()

        self.__request_id__ = 0

        # The following instance variables are initialized by the
        # _connect_with_redirect method, but create them here
        # as a reminder that they will exist.
        self.addr = None
        self.ws = None
        self.endpoint = None
        self.endpoints = None
        self.cacert = None
        self.info = None

        # Create that _Task objects but don't start the tasks yet.
        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.specified_facades = specified_facades or {}

        self.messages = IdQueue(loop=self.loop)
        self.monitor = Monitor(connection=self)
        if max_frame_size is None:
            max_frame_size = self.MAX_FRAME_SIZE
        self.max_frame_size = max_frame_size

        _endpoints = [(endpoint, cacert)] if isinstance(
            endpoint, str) else [(e, cacert) for e in endpoint]
        for _ep in _endpoints:
            try:
                await self._connect_with_redirect([_ep])
                return self
            except OSError as e:
                logging.debug("Cannot access endpoint {}: {}".format(
                    _ep, e.strerror))
                continue
        raise Exception("Unable to connect to websocket")