项目作者: geronimo-iia

项目描述 :
Airflow Indexima Hook and Operator
高级语言: Python
项目地址: git://github.com/geronimo-iia/airflow-indexima.git
创建时间: 2019-11-27T16:44:58Z
项目社区:https://github.com/geronimo-iia/airflow-indexima

开源协议:Other

下载


airflow-indexima

Unix Build Status
PyPI Version
PyPI License

Versions following Semantic Versioning

Overview

Indexima Airflow integration based on pyhive.

This project is used in our prod environment with success.
As it a young project, take care of change, any help is welcome :)

Setup

Requirements

  • Python 3.6+

Installation

Install this library directly into an activated virtual environment:

  1. $ pip install airflow-indexima

or add it to your Poetry project:

  1. $ poetry add airflow-indexima

or you could use it as an Airflow plugin

Usage

After installation, the package can imported:

  1. $ python
  2. >>> import airflow_indexima
  3. >>> airflow_indexima.__version__

See Api documentation

a simple query

  1. from airflow_indexima.operators import IndeximaQueryRunnerOperator
  2. ...
  3. with dag:
  4. ...
  5. op = IndeximaQueryRunnerOperator(
  6. task_id = 'my-task-id',
  7. sql_query= 'DELETE FROM Client WHERE GRPD = 1',
  8. indexima_conn_id='my-indexima-connection'
  9. )
  10. ...

a load into indexima

  1. from airflow_indexima.operators.indexima import IndeximaLoadDataOperator
  2. ...
  3. with dag:
  4. ...
  5. op = IndeximaLoadDataOperator(
  6. task_id = 'my-task-id',
  7. indexima_conn_id='my-indexima-connection',
  8. target_table='Client',
  9. source_select_query='select * from dsi.client',
  10. truncate=True,
  11. load_path_uri='jdbc:redshift://my-private-instance.com:5439/db_client?ssl=true&user=airflow-user&password=XXXXXXXX'
  12. )
  13. ...

get load path uri from Connection

In order to get jdbc uri from an Airflow Connection, you could use:

  • get_redshift_load_path_uri
  • get_postgresql_load_path_uri

from module airflow_indexima.uri

Both method have this profile: Callable[[str, Optional[ConnectionDecorator]], str]

Example:

  1. get_postgresql_load_path_uri(connection_id='my_conn')
  2. >> 'jdbc:postgresql://my-db:5432/db_client?ssl=true&user=airflow-user&password=XXXXXXXX'

Indexima Connection

Authentication

PyHive supported authentication mode:

  • ‘NONE’: needs a username without password
  • ‘CUSTOM’: needs a username and password (default mode)
  • ‘LDAP’: needs a username and password
  • ‘KERBEROS’: need a kerberos service name
  • ‘NOSASL’: corresponds to hive.server2.authentication=NOSASL in hive-site.xml

Configuration

You could set those parameters:

  • host (str): The host to connect to.
  • port (int): The (TCP) port to connect to.
  • timeout_seconds ([int]): define the socket timeout in second (default None)
  • socket_keepalive ([bool]): enable TCP keepalive, default false.
  • auth (str): authentication mode
  • username ([str]): username to login
  • password ([str]): password to login
  • kerberos_service_name ([str]): kerberos service name

host, port, username and password came from airflow Connection configuration.

timeout_seconds, socket_keepalive, auth and kerberos_service_name parameters can came from:

  1. attribut on Hook/Operator class
  2. Airflow Connection in extra parameter, like this:
    1. '{"auth": "CUSTOM", "timeout_seconds": 90, "socket_keepalive": true}'

Setted attribut override airflow connection configuration.

You could add a decorator function in order to post process Connection before usage.
This decorator will be executed after connection configuration (see next section).

customize Connection credential access

If you use another backend to store your password (like AWS SSM), you could define a decorator
and use it as a function in your dag.

  1. from airflow.models import Connection
  2. from airflow import DAG
  3. from airdlow_indexima.uri import define_load_path_factory, get_redshift_load_path_uri
  4. def my_decorator(conn:Connection) -> Connection:
  5. # conn instance will be not shared, and use only on connection request
  6. conn.password = get_ssm_parameter(param_name=f'{conn.conn_id}.{con.login}')
  7. return conn
  8. dag = DAG(
  9. dag_id='my_dag',
  10. user_defined_macros={
  11. # we define a macro get_load_path_uri
  12. 'get_load_path_uri': define_load_path_factory(
  13. conn_id='my-redshift-connection',
  14. decorator=my_decorator,
  15. factory=get_redshift_load_path_uri)
  16. },
  17. ...
  18. )
  19. with dag:
  20. ...
  21. op = IndeximaLoadDataOperator(
  22. task_id = 'my-task-id',
  23. indexima_conn_id='my-indexima-connection',
  24. target_table='Client',
  25. source_select_query='select * from dsi.client',
  26. truncate=True,
  27. load_path_uri='{{ get_load_path_uri() }}'
  28. )
  29. ...

a Connection decorator must follow this type: ConnectionDecorator = Callable[[Connection], Connection]

define_load_path_factory is a function which take:

  • a connnection identifier
  • a decorator ConnectionDecorator
  • an uri factory UriGeneratorFactory = Callable[[str, Optional[ConnectionDecorator]], str]

and return a function with no argument which can be called as a macro in dag’s operator.

Optional connection parameters

On each operator you could set this member:

  • auth (Optional[str]): authentication mode (default: {‘CUSTOM’})
  • kerberos_service_name (Optional[str]): optional kerberos service name
  • timeout_seconds (Optional[Union[int, datetime.timedelta]]): define the socket timeout in second
    1. (could be an int or a timedelta)
  • socket_keepalive (Optional[bool]): enable TCP keepalive.

Note:

  • if execution_timeout is set, it will be used as default value for timeout_seconds.

Production Feedback

In production, you could have few strange behaviour like those that we have meet.

“TSocket read 0 bytes”

You could fine this issue https://github.com/dropbox/PyHive/issues/240 on long load query running.

Try this in sequence:

  1. check your operator configuration, and set timeout_seconds member to 3600 second for example.
    You could have a different behaviour when running a dag with/without airflow context in docker container.
  2. if your facing a broken pipe, after 300s, and you have an AWS NLB V2 :
    Read again network-load-balancers, and focus on this:

    Elastic Load Balancing sets the idle timeout value for TCP flows to 350 seconds. You cannot modify this value. For TCP listeners, clients or targets can use TCP keepalive packets to reset the idle timeout. TCP keepalive packets are not supported for TLS listeners.

    We have tried for you the “socket_keep_alive”, and it did not work at all.
    Our solution was to remove our NLB and use a simple dns A field on indexima master.

“utf-8” or could not read byte …

Be very welcome to add { "serialization.encoding": "utf-8"} in hive_configuration member of IndeximaHook.

This setting is set in IndeximaHook.init, may you override it ?

Playing Airflow without Airflow Server

When I was trying many little things and deals with hive stuff, i wrote a single script that help me a lot.

Feel free to use it (or not) to set your dag by yourself:

  1. import os
  2. import datetime
  3. from airflow.hooks.base_hook import BaseHook
  4. from airflow import DAG
  5. from airflow_indexima.operators.indexima import IndeximaLoadDataOperator
  6. # here we create our Airflow Connection
  7. os.environ['AIRFLOW_CONN_INDEXIMA_ID'] = 'hive://my-user:my-password@my-server:10000/default'
  8. conn = BaseHook.get_connection('indexima_id')
  9. dag = DAG(
  10. dag_id='my_dag',
  11. default_args={
  12. 'start_date': datetime.datetime(year=2019, month=12, day=1),
  13. 'depends_on_past': False,
  14. 'email_on_failure': False,
  15. 'email': [],
  16. },
  17. )
  18. with dag:
  19. load_operator = IndeximaLoadDataOperator(
  20. task_id='my_task',
  21. indexima_conn_id='indexima_id',
  22. target_table='my_table',
  23. source_select_query=(
  24. "select * from source_table where "
  25. "creation_date_tms between '2019-11-30T00:00:00+00:00' and '2019-11-30T12:59:59.000999+00:00'"
  26. ),
  27. truncate=True,
  28. truncate_sql=(
  29. "DELETE FROM my_table WHERE "
  30. "creation_date_tms between '2019-11-30T00:00:00+00:00' and '2019-11-30T12:59:59.000999+00:00'"
  31. ),
  32. load_path_uri='jdbc:postgresql://myserver:5439/db_common?user=etl_user&password=a_strong_password&ssl=true',
  33. retries=2,
  34. execution_timeout=datetime.timedelta(hours=3),
  35. sla=datetime.timedelta(hours=1, minutes=30),
  36. )
  37. # here we run the dag
  38. load_operator.execute(context={})
  39. del os.environ['AIRFLOW_CONN_INDEXIMA_ID']

License

The MIT License (MIT)

Contributing

See Contributing

Thanks

Thanks to @bartosz25 for his help with hive connection details…