Пример #1
0
    def __init__(self, app, id=None, config=None):
        super().__init__(app, id=id, config=config)

        self.ConnectionEvent = asyncio.Event(loop=app.Loop)
        self.ConnectionEvent.clear()

        self.PubSub = PubSub(app)
        self.Loop = app.Loop

        self._host = self.Config['host']
        self._port = int(self.Config['port'])
        self._user = self.Config['user']
        self._password = self.Config['password']
        self._connect_timeout = int(self.Config['connect_timeout'])
        self._db = self.Config['db']
        self._reconnect_delay = self.Config['reconnect_delay']
        self._output_queue_max_size = self.Config['output_queue_max_size']
        self._max_bulk_size = int(self.Config['max_bulk_size'])

        self._conn_future = None
        self._connection_request = False
        self._pause = False
        self._throttled_by_error = False

        # Subscription
        self._on_health_check('connection.open!')
        app.PubSub.subscribe("Application.stop!", self._on_application_stop)
        app.PubSub.subscribe("Application.tick!", self._on_health_check)
        app.PubSub.subscribe("MySQLConnection.pause!", self._on_pause)
        app.PubSub.subscribe("MySQLConnection.unpause!", self._on_unpause)

        self._output_queue = asyncio.Queue(loop=app.Loop)
        self._bulks = {}  # We have a "bulk" per query
Пример #2
0
    def __init__(self, app, connection_id, config=None):
        super().__init__(app, connection_id, config=config)

        self.ConnectionEvent = asyncio.Event(loop=app.Loop)
        self.ConnectionEvent.clear()

        self.PubSub = PubSub(app)
        self.Loop = app.Loop

        self._host = self.Config['host']
        self._port = self.Config['port']
        self._user = self.Config['user']
        self._password = self.Config['password']
        self._connect_timeout = self.Config['connect_timeout']
        self._db = self.Config['db']
        self._reconnect_delay = self.Config['reconnect_delay']
        self._output_queue_max_size = self.Config['output_queue_max_size']

        self._conn_future = None
        self._connection_request = False

        # Subscription
        self._on_health_check('connection.open!')
        app.PubSub.subscribe("Application.stop!", self._on_application_stop)
        app.PubSub.subscribe("Application.tick!", self._on_health_check)

        self._output_queue = asyncio.Queue(loop=app.Loop)
	def __init__(self, app, id=None, config=None):
		super().__init__(app, id=id, config=config)

		self.Connection = None
		self.ConnectionEvent = asyncio.Event(loop=app.Loop)
		self.ConnectionEvent.clear()

		self.PubSub = PubSub(app)
		self.Loop = app.Loop

		self._reconnect()
Пример #4
0
class MySQLConnection(Connection):

    ConfigDefaults = {
        'host': 'localhost',
        'port': 3306,
        'user': '',
        'password': '',
        'db': '',
        'connect_timeout': 1,
        'reconnect_delay': 5.0,
        'output_queue_max_size': 3,
    }

    def __init__(self, app, connection_id, config=None):
        super().__init__(app, connection_id, config=config)

        self.ConnectionEvent = asyncio.Event(loop=app.Loop)
        self.ConnectionEvent.clear()

        self.PubSub = PubSub(app)
        self.Loop = app.Loop

        self._host = self.Config['host']
        self._port = self.Config['port']
        self._user = self.Config['user']
        self._password = self.Config['password']
        self._connect_timeout = self.Config['connect_timeout']
        self._db = self.Config['db']
        self._reconnect_delay = self.Config['reconnect_delay']
        self._output_queue_max_size = self.Config['output_queue_max_size']

        self._conn_future = None
        self._connection_request = False

        # Subscription
        self._on_health_check('connection.open!')
        app.PubSub.subscribe("Application.stop!", self._on_application_stop)
        app.PubSub.subscribe("Application.tick!", self._on_health_check)

        self._output_queue = asyncio.Queue(loop=app.Loop)

    def _on_application_stop(self, message_type, counter):
        self._output_queue.put_nowait(None)

    def _on_health_check(self, message_type):
        if self._conn_future is not None:
            # Connection future exists

            if not self._conn_future.done():
                # Connection future didn't result yet
                # No sanitization needed
                return

            try:
                self._conn_future.result()
            except:
                # Connection future threw an error
                L.exception("Unexpected connection future error")

            # Connection future already resulted (with or without exception)
            self._conn_future = None

        assert (self._conn_future is None)

        self._conn_future = asyncio.ensure_future(self._connection(),
                                                  loop=self.Loop)

    async def _connection(self):
        try:
            async with create_pool(
                    host=self._host,
                    port=self._port,
                    user=self._user,
                    password=self._password,
                    db=self._db,
                    connect_timeout=self.
                    _connect_timeout,  #Doesn't work! See socket.timeout exception below
                    loop=self.Loop) as pool:

                self._conn_pool = pool
                self.ConnectionEvent.set()
                await self._loader()
        except socket.timeout:
            # Socket timeout not implemented in aiomysql as it sets a keepalive to the connection
            # it has been placed as an issue on GitHub: https://github.com/aio-libs/aiomysql/issues/257
            L.exception("MySQL connection timeout")
            pass
        except BaseException:
            L.exception("Unexpected MySQL connection error")
            raise

    def acquire(self):
        assert (self._conn_pool is not None)
        return self._conn_pool.acquire()

    def consume(self, query):
        self._output_queue.put_nowait(query)
        if self._output_queue.qsize() == self._output_queue_max_size:
            self.PubSub.publish("MySQLConnection.pause!", self)

    async def _loader(self):
        while True:
            query = await self._output_queue.get()

            if query is None:
                break

            if self._output_queue.qsize() == self._output_queue_max_size - 1:
                self.PubSub.publish("MySQLConnection.unpause!",
                                    self,
                                    asynchronously=True)

            try:
                async with self.acquire() as conn:
                    try:
                        async with conn.cursor() as cur:
                            await cur.execute(query)
                            await conn.commit()
                    except BaseException as e:
                        L.exception(
                            "Unexpected error when processing MySQL query.")
            except BaseBaseException as e:
                L.exception("Couldn't acquire connection")
Пример #5
0
class MySQLConnection(Connection):
    """
	MySQLConnection is a top BitSwan object, that is used to connect the application to an external MySQL database.
	MySQLConnection is thus used in MySQL-related sources, sinks, processors and lookups such as MySQLLookup.

	MySQLConnection is built on top of aiomysql library and utilizes its functions: https://aiomysql.readthedocs.io/en/latest/

	The following code illustrates how to create and register the MySQL connection inside the application object.
	The connection's ID is set to "MySQLConnection" (the second argument of the connection's constructor),
	which is also then used as a section name in configuration files.

.. code:: python

	app = bspump.BSPumpApplication()
	svc = app.get_service("bspump.PumpService")
	svc.add_connection(
		bspump.mysql.MySQLConnection(app, "MySQLConnection")
	)

	In order to locate MySQLConnection in the constructor method of sources, sinks, processors etc., the following code can be used:

.. code:: python

	svc = app.get_service("bspump.PumpService")
	connection = svc.locate_connection("MySQLConnection")

	"""

    ConfigDefaults = {
        'host': 'localhost',
        'port': 3306,
        'user': '',
        'password': '',
        'db': '',
        'connect_timeout': 10,
        'reconnect_delay': 5.0,
        'output_queue_max_size': 10,
        'max_bulk_size': 2,
    }

    RetryErrors = frozenset([
        1047, 1053, 1077, 1078, 1079, 1080, 2003, 2006, 2012, 2013, 1152, 1205,
        1213, 1223
    ])

    def __init__(self, app, id=None, config=None):
        super().__init__(app, id=id, config=config)

        self.ConnectionEvent = asyncio.Event(loop=app.Loop)
        self.ConnectionEvent.clear()

        self.PubSub = PubSub(app)
        self.Loop = app.Loop

        self._host = self.Config['host']
        self._port = int(self.Config['port'])
        self._user = self.Config['user']
        self._password = self.Config['password']
        self._connect_timeout = int(self.Config['connect_timeout'])
        self._db = self.Config['db']
        self._reconnect_delay = self.Config['reconnect_delay']
        self._output_queue_max_size = self.Config['output_queue_max_size']
        self._max_bulk_size = int(self.Config['max_bulk_size'])

        self._conn_future = None
        self._connection_request = False
        self._pause = False
        self._throttled_by_error = False

        # Subscription
        self._on_health_check('connection.open!')
        app.PubSub.subscribe("Application.stop!", self._on_application_stop)
        app.PubSub.subscribe("Application.tick!", self._on_health_check)
        app.PubSub.subscribe("MySQLConnection.pause!", self._on_pause)
        app.PubSub.subscribe("MySQLConnection.unpause!", self._on_unpause)

        self._output_queue = asyncio.Queue(loop=app.Loop)
        self._bulks = {}  # We have a "bulk" per query

    def _on_pause(self):
        self._pause = True

    def _on_unpause(self):
        self._pause = False

    def _flush(self):
        for query in self._bulks.keys():
            # Break if throttling was requested during the flush,
            # so that put_nowait doesn't raise
            if self._pause:
                break

            self._flush_bulk(query)

    def _flush_bulk(self, query):

        # Enqueue and throttle if needed
        self._output_queue.put_nowait((query, self._bulks[query]))
        if self._output_queue.qsize() == self._output_queue_max_size:
            self.PubSub.publish("MySQLConnection.pause!", self)

        # Reset bulk
        self._bulks[query] = []

    def _on_application_stop(self, message_type, counter):
        self._flush()
        self._output_queue.put_nowait((None, None))

    def _on_health_check(self, message_type):
        if self._conn_future is not None:
            # Connection future exists

            if not self._conn_future.done():
                # Connection future didn't result yet
                # No sanitization needed
                return

            try:
                self._conn_future.result()
            except Exception:
                # Connection future threw an error
                L.exception("Unexpected connection future error")

            # Connection future already resulted (with or without exception)
            self._conn_future = None

        assert (self._conn_future is None)

        self._conn_future = asyncio.ensure_future(self._async_connection(),
                                                  loop=self.Loop)

        self._sync_connection()

    async def _async_connection(self):
        try:
            async with aiomysql.create_pool(
                    host=self._host,
                    port=self._port,
                    user=self._user,
                    password=self._password,
                    db=self._db,
                    conv=convertors,
                    connect_timeout=self._connect_timeout,
                    loop=self.Loop) as pool:

                self._conn_pool = pool
                self.ConnectionEvent.set()
                await self._loader()
        except (pymysql.err.InternalError, pymysql.err.ProgrammingError,
                pymysql.err.OperationalError) as e:
            if e.args[0] in self.RetryErrors:
                L.warn(
                    "Recoverable error '{}' occurred in MySQLConnection. Retrying."
                    .format(e.args[0]))
                self._conn_future = None
                return
            L.exception("Unexpected MySQL connection error")
            raise e
        except BaseException:
            L.exception("Unexpected MySQL connection error")
            raise

    def _sync_connection(self):
        try:
            connection = pymysql.connect(host=self._host,
                                         port=self._port,
                                         user=self._user,
                                         password=self._password,
                                         database=self._db,
                                         conv=convertors,
                                         connect_timeout=self._connect_timeout)
            self._conn_sync = connection
        except (pymysql.err.InternalError, pymysql.err.ProgrammingError,
                pymysql.err.OperationalError) as e:
            if e.args[0] in self.RetryErrors:
                L.warn(
                    "Recoverable error '{}' occurred in MySQLConnection. Retrying."
                    .format(e.args[0]))
                return
            L.exception("Unexpected MySQL connection error")
            raise e
        except BaseException:
            L.exception("Unexpected MySQL connection error")
            raise

    def acquire_connection(self) -> aiomysql.utils._PoolAcquireContextManager:
        """
		Acquire asynchronous database connection

		Use with `async with` statement

	.. code-block:: python

		async with self.Connection.acquire_connection() as connection:
			async with connection.cursor() as cursor:
				await cursor.execute(query)

		:return: Asynchronous Context Manager
		"""
        assert (self._conn_pool is not None)
        return self._conn_pool.acquire()

    def acquire_sync_cursor(self) -> pymysql.cursors.DictCursor:
        """
		Acquire synchronous database cursor

		Use with `with` statement

	.. code-block:: python

		with self.Connection.acquire_sync_cursor() as cursor:
			await cursor.execute(query)

		:return: Context Manager
		"""
        assert (self._conn_sync is not None)
        return pymysql.cursors.DictCursor(self._conn_sync)

    def consume(self, query, data):
        # Create a bulk for this query if doesn't yet exist
        if query not in self._bulks:
            self._bulks[query] = []

        # Add data to the query's bulk
        self._bulks[query].append(data)

        # Flush on _max_bulk_size
        if len(self._bulks[query]) >= self._max_bulk_size:
            self._flush_bulk(query)

    async def _loader(self):
        while True:
            query, data = await self._output_queue.get()

            if query is None:
                break

            if self._output_queue.qsize() == self._output_queue_max_size - 1:
                self.PubSub.publish("MySQLConnection.unpause!",
                                    self,
                                    asynchronously=True)

            async with self.acquire_connection() as connection:
                async with connection.cursor() as cursor:
                    try:
                        await cursor.executemany(query, data)
                        await connection.commit()
                        if self._throttled_by_error:
                            self.Pipeline.throttle(self, False)
                            self._throttled_by_error = False
                    except (pymysql.err.InternalError,
                            pymysql.err.ProgrammingError,
                            pymysql.err.OperationalError) as e:
                        if e.args[0] in self.RetryErrors:
                            L.warn(
                                "Recoverable error '{}' occurred in MySQLConnection. Retrying."
                                .format(e.args[0]))
                            self._output_queue.put_nowait((query, data))
                            self.Pipeline.throttle(self, True)
                            self._throttled_by_error = True
                        raise e
class AMQPConnection(Connection):


	ConfigDefaults = {
		'url': 'amqp://localhost/',
		'appname': 'bspump.py',
		'reconnect_delay': 10.0,
	}


	def __init__(self, app, id=None, config=None):
		super().__init__(app, id=id, config=config)

		self.Connection = None
		self.ConnectionEvent = asyncio.Event(loop=app.Loop)
		self.ConnectionEvent.clear()

		self.PubSub = PubSub(app)
		self.Loop = app.Loop

		self._reconnect()


	def _reconnect(self):
		if self.Connection is not None:
			if not (self.Connection.is_closing or self.Connection.is_closed):
				self.Connection.close()
			self.Connection = None

		parameters = pika.URLParameters(self.Config['url'])
		if parameters.client_properties is None:
			parameters.client_properties = dict()
		parameters.client_properties['application'] = self.Config['appname']


		self.Connection = pika.adapters.asyncio_connection.AsyncioConnection(
			parameters=parameters,
			on_open_callback=self._on_connection_open,
			on_open_error_callback=self._on_connection_open_error,
			on_close_callback=self._on_connection_close
		)

	# Connection callbacks

	def _on_connection_open(self, connection):
		L.info("AMQP connected")
		self.ConnectionEvent.set()
		self.PubSub.publish("AMQPConnection.open!")

	def _on_connection_close(self, connection, code, reason):
		L.warning("AMQP disconnected ({}): {}".format(code, reason))
		self.ConnectionEvent.clear()
		self.PubSub.publish("AMQPConnection.close!")
		self.Loop.call_later(float(self.Config['reconnect_delay']), self._reconnect)


	def _on_connection_open_error(self, connection, error_message=None):
		L.error("AMQP error: {}".format(error_message if error_message is not None else 'Generic error'))
		self.ConnectionEvent.clear()
		self.PubSub.publish("AMQPConnection.close!")
		self.Loop.call_later(float(self.Config['reconnect_delay']), self._reconnect)
Пример #7
0
class ODBCConnection(Connection):
    # Caution: Providing incorrect connection configuration terminates the program with 'Abort trap 6'
    ConfigDefaults = {
        'host': 'localhost',
        'port': 3306,
        'user': '',
        'password': '',
        'driver': '',
        'db': '',
        'connect_timeout': 1,
        'reconnect_delay': 5.0,
        'output_queue_max_size': 10,
        'max_bulk_size': 2,
    }

    def __init__(self, app, id=None, config=None):
        super().__init__(app, id=id, config=config)
        self.ConnectionEvent = asyncio.Event(loop=app.Loop)
        self.ConnectionEvent.clear()

        self.PubSub = PubSub(app)
        self.Loop = app.Loop

        self._host = self.Config['host']
        self._port = int(self.Config['port'])
        self._user = self.Config['user']
        self._password = self.Config['password']
        self._connect_timeout = self.Config['connect_timeout']
        self._dsn = "Driver={};Database={}".format(self.Config['driver'],
                                                   self.Config['db'])
        self._reconnect_delay = self.Config['reconnect_delay']
        self._output_queue_max_size = self.Config['output_queue_max_size']
        self._max_bulk_size = int(self.Config['max_bulk_size'])

        self._conn_future = None
        self._connection_request = False
        self._pause = False

        # Subscription
        self._on_health_check('connection.open!')
        app.PubSub.subscribe("Application.stop!", self._on_application_stop)
        app.PubSub.subscribe("Application.tick!", self._on_health_check)
        app.PubSub.subscribe("ODBCConnection.pause!", self._on_pause)
        app.PubSub.subscribe("ODBCConnection.unpause!", self._on_unpause)

        self._output_queue = asyncio.Queue(loop=app.Loop)
        self._bulks = {}  # We have a "bulk" per query

    def _on_pause(self):
        self._pause = True

    def _on_unpause(self):
        self._pause = False

    def _flush(self):
        for query in self._bulks.keys():
            # Break if throttling was requested during the flush,
            # so that put_nowait doesn't raise
            if self._pause:
                break

            self._flush_bulk(query)

    def _flush_bulk(self, query):

        # Enqueue and thorttle if needed
        self._output_queue.put_nowait((query, self._bulks[query]))
        if self._output_queue.qsize() == self._output_queue_max_size:
            self.PubSub.publish("ODBCConnection.pause!", self)

        # Reset bulk
        self._bulks[query] = []

    def _on_application_stop(self, message_type, counter):
        self._flush()
        self._output_queue.put_nowait((None, None))

    def _on_health_check(self, message_type):
        if self._conn_future is not None:
            # Connection future exists

            if not self._conn_future.done():
                # Connection future didn't result yet
                # No sanitization needed
                return

            try:
                self._conn_future.result()
            except Exception:
                # Connection future threw an error
                L.exception("Unexpected connection future error")

            # Connection future already resulted (with or without exception)
            self._conn_future = None

        assert (self._conn_future is None)

        self._conn_future = asyncio.ensure_future(self._connection(),
                                                  loop=self.Loop)

    async def _connection(self):
        try:
            async with aioodbc.create_pool(
                    host=self._host,
                    port=self._port,
                    user=self._user,
                    password=self._password,
                    dsn=self._dsn,
                    connect_timeout=self._connect_timeout,
                    loop=self.Loop) as pool:
                self._conn_pool = pool
                self.ConnectionEvent.set()
                await self._loader()
        except BaseException:
            L.exception("Unexpected ODBC connection error")
            raise

    def acquire(self):
        assert (self._conn_pool is not None)
        return self._conn_pool.acquire()

    def consume(self, query, data):
        # Create a bulk for this query if doesn't yet exist
        if query not in self._bulks:
            self._bulks[query] = []

        # Add data to the query's bulk
        self._bulks[query].append(data)

        # Flush on _max_bulk_size
        if len(self._bulks[query]) >= self._max_bulk_size:
            self._flush_bulk(query)

    async def _loader(self):
        while True:
            query, data = await self._output_queue.get()
            if query is None:
                break

            if self._output_queue.qsize() == self._output_queue_max_size - 1:
                self.PubSub.publish("ODBCConnection.unpause!",
                                    self,
                                    asynchronously=True)
            async with self.acquire() as conn:
                async with conn.cursor() as cur:
                    await cur.executemany(query, data)
                    await conn.commit()