{"id":13849639,"url":"https://github.com/lyft/presto-gateway","last_synced_at":"2025-04-05T06:10:20.702Z","repository":{"id":33971518,"uuid":"149824037","full_name":"lyft/presto-gateway","owner":"lyft","description":"A load balancer / proxy / gateway for prestodb","archived":false,"fork":false,"pushed_at":"2024-07-25T21:06:53.000Z","size":2081,"stargazers_count":357,"open_issues_count":51,"forks_count":156,"subscribers_count":419,"default_branch":"master","last_synced_at":"2025-03-29T05:09:41.969Z","etag":null,"topics":["lyft"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lyft.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-09-21T22:18:02.000Z","updated_at":"2025-01-06T12:48:39.000Z","dependencies_parsed_at":"2024-01-18T09:57:27.742Z","dependency_job_id":"1ac41d4c-f307-4964-a105-a95301a54905","html_url":"https://github.com/lyft/presto-gateway","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lyft%2Fpresto-gateway","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lyft%2Fpresto-gateway/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lyft%2Fpresto-gateway/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lyft%2Fpresto-gateway/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lyft","download_url":"https://codeload.github.com/lyft/presto-gateway/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247294541,"owners_count":20915340,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["lyft"],"created_at":"2024-08-04T20:00:31.671Z","updated_at":"2025-04-05T06:10:20.677Z","avatar_url":"https://github.com/lyft.png","language":"JavaScript","funding_links":[],"categories":["Tools"],"sub_categories":[],"readme":"### NOTE: This is a legacy version of Trino Gateway. Please refer to https://github.com/trinodb/trino-gateway for active development and updates moving forward.\n\n\n# presto-gateway (outdated)\n\nA load balancer / proxy / gateway for presto compute engine.\n\nHow to setup a dev environment\n------------------------------\nStep 1: setup mysql. Install docker and run the below command when setting up first time:\n```$xslt\ndocker run -d -p 3306:3306  --name mysqldb -e MYSQL_ROOT_PASSWORD=root123 -e MYSQL_DATABASE=prestogateway -d mysql:5.7\n```\nNext time onwards, run the following commands to start mysqldb\n\n```$xslt\ndocker start mysqldb\n```\nNow open mysql console and install the presto-gateway tables:\n```$xslt\nmysql -uroot -proot123 -h127.0.0.1 -Dprestogateway\n\n```\nOnce logged in to mysql console, please run [gateway-ha-persistence.sql](/gateway-ha/src/main/resources/gateway-ha-persistence.sql) to populate the tables.\n\n### Build and run\n\nPlease note these steps have been verified with JDK 8 and 11. Higher versions of Java might run into unexpected issues. \n\nrun `mvn clean install` to build `presto-gateway`\n\nEdit the [config file](/gateway-ha/gateway-ha-config.yml) and update the mysql db information.\n\n```\ncd gateway-ha/target/\njava -jar gateway-ha-{{VERSION}}-jar-with-dependencies.jar server ../gateway-ha-config.yml\n```\n\nIf you encounter a `Failed to connect to JDBC URL` error, this may be due to newer versions of java disabling certain algorithms\nwhen using SSL/TLS, in particular `TLSv1` and `TLSv1.1`. This will cause `Bad handshake` errors when connecting to the MySQL server.\nTo enable `TLSv1` and `TLSv1.1` open the following file in any editor (`sudo` access needed):\n```\n/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home/jre/lib/security/java.security\n```\nSearch for `jdk.tls.disabledAlgorithms`, it should look something like this:\n```\njdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, RC4, DES, MD5withRSA, \\\n    DH keySize \u003c 1024, EC keySize \u003c 224, 3DES_EDE_CBC, anon, NULL, \\\n    include jdk.disabled.namedCurves\n```\nRemove `TLSv1, TLSv1.1` and redo the above steps to build and run `presto-gateway`.\n\nNow you can access load balanced presto at localhost:8080 port. We will refer to this as `prestogateway.lyft.com`\n\nIf you see test failures while building `presto-gateway` or in an IDE, please  run `mvn process-classes` to instrument javalite models\nwhich are used by the tests . Ref [javalite-examples](https://github.com/javalite/javalite-examples/tree/master/simple-example#instrumentation) for more details.\n\n## Gateway API\n\n### Add or update a backend\n```$xslt\ncurl -X POST http://localhost:8080/entity?entityType=GATEWAY_BACKEND \\\n -d '{  \"name\": \"presto1\", \\\n        \"proxyTo\": \"http://presto1.lyft.com\",\\\n        \"active\": true, \\\n        \"routingGroup\": \"adhoc\" \\\n    }'\n\ncurl -X POST http://localhost:8080/entity?entityType=GATEWAY_BACKEND \\\n -d '{  \"name\": \"presto2\", \\\n        \"proxyTo\": \"http://presto2.lyft.com\",\\\n        \"active\": true, \\\n        \"routingGroup\": \"adhoc\" \\\n    }'\n\n```\nIf the backend URL is different from the `proxyTo` URL (for example if they are internal vs. external hostnames). You can use the optional `externalUrl` field to override the link in the Active Backends page.\n```$xslt\ncurl -X POST http://localhost:8080/entity?entityType=GATEWAY_BACKEND \\\n -d '{  \"name\": \"presto1\", \\ \n        \"proxyTo\": \"http://presto1.lyft.com\",\\\n        \"active\": true, \\\n        \"routingGroup\": \"adhoc\" \\\n        \"externalUrl\": \"http://presto1-external.lyft.com\",\\\n    }'\n\ncurl -X POST http://localhost:8080/entity?entityType=GATEWAY_BACKEND \\\n -d '{  \"name\": \"presto2\", \\ \n        \"proxyTo\": \"http://presto2.lyft.com\",\\\n        \"active\": true, \\\n        \"routingGroup\": \"adhoc\" \\\n        \"externalUrl\": \"http://presto2-external.lyft.com\",\\\n    }'\n\n```\n\n\n### Get all backends behind the gateway\n```$xslt\ncurl -X GET http://localhost:8080/entity/GATEWAY_BACKEND\n[\n    {\n        \"active\": true,\n        \"name\": \"presto1\",\n        \"proxyTo\": \"http://presto1.lyft.com\",\n        \"routingGroup\": \"adhoc\"\n    },\n    {\n        \"active\": true,\n        \"name\": \"presto2\",\n        \"proxyTo\": \"http://presto2.lyft.com\",\n        \"routingGroup\": \"adhoc\"\n    }\n]\n```\n\n### Delete a backend from the gateway\n\n```$xslt\ncurl -X POST -d \"presto3\" http://localhost:8080/gateway/backend/modify/delete\n```\n\n### Deactivate a backend\n```$xslt\ncurl -X POST http://localhost:8080/gateway/backend/deactivate/presto2\n```\n\n### Get all active backend behind the Gateway\n\n`curl -X GET http://localhost:8080/gateway/backend/active | python -m json.tool`\n```\n    [{\n        \"active\": true,\n        \"name\": \"presto1\",\n        \"proxyTo\": \"http://presto1.lyft.com\",\n        \"routingGroup\": \"adhoc\"\n    }]\n```\n\n### Activate a backend\n`curl -X POST http://localhost:8080/gateway/backend/activate/presto2`\n\n\n### Query History UI - check query plans etc.\nPrestoGateway records history of recent queries and displays links to check query details page in respective presto cluster.\n![prestogateway.lyft.com](/docs/assets/prestogateway_query_history.png)\n\n### Gateway Admin UI - add and modify backend information\nThe Gateway admin page is used to configure the gateway to multiple backends. Existing backend information can also be modified using the same.\n![prestogateway.lyft.com/entity](/docs/assets/prestogateway_ha_admin.png)\n\n## Resource Groups API\n\nFor resource group and selector apis, we can now specify a query parameter with the request supporting multiple presto databases for different presto backends. This allows a user to configure a db for every presto backend with their own resource groups and selector tables. To use this, just specify the query parameter ?useSchema=\u003cschemaname\u003e to the request. Example, to list all resource groups,\n ```$xslt\ncurl -X GET http://localhost:8080/presto/resourcegroup/read/{INSERT_ID_HERE}?useSchema=newdatabasename\n```\n \n### Add a resource group\nTo add a single resource group, specify all relevant fields in the body. Resource group id should not be specified since the database should autoincrement it.\n```$xslt\ncurl -X POST http://localhost:8080/presto/resourcegroup/create \\\n -d '{  \n        \"name\": \"resourcegroup1\", \\\n        \"softMemoryLimit\": \"100%\", \\\n        \"maxQueued\": 100, \\\n        \"softConcurrencyLimit\": 100, \\\n        \"hardConcurrencyLimit\": 100, \\\n        \"schedulingPolicy\": null, \\\n        \"schedulingWeight\": null, \\\n        \"jmxExport\": null, \\\n        \"softCpuLimit\": null, \\\n        \"hardCpuLimit\": null, \\\n        \"parent\": null, \\\n        \"environment\": \"test\" \\\n    }'\n```\n\n### Get existing resource group(s)\nIf no resourceGroupId (type long) is specified, then all existing resource groups are fetched. \n```$xslt\ncurl -X GET http://localhost:8080/presto/resourcegroup/read/{INSERT_ID_HERE}\n```\n\n### Update a resource group\nSpecify all columns in the body, which will overwrite properties for the resource group with that specific resourceGroupId.\n```$xslt\ncurl -X POST http://localhost:8080/presto/resourcegroup/update \\\n -d '{  \"resourceGroupId\": 1, \\\n        \"name\": \"resourcegroup_updated\", \\\n        \"softMemoryLimit\": \"80%\", \\\n        \"maxQueued\": 50, \\\n        \"softConcurrencyLimit\": 40, \\\n        \"hardConcurrencyLimit\": 60, \\\n        \"schedulingPolicy\": null, \\\n        \"schedulingWeight\": null, \\\n        \"jmxExport\": null, \\\n        \"softCpuLimit\": null, \\\n        \"hardCpuLimit\": null, \\\n        \"parent\": null, \\\n        \"environment\": \"test\" \\\n    }'\n```\n\n### Delete a resource group\nTo delete a resource group, specify the corresponding resourceGroupId (type long).\n```$xslt\ncurl -X POST http://localhost:8080/presto/resourcegroup/delete/{INSERT_ID_HERE}\n```\n\n### Add a selector\nTo add a single selector, specify all relevant fields in the body. Resource group id should not be specified since the database should autoincrement it.\n```$xslt\ncurl -X POST http://localhost:8080/presto/selector/create \\\n -d '{  \n        \"priority\": 1, \\\n        \"userRegex\": \"selector1\", \\\n        \"sourceRegex\": \"resourcegroup1\", \\\n        \"queryType\": \"insert\" \\\n     }'\n```\n\n### Get existing selectors(s)\nIf no resourceGroupId (type long) is specified, then all existing selectors are fetched. \n```$xslt\ncurl -X GET http://localhost:8080/presto/selector/read/{INSERT_ID_HERE}\n```\n\n### Update a selector\nTo update a selector, the existing selector must be specified with all relevant fields under \"current\". The updated version of that selector is specified under \"update\", with all relevant fields included. If the selector under \"current\" does not exist, a new selector will be created with the details under \"update\". Both \"current\" and \"update\" must be included to update a selector. \n```$xslt\ncurl -X POST http://localhost:8080/presto/selector/update \\\n -d '{  \"current\": {\n            \"resourceGroupId\": 1, \\\n            \"priority\": 1, \\\n            \"userRegex\": \"selector1\", \\\n            \"sourceRegex\": \"resourcegroup1\", \\\n            \"queryType\": \"insert\" \\\n        },\n        \"update\":  {\n            \"resourceGroupId\": 1, \\\n            \"priority\": 2, \\\n            \"userRegex\": \"selector1_updated\", \\\n            \"sourceRegex\": \"resourcegroup1\", \\\n            \"queryType\": null \\\n        }\n}'\n```\n\n### Delete a selector\nTo delete a selector, specify all relevant fields in the body.\n```$xslt\ncurl -X POST http://localhost:8080/presto/selector/delete \\\n -d '{  \"resourceGroupId\": 1, \\\n        \"priority\": 2, \\\n        \"userRegex\": \"selector1_updated\", \\\n        \"sourceRegex\": \"resourcegroup1\", \\\n        \"queryType\": null \\\n     }'\n```\n\n### Add a global property\nTo add a single global property, specify all relevant fields in the body.\n```$xslt\ncurl -X POST http://localhost:8080/presto/globalproperty/create \\\n -d '{\n        \"name\": \"cpu_quota_period\", \\\n        \"value\": \"1h\" \\\n     }'\n```\n\n### Get existing global properties\nIf no name (type String) is specified, then all existing global properties are fetched. \n```$xslt\ncurl -X GET http://localhost:8080/presto/globalproperty/read/{INSERT_NAME_HERE}\n```\n\n### Update a global property\nSpecify all columns in the body, which will overwrite properties for the global property with that specific name.\n```$xslt\ncurl -X POST http://localhost:8080/presto/globalproperty/update \\\n -d '{\n        \"name\": \"cpu_quota_period\", \\\n        \"value\": \"2h\" \\\n     }'\n```\n\n### Delete a global property\nTo delete a global property, specify the corresponding name (type String).\n```$xslt\ncurl -X POST http://localhost:8080/presto/globalproperty/delete/{INSERT_NAME_HERE}\n```\n\n## Graceful shutdown\nPresto gateway supports graceful shutdown of Presto clusters. Even when a cluster is deactivated, any submitted query states can still be retrieved based on the Query ID.\n\nTo graceful shutdown a Presto cluster without query losses, the steps are:\n1. Set the backend to deactivate state, this prevents any new incoming queries from getting assigned to the backend.\n2. Poll the Presto backend coorinator URL until the queued query count and the running query count both hit 0.\n3. Terminate the Presto Coordinator \u0026 Worker Java process.\n\n\nTo gracefully shutdown a single worker process, see [this](https://trino.io/docs/current/admin/graceful-shutdown.html) for the operations.\n\n## Routing Rules Engine\nBy default, presto-gateway reads the `X-Trino-Routing-Group` request header to route requests.\nIf this header is not specified, requests are sent to default routing group (adhoc).\n\nThe routing rules engine feature enables you to write custom logic to route requests based on the request info such as any of the [request headers](https://trino.io/docs/current/develop/client-protocol.html#client-request-headers).\nRouting rules are separated from presto-gateway application code to a configuration file, allowing for dynamic rule changes.\n\n### Defining your routing rules\nTo express and fire routing rules, we use the [easy-rules](https://github.com/j-easy/easy-rules) engine. These rules should be stored in a YAML file.\nRules consist of a name, description, condition, and list of actions. If the condition of a particular rule evaluates to true, its actions are fired.\n```yaml\n---\nname: \"airflow\"\ndescription: \"if query from airflow, route to etl group\"\ncondition: \"request.getHeader(\\\"X-Trino-Source\\\") == \\\"airflow\\\"\"\nactions:\n  - \"result.put(\\\"routingGroup\\\", \\\"etl\\\")\"\n---\nname: \"airflow special\"\ndescription: \"if query from airflow with special label, route to etl-special group\"\ncondition: \"request.getHeader(\\\"X-Trino-Source\\\") == \\\"airflow\\\" \u0026\u0026 request.getHeader(\\\"X-Trino-Client-Tags\\\") contains \\\"label=special\\\"\"\nactions:\n  - \"result.put(\\\"routingGroup\\\", \\\"etl-special\\\")\"\n```\nIn the condition, you can access the methods of a [HttpServletRequest](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html) object called `request`.\nThere should be at least one action of the form `result.put(\\\"routingGroup\\\", \\\"foo\\\")` which says that if a request satisfies the condition, it should be routed to `foo`.\n\nThe condition and actions are written in [MVEL](http://mvel.documentnode.com/), an expression language with Java-like syntax.\nIn most cases, users can write their conditions/actions in Java syntax and expect it to work. There are some MVEL-specific operators that could be useful though.\nFor example, instead of doing a null-check before accessing the `String.contains` method like this:\n```yaml\ncondition: \"request.getHeader(\\\"X-Trino-Client-Tags\\\") != null \u0026\u0026 request.getHeader(\\\"X-Trino-Client-Tags\\\").contains(\\\"label=foo\\\")\"\n```\nYou can use the `contains` operator\n```yaml\ncondition: \"request.getHeader(\\\"X-Trino-Client-Tags\\\") contains \\\"label=foo\\\"\"\n```\nIf no rules match, then request is routed to adhoc.\n\n### Execution of Rules\nAll rules whose conditions are satisfied will fire. For example, in the \"airflow\" and \"airflow special\" example rules given above, a query with source `airflow` and label `special`\nwill satisfy both rules. The `routingGroup` is set to `etl` and then to `etl-special` because of the order in which the rules of defined.\nIf we swap the order of the rules, then we would possibly get `etl` instead, which is undesirable.\n\nOne could solve this by writing the rules such that they're atomic (any query will match exactly one rule). For example we can change the first rule to\n```yaml\n---\nname: \"airflow\"\ndescription: \"if query from airflow, route to etl group\"\ncondition: \"request.getHeader(\\\"X-Trino-Source\\\") == \\\"airflow\\\" \u0026\u0026 request.getHeader(\\\"X-Trino-Client-Tags\\\") == null\"\nactions:\n  - \"result.put(\\\"routingGroup\\\", \\\"etl\\\")\"\n---\n```\nThis could be hard to maintain as we add more rules. To have better control over the execution of rules, we could use rule priorities and composite rules.\nOverall, with priorities, composite rules, and the constructs that MVEL support, you should likely be able to express your routing logic.\n\n#### Rule Priority\nWe can assign an integer value `priority` to a rule. The lower this integer is, the earlier it will fire.\nIf the priority is not specified, the priority is defaulted to INT\\_MAX.\nWe can add priorities to our airflow and airflow special rule like so:\n```yaml\n---\nname: \"airflow\"\ndescription: \"if query from airflow, route to etl group\"\npriority: 0\ncondition: \"request.getHeader(\\\"X-Trino-Source\\\") == \\\"airflow\\\"\"\nactions:\n  - \"result.put(\\\"routingGroup\\\", \\\"etl\\\")\"\n---\nname: \"airflow special\"\ndescription: \"if query from airflow with special label, route to etl-special group\"\npriority: 1\ncondition: \"request.getHeader(\\\"X-Trino-Source\\\") == \\\"airflow\\\" \u0026\u0026 request.getHeader(\\\"X-Trino-Client-Tags\\\") contains \\\"label=special\\\"\"\nactions:\n  - \"result.put(\\\"routingGroup\\\", \\\"etl-special\\\")\"\n```\nNote that both rules will still fire. The difference is that we've guaranteed that the first rule (priority 0) is fired before the second rule (priority 1). Thus `routingGroup`\nis set to `etl` and then to `etl-special`, so the `routingGroup` will always be `etl-special` in the end.\n\nAbove, the more specific rules have less priority since we want them to be the last to set `routingGroup`. This is a little counterintuitive.\nTo further control the execution of rules, for example to have only one rule fire, we can use composite rules.\n\n##### Composite Rules\nFirst, please refer to easy-rule composite rules docs: https://github.com/j-easy/easy-rules/wiki/defining-rules#composite-rules\n\nAbove, we saw how to control the order of rule execution using priorities. In addition to this, we could have only the first rule matched to be\nfired (the highest priority one) and the rest ignored. We can use `ActivationRuleGroup` to achieve this.\n```yaml\n---\nname: \"airflow rule group\"\ndescription: \"routing rules for query from airflow\"\ncompositeRuleType: \"ActivationRuleGroup\"\ncomposingRules:\n  - name: \"airflow special\"\n    description: \"if query from airflow with special label, route to etl-special group\"\n    priority: 0\n    condition: \"request.getHeader(\\\"X-Trino-Source\\\") == \\\"airflow\\\" \u0026\u0026 request.getHeader(\\\"X-Trino-Client-Tags\\\") contains \\\"label=special\\\"\"\n    actions:\n      - \"result.put(\\\"routingGroup\\\", \\\"etl-special\\\")\"\n  - name: \"airflow\"\n    description: \"if query from airflow, route to etl group\"\n    priority: 1\n    condition: \"request.getHeader(\\\"X-Trino-Source\\\") == \\\"airflow\\\"\"\n    actions:\n      - \"result.put(\\\"routingGroup\\\", \\\"etl\\\")\"\n```\nNote that the priorities have switched. The more specific rule has a higher priority, since we want it to be fired first.\nA query coming from airflow with special label is matched to the \"airflow special\" rule first, since it's higher priority,\nand the second rule is ignored. A query coming from airflow with no labels does not match the first rule, and is then tested and matched to the second rule.\n\nWe can also use `ConditionalRuleGroup` and `ActivationRuleGroup` to implement an if/else workflow.\nThe following logic in pseudocode:\n```\nif source == \"airflow\":\n  if clientTags[\"label\"] == \"foo\":\n    return \"etl-foo\"\n  else if clientTags[\"label\"] = \"bar\":\n    return \"etl-bar\"\n  else\n    return \"etl\"\n```\nCan be implemented with these rules:\n```yaml\nname: \"airflow rule group\"\ndescription: \"routing rules for query from airflow\"\ncompositeRuleType: \"ConditionalRuleGroup\"\ncomposingRules:\n  - name: \"main condition\"\n    description: \"source is airflow\"\n    priority: 0 # rule with the highest priority acts as main condition\n    condition: \"request.getHeader(\\\"X-Trino-Source\\\") == \\\"airflow\\\"\"\n    actions:\n      - \"\"\n  - name: \"airflow subrules\"\n    compositeRuleType: \"ActivationRuleGroup\" # use ActivationRuleGroup to simulate if/else\n    composingRules:\n      - name: \"label foo\"\n        description: \"label client tag is foo\"\n        priority: 0\n        condition: \"request.getHeader(\\\"X-Trino-Client-Tags\\\") contains \\\"label=foo\\\"\"\n        actions:\n          - \"result.put(\\\"routingGroup\\\", \\\"etl-foo\\\")\"\n      - name: \"label bar\"\n        description: \"label client tag is bar\"\n        priority: 0\n        condition: \"request.getHeader(\\\"X-Trino-Client-Tags\\\") contains \\\"label=bar\\\"\"\n        actions:\n          - \"result.put(\\\"routingGroup\\\", \\\"etl-bar\\\")\"\n      - name: \"airflow default\"\n        description: \"airflow queries default to etl\"\n        condition: \"true\"\n        actions:\n          - \"result.put(\\\"routingGroup\\\", \\\"etl\\\")\"\n```\n\n##### If statements (MVEL Flow Control)\nAbove, we saw how we can use `ConditionalRuleGroup` and `ActivationRuleGroup` to implement and `if/else` workflow.\nWe could also take advantage of the fact that MVEL supports `if` statements and other flow control (loops, etc).\nThe following logic in pseudocode:\n```\nif source == \"airflow\":\n  if clientTags[\"label\"] == \"foo\":\n    return \"etl-foo\"\n  else if clientTags[\"label\"] = \"bar\":\n    return \"etl-bar\"\n  else\n    return \"etl\"\n```\nCan be implemented with these rules:\n```yaml\n---\nname: \"airflow rules\"\ndescription: \"if query from airflow\"\ncondition: \"request.getHeader(\\\"X-Trino-Source\\\") == \\\"airflow\\\"\"\nactions:\n  - \"if (request.getHeader(\\\"X-Trino-Client-Tags\\\") contains \\\"label=foo\\\") {\n      result.put(\\\"routingGroup\\\", \\\"etl-foo\\\")\n    }\n    else \"if (request.getHeader(\\\"X-Trino-Client-Tags\\\") contains \\\"label=bar\\\") {\n      result.put(\\\"routingGroup\\\", \\\"etl-bar\\\")\n    }\n    else {\n      result.put(\\\"routingGroup\\\", \\\"etl\\\")\n    }\"\n```\n\n### Enabling routing rules engine\nTo enable routing rules engine, find the following lines in `gateway-ha-config.yml`.\nSet `rulesEngineEnabled` to True and `rulesConfigPath` to the path to your rules config file.\n```\nroutingRules:\n  rulesEngineEnabled: true\n  rulesConfigPath: \"src/test/resources/rules/routing_rules.yml\" # replace with path to your rules config file\n```\n\n\n## Contributing\n\nWant to help build Presto Gateway? Check out our [contributing documentation](CONTRIBUTING.md)\n\nReferences :sparkles:\n--------------------\n[Lyft](https://eng.lyft.com/presto-infrastructure-at-lyft-b10adb9db01)\n\n[Pinterest](https://medium.com/pinterest-engineering/presto-at-pinterest-a8bda7515e52)\n    \n[Zomato](https://www.zomato.com/blog/powering-data-analytics-with-trino)\n\n[Shopify](https://shopify.engineering/faster-trino-query-execution-infrastructure)\n    \n[Electronic Arts](https://www.youtube.com/watch?v=-5mlZGjt6H4)\n    \n{{Your org here}}\n    \n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flyft%2Fpresto-gateway","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flyft%2Fpresto-gateway","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flyft%2Fpresto-gateway/lists"}