├── .github ├── CODEOWNERS └── workflows │ └── main.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CODE-OF-CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── NOTICE ├── README.md ├── pom.xml ├── shunting-yard-binary ├── pom.xml └── src │ └── main │ ├── assembly │ └── shunting-yard-package.xml │ ├── resources │ ├── log4j.xml │ └── shunting-yard-minimal.yml.template │ └── scripts │ └── replicator.sh ├── shunting-yard-common ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── expediagroup │ │ └── shuntingyard │ │ └── common │ │ ├── Preconditions.java │ │ ├── Property.java │ │ ├── PropertyUtils.java │ │ ├── ShuntingYardException.java │ │ ├── io │ │ └── jackson │ │ │ ├── DummyMapEntry.java │ │ │ └── ThriftSerDeUtils.java │ │ ├── messaging │ │ ├── Message.java │ │ ├── MessageReaderFactory.java │ │ ├── MessageTask.java │ │ └── MessageTaskFactory.java │ │ ├── metrics │ │ ├── HiveMetricsHelper.java │ │ └── MetricsConstant.java │ │ └── receiver │ │ └── thrift │ │ ├── ExpressionBuilder.java │ │ └── ThriftListenerUtils.java │ └── test │ ├── data │ └── com │ │ └── expediagroup │ │ └── shuntingyard │ │ └── common │ │ └── io │ │ └── jackson │ │ └── DeserializerWithJsonInputTest │ │ ├── add_partition.json │ │ ├── alter_partition.json │ │ ├── alter_table.json │ │ ├── create_table.json │ │ ├── drop_partition.json │ │ ├── drop_table.json │ │ └── insert_table.json │ └── java │ └── com │ └── expediagroup │ └── shuntingyard │ └── common │ ├── PreconditionsTest.java │ ├── PropertyUtilsTest.java │ ├── io │ └── jackson │ │ ├── InvalidTBase.java │ │ └── ThriftSerDeUtilsTest.java │ ├── messaging │ ├── BogusMessageReaderFactory.java │ ├── MessageReaderFactoryTest.java │ ├── MessageTest.java │ ├── NotReallyAMessageReaderFactory.java │ └── SomeMessageReaderFactory.java │ ├── metrics │ └── HiveMetricsHelperTest.java │ └── receiver │ └── thrift │ └── ThriftListenerUtilsTest.java ├── shunting-yard-receiver ├── pom.xml └── shunting-yard-receiver-sqs │ ├── pom.xml │ └── src │ ├── main │ └── java │ │ └── com │ │ └── expediagroup │ │ └── shuntingyard │ │ └── receiver │ │ └── sqs │ │ ├── SqsConsumerProperty.java │ │ ├── SqsReceiverUtils.java │ │ ├── aws │ │ └── ConfigurationAwsCredentialsProvider.java │ │ └── messaging │ │ └── SqsMessageReaderFactory.java │ └── test │ └── java │ └── com │ └── expediagroup │ └── shuntingyard │ └── receiver │ └── sqs │ ├── SqsConsumerPropertyTest.java │ ├── SqsReceiverUtilsTest.java │ └── aws │ └── ConfigurationAwsCredentialsProviderTest.java ├── shunting-yard-replicator ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── expediagroup │ │ └── shuntingyard │ │ └── replicator │ │ ├── exec │ │ ├── ConfigFileValidationApplicationListener.java │ │ ├── ConfigFileValidationException.java │ │ ├── ConfigFileValidator.java │ │ ├── Constants.java │ │ ├── MetaStoreEventReplication.java │ │ ├── MetaStoreEventReplicationHelp.java │ │ ├── app │ │ │ ├── ConfigurationVariables.java │ │ │ └── ReplicationRunner.java │ │ ├── conf │ │ │ ├── EventReceiverConfiguration.java │ │ │ ├── OrphanedDataStrategyConfiguration.java │ │ │ ├── ReplicaCatalog.java │ │ │ ├── ShuntingYardTableReplicationsMap.java │ │ │ ├── SourceCatalog.java │ │ │ ├── SourceTableFilter.java │ │ │ └── ct │ │ │ │ ├── ShuntingYardTableReplication.java │ │ │ │ └── ShuntingYardTableReplications.java │ │ ├── context │ │ │ └── CommonBeans.java │ │ ├── event │ │ │ ├── MetaStoreEvent.java │ │ │ └── aggregation │ │ │ │ ├── DefaultMetaStoreEventAggregator.java │ │ │ │ ├── DefaultMetaStoreEventCompactor.java │ │ │ │ ├── EventMerger.java │ │ │ │ └── MetaStoreEventAggregator.java │ │ ├── external │ │ │ ├── CircusTrainConfig.java │ │ │ └── Marshaller.java │ │ ├── launcher │ │ │ └── CircusTrainRunner.java │ │ ├── messaging │ │ │ ├── AggregatingMetaStoreEventReader.java │ │ │ ├── FilteringMessageReader.java │ │ │ ├── MessageReaderAdapter.java │ │ │ └── MetaStoreEventReader.java │ │ └── receiver │ │ │ ├── CircusTrainReplicationMetaStoreEventListener.java │ │ │ ├── Context.java │ │ │ ├── ContextFactory.java │ │ │ ├── ReplicationMetaStoreEventListener.java │ │ │ └── TableSelector.java │ │ ├── metastore │ │ └── DefaultMetaStoreClientSupplier.java │ │ ├── util │ │ └── TableDatabaseNameJoiner.java │ │ └── yaml │ │ ├── AdvancedPropertyUtils.java │ │ ├── AdvancedRepresenter.java │ │ └── YamlFactory.java │ └── test │ ├── java │ └── com │ │ └── expediagroup │ │ └── shuntingyard │ │ └── replicator │ │ ├── exec │ │ ├── ConfigFileValidationApplicationListenerTest.java │ │ ├── ConfigFileValidatorTest.java │ │ ├── app │ │ │ ├── ConfigurationVariablesTest.java │ │ │ └── ReplicationRunnerTest.java │ │ ├── conf │ │ │ ├── ShuntingYardReplicationsMapTest.java │ │ │ └── ct │ │ │ │ └── ShuntingYardTableReplicationTest.java │ │ ├── event │ │ │ ├── MetaStoreEventTest.java │ │ │ └── aggregation │ │ │ │ ├── DefaultMetaStoreEventAggregatorTest.java │ │ │ │ ├── DefaultMetaStoreEventCompactorTest.java │ │ │ │ └── EventMergerTest.java │ │ ├── external │ │ │ ├── CircusTrainConfigTest.java │ │ │ └── MarshallerTest.java │ │ ├── messaging │ │ │ ├── AggregatingMetaStoreEventReaderTest.java │ │ │ ├── FilteringMessageReaderTest.java │ │ │ └── MessageReaderAdapterTest.java │ │ └── receiver │ │ │ ├── CircusTrainReplicationMetaStoreEventListenerTest.java │ │ │ ├── ContextFactoryTest.java │ │ │ └── TableSelectorTest.java │ │ ├── metastore │ │ └── DefaultMetaStoreClientSupplierTest.java │ │ ├── util │ │ └── TableDatabaseNameJoinerTest.java │ │ └── yaml │ │ ├── AdvancedPropertyUtilsTest.java │ │ ├── AdvancedRepresenterTest.java │ │ └── YamlFactoryTest.java │ └── resources │ └── log4j.xml └── shunting-yard.png /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @ExpediaGroup/apiary-committers 2 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | name: Package and run all tests 8 | runs-on: ubuntu-18.04 9 | steps: 10 | - uses: actions/checkout@v2 11 | with: 12 | fetch-depth: 0 13 | - name: Init Coveralls 14 | shell: bash 15 | run: | 16 | COVERALLS_TOKEN=${{ secrets.COVERALLS_REPO_TOKEN }} 17 | if [[ -z "${COVERALLS_TOKEN}" ]]; 18 | then 19 | echo "Coveralls token not available" 20 | COVERALLS_SKIP=true 21 | else 22 | echo "Coveralls token available" 23 | COVERALLS_SKIP=false 24 | fi 25 | echo "COVERALLS_SKIP=${COVERALLS_SKIP}" >> $GITHUB_ENV 26 | - name: Set up JDK 27 | uses: actions/setup-java@v1 28 | with: 29 | java-version: 8 30 | - name: Run Maven Targets 31 | run: mvn package jacoco:report coveralls:report --batch-mode --show-version --activate-profiles coveralls -Dcoveralls.skip=$COVERALLS_SKIP -DrepoToken=${{ secrets.COVERALLS_REPO_TOKEN }} 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse files 2 | .classpath 3 | .settings 4 | .project 5 | .springBeans 6 | 7 | # Checkstyle, findbugs and other eclipse plugins 8 | */.checkstyle 9 | */.fbExcludeFilterFile 10 | **/.factorypath 11 | */.apt_generated/ 12 | 13 | # Jenv configuration 14 | .java-version 15 | 16 | # Intellij 17 | .idea/ 18 | *.iml 19 | *.iws 20 | 21 | # Netbeans files 22 | nbactions.xml 23 | 24 | # Maven target folder 25 | target/ 26 | **/dependency-reduced-pom.xml 27 | *.versionsBackup 28 | 29 | # Any left over .svn folders 30 | .svn/ 31 | 32 | # MAC stuff 33 | .DS_Store 34 | 35 | # AWS credentials 36 | *.pem 37 | 38 | # Test Specific files 39 | metastore_db 40 | 41 | 42 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk8 4 | os: 5 | - linux 6 | cache: 7 | directories: 8 | - ~/.m2/repository 9 | install: 10 | - mvn install -DskipTests=true -Dmaven.javadoc.skip=true --quiet --batch-mode --show-version --activate-profiles coveralls 11 | script: 12 | - mvn test jacoco:report coveralls:report --batch-mode --activate-profiles coveralls -DrepoToken=${COVERALLS_REPO_TOKEN} 13 | 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 5 | 6 | ## [3.2.0] - 2020-10-02 7 | ### Changed 8 | - Upgraded shunting-yard parent from `hotels-oss-parent` to `eg-oss-parent`. 9 | - Upgraded version of `hive.version` to `2.3.7` (was `2.3.4`). Allows Shunting Yard to be used on JDK>=9. 10 | - Upgraded `hcommon.hive.metastore` from `1.4.1` to `1.4.2`. 11 | - Upgraded `circus.train` from `15.0.0` to `16.3.0`. 12 | - Upgraded AWS SDK (`aws.version`) from `1.11.415` to `1.11.852`. 13 | - Upgraded `micrometer.version` (in shunting-yard-replicator) from `1.0.5` to `1.5.4`. 14 | 15 | ## [3.1.0] - 2020-01-16 16 | ### Changed 17 | - Circus Train `replication-Mode` will be `FULL` for `insert overwrite` operation on source table (was `METADATA_UPDATE`). 18 | - Circus Train `replication-Mode` will always be `FULL` for `ALTER_PARTITION` event. 19 | 20 | ## [3.0.1] - 2019-11-22 21 | ### Changed 22 | - Upgraded `hotels-oss-parent` to 4.1.0 (was 4.0.1). 23 | - Upgraded `hcommon-hive-metastore` to 1.4.1 (was 1.0.0). 24 | - Upgraded Circus Train version to 15.0.0 (was 12.0.0). 25 | 26 | ### Added 27 | - Support for skipping Circus Train's housekeeping process. 28 | 29 | ## [3.0.0] - 2019-06-20 30 | ### Changed 31 | - Maven group ID is now com.expediagroup.shuntingyard (was com.hotels.shunting.yard). 32 | - All Java classes moved to com.expediagroup.shuntingyard (was com.hotels.shunting.yard). 33 | - hotels-oss-parent version updated to 4.0.1 (was 2.3.5). 34 | - Updated configuration properties to not have package names in them. 35 | 36 | ## [2.0.1] - 2019-06-12 37 | ### Fixed 38 | * `FilteringMessageReader` now deletes filtered messages to mimic previous behaviour of delete on read. 39 | 40 | ### Changed 41 | - Excluded `org.pentaho.pentaho-aggdesigner-algorithm` dependency as it's not available in Maven Central. 42 | 43 | ## [2.0.0] - 2019-04-16 44 | ### Added 45 | * Support for specifying target database and table name. 46 | 47 | ### Changed 48 | * Extracted Apiary SQS receiver logic to library in [`apiary-receivers`](https://github.com/ExpediaInc/apiary-extensions/tree/master/apiary-receivers). 49 | * Moved SQS message deletion out of receiver and into `MessageReaderAdapter`. 50 | * Upgraded `apiary-receiver-sqs` to 2.0.0 (was 1.4.0). 51 | 52 | ## [1.0.0] - 2019-03-19 53 | ### Added 54 | * Support for handling new elements (location and table parameters) coming in from Apiary events. 55 | * Support for performing metadata only sync in alter table and alter partition event. 56 | 57 | ## [0.0.8] - 2019-02-05 58 | ### Added 59 | * Support for passing certain configurations like Graphite directly to internal CT instance using `--ct-config` argument. 60 | 61 | ## [0.0.7] - 2019-01-28 62 | ### Added 63 | * log4j.xml to be packaged with Shunting-Yard Binary. 64 | 65 | ## [0.0.6] - 2019-01-22 66 | ### Changed 67 | * Upgraded `hive` to 2.3.4 (was 2.3.0). 68 | * ContainerCredentialsProvider to a more robust EC2ContainerCredentialsProvider. 69 | 70 | ## [0.0.5] - 2019-01-14 71 | ### Added 72 | * Support to read AWS Credentials from within the Elastic Container Service Task using ContainerCredentialsProvider. 73 | 74 | ## [0.0.4] - 2019-01-08 75 | ### Changed 76 | * Refactored project to remove checkstyle and findbugs warnings, which does not impact functionality. 77 | * Upgraded `hotels-oss-parent` to 2.3.5 (was 2.1.0). 78 | ### Added 79 | * Support for selecting the tables to be replicated [#6](https://github.com/HotelsDotCom/shunting-yard/issues/6). 80 | 81 | ## [0.0.3] - 2018-11-01 82 | ### Changed 83 | * Enforce exception handling [#2](https://github.com/HotelsDotCom/shunting-yard/issues/2). 84 | 85 | ### Added 86 | * Event aggregation based on time windows [#4](https://github.com/HotelsDotCom/shunting-yard/issues/4). This is a breaking change since the event model has been changed to a more suitable structure for [Circus Train](https://github.com/HotelsDotCom/circus-train). 87 | * Support for handling Hive Metastore Events from Apiary. 88 | 89 | ### Removed 90 | * Receivers & emitters for Kinesis and Kafka. 91 | 92 | ## [0.0.2] - 2018-06-06 - not ready for production 93 | ### Changed 94 | * Generate a fat emitter JAR per emitter implementation [#13](https://github.com/HotelsDotCom/shunting-yard/issues/13). 95 | 96 | ## [0.0.1] - 2018-06-05 - not ready for production 97 | ### Added 98 | * First release. 99 | -------------------------------------------------------------------------------- /CODE-OF-CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behaviour that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behaviour by participants include: 24 | 25 | * The use of sexualised language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behaviour and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behaviour. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviours that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behaviour may be 58 | reported by contacting [a member of the project team](https://github.com/orgs/HotelsDotCom/teams/shuting-yard-committers/members). All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How To Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are just a few guidelines you need to follow which are described in detail below. 4 | 5 | ## 1. Fork this repo 6 | 7 | You should create a fork of this project in your account and work from there. You can create a fork by clicking the fork button in GitHub. 8 | 9 | ## 2. One feature, one branch 10 | 11 | Work for each new feature/issue should occur in its own branch. To create a new branch from the command line: 12 | ```shell 13 | git checkout -b my-new-feature 14 | ``` 15 | where "my-new-feature" describes what you're working on. 16 | 17 | ## 3. Add unit tests 18 | If your contribution modifies existing or adds new code please add corresponding unit tests for this. 19 | 20 | ## 4. Ensure that the build passes 21 | 22 | Run 23 | ```shell 24 | mvn package 25 | ``` 26 | and check that there are no errors. 27 | 28 | ## 5. Add documentation for new or updated functionality 29 | 30 | Please review all of the .md files in this project to see if they are impacted by your change and update them accordingly. 31 | 32 | ## 6. Add to CHANGELOG.md 33 | 34 | Any notable changes should be recorded in the CHANGELOG.md following the [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) conventions. 35 | 36 | ## 7. Submit a pull request and describe the change 37 | 38 | Push your changes to your branch and open a pull request against the parent repo on GitHub. The project administrators will review your pull request and respond with feedback. 39 | 40 | # How your contribution gets merged 41 | 42 | Upon pull request submission, your code will be reviewed by the maintainers. They will confirm at least the following: 43 | 44 | - Tests run successfully (unit, coverage, integration, style). 45 | - Contribution policy has been followed. 46 | 47 | Two (human) reviewers will need to sign off on your pull request before it can be merged. 48 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Expedia Inc. 2 | Copyright (C) 2018 Expedia Inc. 3 | 4 | This product includes software developed by Expedia Inc. licensed under the Apache License, Version 2.0. 5 | 6 | This product includes software developed by The Apache Software Foundation (http://www.apache.org/) licensed under the Apache License, Version 2.0. 7 | -------------------------------------------------------------------------------- /shunting-yard-binary/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | 5 | com.expediagroup 6 | shunting-yard 7 | 3.2.1-SNAPSHOT 8 | 9 | 10 | shunting-yard-binary 11 | Shunting Yard packager 12 | pom 13 | 14 | 15 | 16 | com.expediagroup 17 | shunting-yard-common 18 | ${project.version} 19 | 20 | 21 | com.expediagroup 22 | shunting-yard-receiver-sqs 23 | ${project.version} 24 | 25 | 26 | com.expediagroup 27 | shunting-yard-replicator 28 | ${project.version} 29 | 30 | 31 | 32 | 33 | 34 | 35 | org.apache.maven.plugins 36 | maven-shade-plugin 37 | 38 | 39 | replicator 40 | package 41 | 42 | shade 43 | 44 | 45 | false 46 | target/shunting-yard-${project.version}-all.jar 47 | 48 | 49 | com.hotels:* 50 | com.hotels.hadoop:* 51 | com.expediagroup:* 52 | com.google.cloud*:* 53 | com.google.api*:* 54 | com.google.oauth*:* 55 | com.google.http*:* 56 | com.google.guava:guava 57 | 58 | 59 | 60 | 61 | com.google 62 | ${shade.prefix}.com.google 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | org.apache.maven.plugins 71 | maven-assembly-plugin 72 | 3.0.0 73 | 74 | posix 75 | 76 | 77 | 78 | make-assembly 79 | package 80 | 81 | single 82 | 83 | 84 | 85 | src/main/assembly/shunting-yard-package.xml 86 | 87 | true 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /shunting-yard-binary/src/main/assembly/shunting-yard-package.xml: -------------------------------------------------------------------------------- 1 | 3 | bin 4 | 5 | tgz 6 | 7 | shunting-yard-${project.version} 8 | 9 | 10 | target/shunting-yard-${project.version}-all.jar 11 | lib 12 | shunting-yard-all-latest.jar 13 | 14 | 15 | 16 | 17 | lib 18 | false 19 | 20 | com.expediagroup:* 21 | com.hotels:* 22 | com.google.api*:* 23 | com.google.cloud*:* 24 | com.google.oauth*:* 25 | com.google.http*:* 26 | com.google.guava:guava 27 | com.hotels.hadoop:* 28 | 29 | 30 | 31 | 32 | 33 | src/main/scripts 34 | bin 35 | false 36 | 744 37 | 38 | 39 | src/main/resources 40 | conf 41 | false 42 | 43 | 44 | .. 45 | 46 | false 47 | 644 48 | 49 | README.md 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /shunting-yard-binary/src/main/resources/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /shunting-yard-binary/src/main/resources/shunting-yard-minimal.yml.template: -------------------------------------------------------------------------------- 1 | source-catalog: 2 | name: source-data-warehouse 3 | hive-metastore-uris: thrift://source-hive-metastore-url:48869 4 | replica-catalog: 5 | name: target-data-warehouse 6 | hive-metastore-uris: thrift://domain-name.eu-west-1.compute.amazonaws.com:9083 7 | event-receiver: 8 | message-reader-factory-class: com.expediagroup.shuntingyard.receiver.sqs.messaging.SqsMessageReaderFactory 9 | configuration-properties: 10 | sqs.queue: https://sqs.us-west-2.amazonaws.com/1234567/sqs-queue 11 | sqs.wait.time.seconds: 15 12 | source-table-filter: 13 | table-names: 14 | - db1.table_1 15 | - db2.table_2 16 | table-replications: 17 | - source-table: 18 | database-name: db1 19 | table-name: table_1 20 | replica-table: 21 | database-name: db_1 22 | table-name: table_1_new 23 | -------------------------------------------------------------------------------- /shunting-yard-binary/src/main/scripts/replicator.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | 6 | if [[ -z ${CIRCUS_TRAIN_HOME-} ]]; then 7 | echo "Environment variable CIRCUS_TRAIN_HOME is not set" 8 | exit -1 9 | fi 10 | 11 | if [[ -z $SHUNTING_YARD_HOME ]]; then 12 | #work out the script location 13 | SOURCE="${BASH_SOURCE[0]}" 14 | while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink 15 | SCRIPT_DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" 16 | SOURCE="$(readlink "$SOURCE")" 17 | [[ $SOURCE != /* ]] && SOURCE="$SCRIPT_DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located 18 | done 19 | SHUNTING_YARD_HOME="$( cd -P "$( dirname "$SOURCE" )" && cd .. && pwd )" 20 | fi 21 | echo "Using $SHUNTING_YARD_HOME" 22 | 23 | if [[ -z ${HIVE_LIB-} ]]; then 24 | export HIVE_LIB=/usr/hdp/current/hive-client/lib 25 | fi 26 | if [[ -z ${HCAT_LIB-} ]]; then 27 | export HCAT_LIB=/usr/hdp/current/hive-webhcat/share/hcatalog 28 | fi 29 | if [[ -z ${HIVE_CONF_PATH-} ]]; then 30 | export HIVE_CONF_PATH=/etc/hive/conf 31 | fi 32 | 33 | LIBFB303_JAR=`ls $HIVE_LIB/libfb303-*.jar | tr '\n' ':'` 34 | 35 | SHUNTING_YARD_LIBS=$SHUNTING_YARD_HOME/lib/*\ 36 | :$HIVE_LIB/hive-exec.jar\ 37 | :$HIVE_LIB/hive-metastore.jar\ 38 | :$LIBFB303_JAR\ 39 | :$HIVE_CONF_PATH/ 40 | 41 | if [[ -z ${SHUNTING_YARD_CLASSPATH-} ]]; then 42 | export SHUNTING_YARD_CLASSPATH=$SHUNTING_YARD_LIBS 43 | else 44 | export SHUNTING_YARD_CLASSPATH=$SHUNTING_YARD_CLASSPATH:$SHUNTING_YARD_LIBS 45 | fi 46 | 47 | if [[ -z ${HADOOP_CLASSPATH-} ]]; then 48 | export HADOOP_CLASSPATH=`hadoop classpath` 49 | fi 50 | 51 | java -cp $SHUNTING_YARD_CLASSPATH:$HADOOP_CLASSPATH \ 52 | com.expediagroup.shuntingyard.replicator.exec.MetaStoreEventReplication \ 53 | "$@" 54 | 55 | exit 56 | -------------------------------------------------------------------------------- /shunting-yard-common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | 5 | com.expediagroup 6 | shunting-yard 7 | 3.2.1-SNAPSHOT 8 | 9 | 10 | shunting-yard-common 11 | 12 | 13 | 14 | org.slf4j 15 | slf4j-log4j12 16 | 17 | 18 | 19 | org.apache.commons 20 | commons-lang3 21 | 22 | 23 | 24 | 25 | com.expedia.apiary 26 | apiary-receiver-sqs 27 | ${apiary.receiver.sqs.verison} 28 | 29 | 30 | 31 | 32 | org.apache.hadoop 33 | hadoop-common 34 | 35 | 36 | 37 | 38 | org.apache.hive 39 | hive-metastore 40 | 41 | 42 | org.apache.hive.hcatalog 43 | hive-webhcat-java-client 44 | 45 | 46 | org.pentaho 47 | pentaho-aggdesigner-algorithm 48 | 49 | 50 | 51 | 52 | 53 | 54 | junit 55 | junit 56 | 57 | 58 | fm.last.commons 59 | lastcommons-test 60 | test 61 | 62 | 63 | org.assertj 64 | assertj-core 65 | 66 | 67 | org.mockito 68 | mockito-core 69 | 70 | 71 | org.powermock 72 | powermock-module-junit4 73 | 74 | 75 | org.powermock 76 | powermock-api-mockito2 77 | 78 | 79 | org.hamcrest 80 | hamcrest-core 81 | 1.3 82 | test 83 | 84 | 85 | org.apache.commons 86 | commons-text 87 | 1.3 88 | test 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /shunting-yard-common/src/main/java/com/expediagroup/shuntingyard/common/Preconditions.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.common; 17 | 18 | public final class Preconditions { 19 | 20 | private Preconditions() {} 21 | 22 | public static T checkNotNull(T t, String message) { 23 | if (t == null) { 24 | throw new NullPointerException(message); 25 | } 26 | return t; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /shunting-yard-common/src/main/java/com/expediagroup/shuntingyard/common/Property.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.common; 17 | 18 | public interface Property { 19 | 20 | String key(); 21 | 22 | String unPrefixedKey(); 23 | 24 | Object defaultValue(); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /shunting-yard-common/src/main/java/com/expediagroup/shuntingyard/common/PropertyUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.common; 17 | 18 | import org.apache.hadoop.conf.Configuration; 19 | 20 | public class PropertyUtils { 21 | 22 | private PropertyUtils() {} 23 | 24 | public static String stringProperty(Configuration conf, Property property) { 25 | return conf.get(property.key(), (String) property.defaultValue()); 26 | } 27 | 28 | public static boolean booleanProperty(Configuration conf, Property property) { 29 | return conf.getBoolean(property.key(), (boolean) property.defaultValue()); 30 | } 31 | 32 | public static int intProperty(Configuration conf, Property property) { 33 | return conf.getInt(property.key(), (int) property.defaultValue()); 34 | } 35 | 36 | public static long longProperty(Configuration conf, Property property) { 37 | return conf.getLong(property.key(), (long) property.defaultValue()); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /shunting-yard-common/src/main/java/com/expediagroup/shuntingyard/common/ShuntingYardException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.common; 17 | 18 | public class ShuntingYardException extends RuntimeException { 19 | private static final long serialVersionUID = 1L; 20 | 21 | public ShuntingYardException(String message) { 22 | super(message); 23 | } 24 | 25 | public ShuntingYardException(String message, Throwable cause) { 26 | super(message, cause); 27 | } 28 | 29 | public ShuntingYardException(Throwable cause) { 30 | super(cause); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /shunting-yard-common/src/main/java/com/expediagroup/shuntingyard/common/io/jackson/DummyMapEntry.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.common.io.jackson; 17 | 18 | import java.util.Map; 19 | 20 | public class DummyMapEntry implements Map.Entry { 21 | 22 | private K key; 23 | private V value; 24 | 25 | @Override 26 | public K getKey() { 27 | return key; 28 | } 29 | 30 | public void setKey(K key) { 31 | this.key = key; 32 | } 33 | 34 | @Override 35 | public V getValue() { 36 | return value; 37 | } 38 | 39 | @Override 40 | public V setValue(V value) { 41 | V old = this.value; 42 | this.value = value; 43 | return old; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /shunting-yard-common/src/main/java/com/expediagroup/shuntingyard/common/io/jackson/ThriftSerDeUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.common.io.jackson; 17 | 18 | import org.apache.thrift.TBase; 19 | import org.apache.thrift.TFieldIdEnum; 20 | 21 | final class ThriftSerDeUtils { 22 | 23 | private ThriftSerDeUtils() {} 24 | 25 | static > E[] fields(Class tClazz) { 26 | for (Class clazz : tClazz.getDeclaredClasses()) { 27 | if (TFieldIdEnum.class.isAssignableFrom(clazz)) { 28 | return (E[]) clazz.getEnumConstants(); 29 | } 30 | } 31 | return null; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /shunting-yard-common/src/main/java/com/expediagroup/shuntingyard/common/messaging/Message.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.common.messaging; 17 | 18 | public class Message { 19 | 20 | public static class Builder { 21 | private String database; 22 | private String table; 23 | private long timestamp = System.currentTimeMillis(); 24 | private String payload; 25 | 26 | private Builder() {} 27 | 28 | private static String checkEmpty(String string, String message) { 29 | if (string == null || string.trim().isEmpty()) { 30 | throw new IllegalArgumentException(message); 31 | } 32 | return string.trim(); 33 | } 34 | 35 | private static T checkNull(T object, String message) { 36 | if (object == null) { 37 | throw new IllegalArgumentException(message); 38 | } 39 | return object; 40 | } 41 | 42 | public Builder database(String database) { 43 | this.database = database; 44 | return this; 45 | } 46 | 47 | public Builder table(String table) { 48 | this.table = table; 49 | return this; 50 | } 51 | 52 | public Builder timestamp(long timestamp) { 53 | this.timestamp = timestamp; 54 | return this; 55 | } 56 | 57 | public Builder payload(String payload) { 58 | this.payload = payload; 59 | return this; 60 | } 61 | 62 | public Message build() { 63 | return new Message(checkEmpty(database, "Parameter 'database' is required"), 64 | checkEmpty(table, "Parameter 'table' is required"), timestamp, 65 | checkNull(payload, "Parameter 'payload' is required")); 66 | } 67 | } 68 | 69 | public static Builder builder() { 70 | return new Builder(); 71 | } 72 | 73 | private final String database; 74 | private final String table; 75 | private final long timestamp; 76 | private final String payload; 77 | 78 | private Message(String database, String table, long timestamp, String payload) { 79 | this.database = database; 80 | this.table = table; 81 | this.timestamp = timestamp; 82 | this.payload = payload; 83 | } 84 | 85 | public String getQualifiedTableName() { 86 | return String.format("%s.%s", database, table); 87 | } 88 | 89 | public String getDatabase() { 90 | return database; 91 | } 92 | 93 | public String getTable() { 94 | return table; 95 | } 96 | 97 | public long getTimestamp() { 98 | return timestamp; 99 | } 100 | 101 | public String getPayload() { 102 | return payload; 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /shunting-yard-common/src/main/java/com/expediagroup/shuntingyard/common/messaging/MessageReaderFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.common.messaging; 17 | 18 | import org.apache.hadoop.conf.Configuration; 19 | 20 | import com.expediagroup.shuntingyard.common.ShuntingYardException; 21 | 22 | import com.expedia.apiary.extensions.receiver.common.messaging.MessageReader; 23 | 24 | public interface MessageReaderFactory { 25 | 26 | static MessageReaderFactory newInstance(String messageReaderFactoryClassName) { 27 | try { 28 | @SuppressWarnings("unchecked") 29 | Class clazz = (Class) Class 30 | .forName(messageReaderFactoryClassName); 31 | return clazz.newInstance(); 32 | } catch (ClassCastException e) { 33 | throw new ShuntingYardException( 34 | "Class " + messageReaderFactoryClassName + " does not seem to be a MessageReaderFactory implementation", e); 35 | } catch (Exception e) { 36 | throw new ShuntingYardException( 37 | "Unable to instantiate a MessageReaderFactory of class " + messageReaderFactoryClassName, e); 38 | } 39 | } 40 | 41 | MessageReader newInstance(Configuration conf); 42 | 43 | } 44 | -------------------------------------------------------------------------------- /shunting-yard-common/src/main/java/com/expediagroup/shuntingyard/common/messaging/MessageTask.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.common.messaging; 17 | 18 | public interface MessageTask extends Runnable { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /shunting-yard-common/src/main/java/com/expediagroup/shuntingyard/common/messaging/MessageTaskFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.common.messaging; 17 | 18 | public interface MessageTaskFactory { 19 | 20 | MessageTask newTask(Message message); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /shunting-yard-common/src/main/java/com/expediagroup/shuntingyard/common/metrics/HiveMetricsHelper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.common.metrics; 17 | 18 | import java.util.Optional; 19 | 20 | import org.apache.hadoop.hive.common.metrics.common.Metrics; 21 | import org.apache.hadoop.hive.common.metrics.common.MetricsFactory; 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | 25 | public class HiveMetricsHelper { 26 | private static final Logger log = LoggerFactory.getLogger(HiveMetricsHelper.class); 27 | 28 | private HiveMetricsHelper() {} 29 | 30 | public static Optional incrementCounter(String name) { 31 | try { 32 | Metrics metrics = MetricsFactory.getInstance(); 33 | if (metrics != null) { 34 | return Optional.of(metrics.incrementCounter(name)); 35 | } 36 | } catch (Exception e) { 37 | log.warn("Unable to increment counter {}", name, e); 38 | } 39 | return Optional.empty(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /shunting-yard-common/src/main/java/com/expediagroup/shuntingyard/common/metrics/MetricsConstant.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.common.metrics; 17 | 18 | public final class MetricsConstant { 19 | 20 | public static final String RECEIVER_FAILURES = "shunting_yard_receiver_failures"; 21 | public static final String RECEIVER_EMPTY = "shunting_yard_receiver_empty"; 22 | public static final String RECEIVER_SUCCESSES = "shunting_yard_receiver_successes"; 23 | 24 | private MetricsConstant() {} 25 | 26 | } 27 | -------------------------------------------------------------------------------- /shunting-yard-common/src/main/java/com/expediagroup/shuntingyard/common/receiver/thrift/ExpressionBuilder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. and Apache Hive authors. 3 | * 4 | * Copied from Hive 2.3.0: 5 | * 6 | * https://github.com/apache/hive/blob/rel/release-2.3.0/hcatalog/webhcat/java-client/src/main/java/org/ 7 | * apache/hive/hcatalog/api/HCatClientHMSImpl.java#L521 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | */ 21 | 22 | package com.expediagroup.shuntingyard.common.receiver.thrift; 23 | 24 | import java.util.Map; 25 | 26 | import org.apache.hadoop.hive.metastore.api.FieldSchema; 27 | import org.apache.hadoop.hive.metastore.api.Table; 28 | import org.apache.hadoop.hive.ql.exec.FunctionRegistry; 29 | import org.apache.hadoop.hive.ql.parse.SemanticException; 30 | import org.apache.hadoop.hive.ql.plan.ExprNodeColumnDesc; 31 | import org.apache.hadoop.hive.ql.plan.ExprNodeConstantDesc; 32 | import org.apache.hadoop.hive.ql.plan.ExprNodeDesc; 33 | import org.apache.hadoop.hive.ql.plan.ExprNodeGenericFuncDesc; 34 | import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorConverters; 35 | import org.apache.hadoop.hive.serde2.typeinfo.PrimitiveTypeInfo; 36 | import org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory; 37 | import org.apache.hadoop.hive.serde2.typeinfo.TypeInfoUtils; 38 | 39 | import com.google.common.collect.Lists; 40 | import com.google.common.collect.Maps; 41 | 42 | /** 43 | * Helper class to help build ExprDesc tree to represent the partitions to be dropped. Note: At present, the 44 | * ExpressionBuilder only constructs partition predicates where partition-keys equal specific values, and logical-AND 45 | * expressions. E.g. ( dt = '20150310' AND region = 'US' ) This only supports the partition-specs specified by the Map 46 | * argument of: {@link org.apache.hive.hcatalog.api.HCatClient#dropPartitions(String, String, Map, boolean)} 47 | */ 48 | class ExpressionBuilder { 49 | 50 | private final Map partColumnTypesMap = Maps.newHashMap(); 51 | private final Map partSpecs; 52 | 53 | public ExpressionBuilder(Table table, Map partSpecs) { 54 | this.partSpecs = partSpecs; 55 | for (FieldSchema partField : table.getPartitionKeys()) { 56 | partColumnTypesMap 57 | .put(partField.getName().toLowerCase(), TypeInfoFactory.getPrimitiveTypeInfo(partField.getType())); 58 | } 59 | } 60 | 61 | private PrimitiveTypeInfo getTypeFor(String partColumn) { 62 | return partColumnTypesMap.get(partColumn.toLowerCase()); 63 | } 64 | 65 | private Object getTypeAppropriateValueFor(PrimitiveTypeInfo type, String value) { 66 | ObjectInspectorConverters.Converter converter = ObjectInspectorConverters 67 | .getConverter(TypeInfoUtils.getStandardJavaObjectInspectorFromTypeInfo(TypeInfoFactory.stringTypeInfo), 68 | TypeInfoUtils.getStandardJavaObjectInspectorFromTypeInfo(type)); 69 | 70 | return converter.convert(value); 71 | } 72 | 73 | public ExprNodeGenericFuncDesc equalityPredicate(String partColumn, String value) throws SemanticException { 74 | 75 | PrimitiveTypeInfo partColumnType = getTypeFor(partColumn); 76 | ExprNodeColumnDesc partColumnExpr = new ExprNodeColumnDesc(partColumnType, partColumn, null, true); 77 | ExprNodeConstantDesc valueExpr = new ExprNodeConstantDesc(partColumnType, 78 | getTypeAppropriateValueFor(partColumnType, value)); 79 | 80 | return binaryPredicate("=", partColumnExpr, valueExpr); 81 | } 82 | 83 | public ExprNodeGenericFuncDesc binaryPredicate(String function, ExprNodeDesc lhs, ExprNodeDesc rhs) 84 | throws SemanticException { 85 | return new ExprNodeGenericFuncDesc(TypeInfoFactory.booleanTypeInfo, 86 | FunctionRegistry.getFunctionInfo(function).getGenericUDF(), Lists.newArrayList(lhs, rhs)); 87 | } 88 | 89 | public ExprNodeGenericFuncDesc build() throws SemanticException { 90 | ExprNodeGenericFuncDesc resultExpr = null; 91 | 92 | for (Map.Entry partSpec : partSpecs.entrySet()) { 93 | String column = partSpec.getKey(); 94 | String value = partSpec.getValue(); 95 | ExprNodeGenericFuncDesc partExpr = equalityPredicate(column, value); 96 | 97 | resultExpr = (resultExpr == null ? partExpr : binaryPredicate("and", resultExpr, partExpr)); 98 | } 99 | 100 | return resultExpr; 101 | } 102 | } // class ExpressionBuilder; 103 | -------------------------------------------------------------------------------- /shunting-yard-common/src/main/java/com/expediagroup/shuntingyard/common/receiver/thrift/ThriftListenerUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.common.receiver.thrift; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | import java.util.Map; 21 | 22 | import org.apache.hadoop.hive.common.ObjectPair; 23 | import org.apache.hadoop.hive.metastore.Warehouse; 24 | import org.apache.hadoop.hive.metastore.api.Partition; 25 | import org.apache.hadoop.hive.metastore.api.Table; 26 | import org.apache.hadoop.hive.ql.exec.SerializationUtilities; 27 | import org.apache.hadoop.hive.ql.parse.SemanticException; 28 | import org.apache.hadoop.hive.ql.plan.ExprNodeGenericFuncDesc; 29 | 30 | public class ThriftListenerUtils { 31 | 32 | private ThriftListenerUtils() {} 33 | 34 | public static List> toObjectPairs(Table table, List partitions) { 35 | List> pairs = new ArrayList<>(partitions.size()); 36 | for (Partition partition : partitions) { 37 | Map partitionSpec = Warehouse.makeSpecFromValues(table.getPartitionKeys(), partition.getValues()); 38 | ExprNodeGenericFuncDesc partitionExpression; 39 | try { 40 | partitionExpression = new ExpressionBuilder(table, partitionSpec).build(); 41 | } catch (SemanticException e) { 42 | throw new RuntimeException("Unable to build expression", e); 43 | } 44 | ObjectPair serializedPartitionExpression = new ObjectPair<>(partitionSpec.size(), 45 | SerializationUtilities.serializeExpressionToKryo(partitionExpression)); 46 | pairs.add(serializedPartitionExpression); 47 | } 48 | return pairs; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /shunting-yard-common/src/test/data/com/expediagroup/shuntingyard/common/io/jackson/DeserializerWithJsonInputTest/add_partition.json: -------------------------------------------------------------------------------- 1 | "Message" : "{ 2 | \"protocolVersion\":\"1.0\", 3 | \"eventType\":\"ADD_PARTITION\", 4 | \"dbName\":\"some_db\", 5 | \"tableName\":\"some_table\", 6 | \"tableLocation\":\"s3://table_location\", 7 | \"partitionKeys\":{\"col_1\":\"string\", \"col_2\": \"integer\",\"col_3\":\"string\"}, 8 | \"partitionValues\":[\"val_1\", \"val_2\", \"val_3\"], 9 | \"partitionLocation\":\"s3://table_location/partition_location\", 10 | \"tableParameters\":{ 11 | \"param_1\": \"val_1\", 12 | \"param_2\": \"val_2\" 13 | } 14 | }" 15 | -------------------------------------------------------------------------------- /shunting-yard-common/src/test/data/com/expediagroup/shuntingyard/common/io/jackson/DeserializerWithJsonInputTest/alter_partition.json: -------------------------------------------------------------------------------- 1 | "Message" : "{ 2 | \"protocolVersion\":\"1.0\", 3 | \"eventType\":\"ALTER_PARTITION\", 4 | \"dbName\":\"some_db\", 5 | \"tableName\":\"some_table\", 6 | \"tableLocation\":\"s3://table_location\", 7 | \"partitionKeys\":{\"col_1\": \"string\", \"col_2\": \"integer\",\"col_3\": \"string\"}, 8 | \"partitionValues\":[\"val_1\", \"val_2\", \"val_3\"], 9 | \"partitionLocation\":\"s3://table_location/partition_location\", 10 | \"oldPartitionValues\": [\"val_4\",\"val_5\", \"val_6\"], 11 | \"oldPartitionLocation\":\"s3://table_location/old_partition_location\", 12 | \"tableParameters\":{ 13 | \"param_1\": \"val_1\", 14 | \"param_2\": \"val_2\" 15 | } 16 | }" 17 | -------------------------------------------------------------------------------- /shunting-yard-common/src/test/data/com/expediagroup/shuntingyard/common/io/jackson/DeserializerWithJsonInputTest/alter_table.json: -------------------------------------------------------------------------------- 1 | "Message" : "{ 2 | \"protocolVersion\":\"1.0\", 3 | \"eventType\":\"ALTER_TABLE\", 4 | \"dbName\":\"some_db\", 5 | \"tableName\":\"some_table\", 6 | \"tableLocation\":\"s3://table_location\", 7 | \"tableParameters\":{ 8 | \"param_1\": \"val_1\", 9 | \"param_2\": \"val_2\" 10 | }, 11 | \"oldTableName\": \"some_table\", 12 | \"oldTableLocation\": \"s3://old_table_location\" 13 | }" 14 | -------------------------------------------------------------------------------- /shunting-yard-common/src/test/data/com/expediagroup/shuntingyard/common/io/jackson/DeserializerWithJsonInputTest/create_table.json: -------------------------------------------------------------------------------- 1 | "Message" : "{ 2 | \"protocolVersion\":\"1.0\", 3 | \"eventType\":\"CREATE_TABLE\", 4 | \"dbName\":\"some_db\", 5 | \"tableName\":\"some_table\", 6 | \"tableLocation\":\"s3://table_location\" 7 | }" 8 | -------------------------------------------------------------------------------- /shunting-yard-common/src/test/data/com/expediagroup/shuntingyard/common/io/jackson/DeserializerWithJsonInputTest/drop_partition.json: -------------------------------------------------------------------------------- 1 | "Message" : "{ 2 | \"protocolVersion\":\"1.0\", 3 | \"eventType\":\"DROP_PARTITION\", 4 | \"dbName\":\"some_db\", 5 | \"tableName\":\"some_table\", 6 | \"tableLocation\":\"s3://table_location\", 7 | \"partitionKeys\":{\"col_1\": \"string\", \"col_2\": \"integer\",\"col_3\":\"string\"}, 8 | \"partitionValues\":[\"val_1\", \"val_2\", \"val_3\"], 9 | \"partitionLocation\":\"s3://table_location/partition_location\", 10 | \"tableParameters\":{ 11 | \"param_1\": \"val_1\", 12 | \"param_2\": \"val_2\" 13 | } 14 | }" 15 | -------------------------------------------------------------------------------- /shunting-yard-common/src/test/data/com/expediagroup/shuntingyard/common/io/jackson/DeserializerWithJsonInputTest/drop_table.json: -------------------------------------------------------------------------------- 1 | "Message" : "{ 2 | \"protocolVersion\":\"1.0\", 3 | \"eventType\":\"DROP_TABLE\", 4 | \"dbName\":\"some_db\", 5 | \"tableName\":\"some_table\", 6 | \"tableLocation\":\"s3://table_location\" 7 | }" 8 | -------------------------------------------------------------------------------- /shunting-yard-common/src/test/data/com/expediagroup/shuntingyard/common/io/jackson/DeserializerWithJsonInputTest/insert_table.json: -------------------------------------------------------------------------------- 1 | "Message" : "{ 2 | \"protocolVersion\":\"1.0\", 3 | \"eventType\": \"INSERT\", 4 | \"dbName\": \"some_db\", 5 | \"tableName\":\"some_table\", 6 | \"files\": [\"file:/a/b.txt\",\"file:/a/c.txt\"], 7 | \"fileChecksums\":[\"123\",\"456\"], 8 | \"partitionKeyValues\": {\"col_1\": \"val_1\",\"col_2\": \"val_2\",\"col_3\": \"val_3\"} 9 | }" 10 | -------------------------------------------------------------------------------- /shunting-yard-common/src/test/java/com/expediagroup/shuntingyard/common/PreconditionsTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.common; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | 20 | import static com.expediagroup.shuntingyard.common.Preconditions.checkNotNull; 21 | 22 | import org.junit.Rule; 23 | import org.junit.Test; 24 | import org.junit.rules.ExpectedException; 25 | import org.junit.runner.RunWith; 26 | import org.mockito.junit.MockitoJUnitRunner; 27 | 28 | @RunWith(MockitoJUnitRunner.class) 29 | public class PreconditionsTest { 30 | 31 | public @Rule ExpectedException exception = ExpectedException.none(); 32 | 33 | @Test 34 | public void checkNotNullSucceeds() { 35 | assertThat(checkNotNull("", "message")).isSameAs(""); 36 | } 37 | 38 | @Test 39 | public void checkNotNullFails() { 40 | exception.expect(NullPointerException.class); 41 | exception.expectMessage("message"); 42 | checkNotNull(null, "message"); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /shunting-yard-common/src/test/java/com/expediagroup/shuntingyard/common/PropertyUtilsTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.common; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static org.mockito.Mockito.when; 20 | 21 | import static com.expediagroup.shuntingyard.common.PropertyUtils.booleanProperty; 22 | import static com.expediagroup.shuntingyard.common.PropertyUtils.intProperty; 23 | import static com.expediagroup.shuntingyard.common.PropertyUtils.longProperty; 24 | import static com.expediagroup.shuntingyard.common.PropertyUtils.stringProperty; 25 | 26 | import org.apache.hadoop.conf.Configuration; 27 | import org.junit.Before; 28 | import org.junit.Rule; 29 | import org.junit.Test; 30 | import org.junit.rules.ExpectedException; 31 | import org.junit.runner.RunWith; 32 | import org.mockito.Mock; 33 | import org.mockito.junit.MockitoJUnitRunner; 34 | 35 | import com.expediagroup.shuntingyard.common.Property; 36 | 37 | @RunWith(MockitoJUnitRunner.class) 38 | public class PropertyUtilsTest { 39 | 40 | private static final String STRING_PROPERTY = "property.string"; 41 | private static final String BOOLEAN_PROPERTY = "property.boolean"; 42 | private static final String INT_PROPERTY = "property.int"; 43 | private static final String LONG_PROPERTY = "property.long"; 44 | 45 | public @Rule ExpectedException exception = ExpectedException.none(); 46 | 47 | private @Mock Property property; 48 | 49 | private final Configuration conf = new Configuration(); 50 | 51 | @Before 52 | public void init() { 53 | conf.set(STRING_PROPERTY, "string"); 54 | conf.setBoolean(BOOLEAN_PROPERTY, true); 55 | conf.setInt(INT_PROPERTY, 1024); 56 | conf.setLong(LONG_PROPERTY, 18000L); 57 | } 58 | 59 | @Test 60 | public void stringPropertyReturnsConfValue() { 61 | when(property.key()).thenReturn(STRING_PROPERTY); 62 | when(property.defaultValue()).thenReturn("default"); 63 | assertThat(stringProperty(conf, property)).isEqualTo("string"); 64 | } 65 | 66 | @Test 67 | public void stringPropertyReturnsDefaultValue() { 68 | when(property.key()).thenReturn("unset"); 69 | when(property.defaultValue()).thenReturn("default"); 70 | assertThat(stringProperty(conf, property)).isEqualTo("default"); 71 | } 72 | 73 | @Test 74 | public void stringPropertyReturnsNull() { 75 | when(property.key()).thenReturn("unset"); 76 | assertThat(stringProperty(conf, property)).isNull(); 77 | } 78 | 79 | @Test 80 | public void booleanPropertyPropertyReturnsConfValue() { 81 | when(property.key()).thenReturn(BOOLEAN_PROPERTY); 82 | when(property.defaultValue()).thenReturn(false); 83 | assertThat(booleanProperty(conf, property)).isTrue(); 84 | } 85 | 86 | @Test 87 | public void booleanPropertyPropertyReturnsDefaultValue() { 88 | when(property.key()).thenReturn("unset"); 89 | when(property.defaultValue()).thenReturn(false); 90 | assertThat(booleanProperty(conf, property)).isFalse(); 91 | } 92 | 93 | @Test(expected = NullPointerException.class) 94 | public void booleanPropertyPropertyThrowsNullPointerException() { 95 | when(property.key()).thenReturn("unset"); 96 | booleanProperty(conf, property); 97 | } 98 | 99 | @Test 100 | public void intPropertyReturnsConfValue() { 101 | when(property.key()).thenReturn(INT_PROPERTY); 102 | when(property.defaultValue()).thenReturn(100); 103 | assertThat(intProperty(conf, property)).isEqualTo(1024); 104 | } 105 | 106 | @Test 107 | public void intPropertyReturnsDefaultValue() { 108 | when(property.key()).thenReturn("unset"); 109 | when(property.defaultValue()).thenReturn(100); 110 | assertThat(intProperty(conf, property)).isEqualTo(100); 111 | } 112 | 113 | @Test(expected = NullPointerException.class) 114 | public void intPropertyThrowsNullPointerException() { 115 | when(property.key()).thenReturn("unset"); 116 | intProperty(conf, property); 117 | } 118 | 119 | @Test 120 | public void longPropertyReturnsConfValue() { 121 | when(property.key()).thenReturn(LONG_PROPERTY); 122 | when(property.defaultValue()).thenReturn(1234567890L); 123 | assertThat(longProperty(conf, property)).isEqualTo(18000L); 124 | } 125 | 126 | @Test 127 | public void longPropertyReturnsDefaultValue() { 128 | when(property.key()).thenReturn("unset"); 129 | when(property.defaultValue()).thenReturn(1234567890L); 130 | assertThat(longProperty(conf, property)).isEqualTo(1234567890L); 131 | } 132 | 133 | @Test(expected = NullPointerException.class) 134 | public void longPropertyThrowsNullPointerException() { 135 | when(property.key()).thenReturn("unset"); 136 | longProperty(conf, property); 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /shunting-yard-common/src/test/java/com/expediagroup/shuntingyard/common/io/jackson/InvalidTBase.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.common.io.jackson; 17 | 18 | import org.apache.thrift.TBase; 19 | import org.apache.thrift.TException; 20 | import org.apache.thrift.TFieldIdEnum; 21 | import org.apache.thrift.protocol.TProtocol; 22 | 23 | enum Fields implements TFieldIdEnum { 24 | IGNORE; 25 | 26 | @Override 27 | public short getThriftFieldId() { 28 | return 0; 29 | } 30 | 31 | @Override 32 | public String getFieldName() { 33 | return null; 34 | } 35 | } 36 | 37 | class InvalidTBase implements TBase { 38 | private static final long serialVersionUID = 1L; 39 | 40 | @Override 41 | public int compareTo(InvalidTBase o) { 42 | return 0; 43 | } 44 | 45 | @Override 46 | public void read(TProtocol iprot) throws TException {} 47 | 48 | @Override 49 | public void write(TProtocol oprot) throws TException {} 50 | 51 | @Override 52 | public Fields fieldForId(int fieldId) { 53 | return null; 54 | } 55 | 56 | @Override 57 | public boolean isSet(Fields field) { 58 | return false; 59 | } 60 | 61 | @Override 62 | public Object getFieldValue(Fields field) { 63 | return null; 64 | } 65 | 66 | @Override 67 | public void setFieldValue(Fields field, Object value) {} 68 | 69 | @Override 70 | public TBase deepCopy() { 71 | return null; 72 | } 73 | 74 | @Override 75 | public void clear() {} 76 | } 77 | -------------------------------------------------------------------------------- /shunting-yard-common/src/test/java/com/expediagroup/shuntingyard/common/io/jackson/ThriftSerDeUtilsTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.common.io.jackson; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | 20 | import org.apache.hadoop.hive.metastore.api.Table; 21 | import org.apache.thrift.TFieldIdEnum; 22 | import org.junit.Test; 23 | 24 | import com.expediagroup.shuntingyard.common.io.jackson.ThriftSerDeUtils; 25 | 26 | public class ThriftSerDeUtilsTest { 27 | 28 | @Test 29 | public void returnFields() { 30 | TFieldIdEnum[] fields = ThriftSerDeUtils.fields(Table.class); 31 | assertThat(fields).isNotNull().isEqualTo(Table._Fields.values()); 32 | } 33 | 34 | @Test 35 | public void returnNull() { 36 | assertThat(ThriftSerDeUtils.fields(InvalidTBase.class)).isNull(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /shunting-yard-common/src/test/java/com/expediagroup/shuntingyard/common/messaging/BogusMessageReaderFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.common.messaging; 17 | 18 | import org.apache.hadoop.conf.Configuration; 19 | 20 | import com.expediagroup.shuntingyard.common.messaging.MessageReaderFactory; 21 | 22 | import com.expedia.apiary.extensions.receiver.common.messaging.MessageReader; 23 | 24 | class BogusMessageReaderFactory implements MessageReaderFactory { 25 | 26 | public BogusMessageReaderFactory() { 27 | throw new RuntimeException("You cannot construct me!"); 28 | } 29 | 30 | @Override 31 | public MessageReader newInstance(Configuration conf) { 32 | return null; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /shunting-yard-common/src/test/java/com/expediagroup/shuntingyard/common/messaging/MessageReaderFactoryTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.common.messaging; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static org.hamcrest.CoreMatchers.instanceOf; 20 | 21 | import org.junit.Rule; 22 | import org.junit.Test; 23 | import org.junit.rules.ExpectedException; 24 | import org.junit.runner.RunWith; 25 | import org.mockito.junit.MockitoJUnitRunner; 26 | 27 | import com.expediagroup.shuntingyard.common.ShuntingYardException; 28 | import com.expediagroup.shuntingyard.common.messaging.MessageReaderFactory; 29 | 30 | @RunWith(MockitoJUnitRunner.class) 31 | public class MessageReaderFactoryTest { 32 | 33 | public @Rule ExpectedException expectedException = ExpectedException.none(); 34 | 35 | @Test 36 | public void compliant() { 37 | MessageReaderFactory factory = MessageReaderFactory.newInstance(SomeMessageReaderFactory.class.getName()); 38 | assertThat(factory).isNotNull().isExactlyInstanceOf(SomeMessageReaderFactory.class); 39 | } 40 | 41 | @Test 42 | public void classDoesNotImplementMessageReader() { 43 | expectedException.expect(ShuntingYardException.class); 44 | expectedException.expectCause(instanceOf(ClassCastException.class)); 45 | MessageReaderFactory.newInstance(NotReallyAMessageReaderFactory.class.getName()); 46 | } 47 | 48 | @Test 49 | public void bogus() { 50 | expectedException.expect(ShuntingYardException.class); 51 | MessageReaderFactory.newInstance(BogusMessageReaderFactory.class.getName()); 52 | } 53 | 54 | @Test 55 | public void messageReaderClassNotFound() { 56 | expectedException.expect(ShuntingYardException.class); 57 | expectedException.expectCause(instanceOf(ClassNotFoundException.class)); 58 | MessageReaderFactory.newInstance("com.hotels.shunting.yard.common.messaging.UnknownMessageReaderFactory"); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /shunting-yard-common/src/test/java/com/expediagroup/shuntingyard/common/messaging/MessageTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.common.messaging; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static org.assertj.core.api.Assertions.assertThatExceptionOfType; 20 | 21 | import org.junit.Before; 22 | import org.junit.Test; 23 | 24 | import com.expediagroup.shuntingyard.common.messaging.Message; 25 | 26 | public class MessageTest { 27 | 28 | private final Message.Builder objectUnderTest = Message.builder(); 29 | 30 | @Before 31 | public void setUp() { 32 | objectUnderTest.database("test_db"); 33 | objectUnderTest.table("test_table"); 34 | objectUnderTest.payload("test_payload"); 35 | objectUnderTest.timestamp(1515585600000L); 36 | } 37 | 38 | @Test 39 | public void nullPayload() { 40 | objectUnderTest.payload(null); 41 | assertThatExceptionOfType(IllegalArgumentException.class) 42 | .isThrownBy(() -> objectUnderTest.build()) 43 | .withMessage("Parameter 'payload' is required"); 44 | } 45 | 46 | @Test 47 | public void emptyTable() { 48 | objectUnderTest.table(""); 49 | 50 | assertThatExceptionOfType(IllegalArgumentException.class) 51 | .isThrownBy(() -> objectUnderTest.build()) 52 | .withMessage("Parameter 'table' is required"); 53 | } 54 | 55 | @Test 56 | public void typical() { 57 | Message message = objectUnderTest.build(); 58 | 59 | assertThat(message).isNotNull(); 60 | assertThat(message.getDatabase()).isEqualTo("test_db"); 61 | assertThat(message.getPayload()).isEqualTo("test_payload"); 62 | assertThat(message.getQualifiedTableName()).isEqualTo("test_db.test_table"); 63 | assertThat(message.getTable()).isEqualTo("test_table"); 64 | assertThat(message.getTimestamp()).isEqualTo(1515585600000L); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /shunting-yard-common/src/test/java/com/expediagroup/shuntingyard/common/messaging/NotReallyAMessageReaderFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.common.messaging; 17 | 18 | class NotReallyAMessageReaderFactory {} 19 | -------------------------------------------------------------------------------- /shunting-yard-common/src/test/java/com/expediagroup/shuntingyard/common/messaging/SomeMessageReaderFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.common.messaging; 17 | 18 | import org.apache.hadoop.conf.Configuration; 19 | 20 | import com.expediagroup.shuntingyard.common.messaging.MessageReaderFactory; 21 | 22 | import com.expedia.apiary.extensions.receiver.common.messaging.MessageReader; 23 | 24 | class SomeMessageReaderFactory implements MessageReaderFactory { 25 | 26 | @Override 27 | public MessageReader newInstance(Configuration conf) { 28 | return null; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /shunting-yard-common/src/test/java/com/expediagroup/shuntingyard/common/metrics/HiveMetricsHelperTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.common.metrics; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static org.mockito.Mockito.when; 20 | import static org.powermock.api.mockito.PowerMockito.mockStatic; 21 | 22 | import org.apache.hadoop.hive.common.metrics.common.Metrics; 23 | import org.apache.hadoop.hive.common.metrics.common.MetricsFactory; 24 | import org.junit.Before; 25 | import org.junit.Test; 26 | import org.junit.runner.RunWith; 27 | import org.mockito.Mock; 28 | import org.powermock.core.classloader.annotations.PrepareForTest; 29 | import org.powermock.modules.junit4.PowerMockRunner; 30 | 31 | import com.expediagroup.shuntingyard.common.metrics.HiveMetricsHelper; 32 | 33 | @RunWith(PowerMockRunner.class) 34 | @PrepareForTest(MetricsFactory.class) 35 | public class HiveMetricsHelperTest { 36 | 37 | private @Mock Metrics metrics; 38 | 39 | @Before 40 | public void init() throws Exception { 41 | mockStatic(MetricsFactory.class); 42 | } 43 | 44 | @Test 45 | public void nullMetricsFactory() { 46 | when(MetricsFactory.getInstance()).thenReturn(null); 47 | assertThat(HiveMetricsHelper.incrementCounter("name")).isNotPresent(); 48 | } 49 | 50 | @Test 51 | public void exceptionInIncrementCounter() { 52 | when(MetricsFactory.getInstance()).thenReturn(metrics); 53 | when(metrics.incrementCounter("name")).thenThrow(RuntimeException.class); 54 | assertThat(HiveMetricsHelper.incrementCounter("name")).isNotPresent(); 55 | } 56 | 57 | @Test 58 | public void incrementCounter() { 59 | when(MetricsFactory.getInstance()).thenReturn(metrics); 60 | when(metrics.incrementCounter("name")).thenReturn(123L); 61 | assertThat(HiveMetricsHelper.incrementCounter("name")).get().isEqualTo(123L); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /shunting-yard-common/src/test/java/com/expediagroup/shuntingyard/common/receiver/thrift/ThriftListenerUtilsTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.common.receiver.thrift; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | 20 | import java.util.ArrayList; 21 | import java.util.Arrays; 22 | import java.util.HashMap; 23 | import java.util.List; 24 | 25 | import org.apache.hadoop.hive.common.ObjectPair; 26 | import org.apache.hadoop.hive.metastore.api.FieldSchema; 27 | import org.apache.hadoop.hive.metastore.api.Partition; 28 | import org.apache.hadoop.hive.metastore.api.StorageDescriptor; 29 | import org.apache.hadoop.hive.metastore.api.Table; 30 | import org.junit.Rule; 31 | import org.junit.Test; 32 | import org.junit.rules.ExpectedException; 33 | import org.junit.runner.RunWith; 34 | import org.mockito.junit.MockitoJUnitRunner; 35 | 36 | import com.expediagroup.shuntingyard.common.receiver.thrift.ThriftListenerUtils; 37 | import com.google.common.base.Joiner; 38 | 39 | @RunWith(MockitoJUnitRunner.class) 40 | public class ThriftListenerUtilsTest { 41 | 42 | private static final String DATABASE = "test_db"; 43 | private static final String TABLE = "test_table"; 44 | private static final List DATA_COLS = Arrays.asList(new FieldSchema("col", "integer", "comment")); 45 | 46 | private static Table createTable(FieldSchema... partCols) { 47 | Table table = new Table(); 48 | table.setDbName(DATABASE); 49 | table.setTableName(TABLE); 50 | table.setPartitionKeys(Arrays.asList(partCols)); 51 | table.setSd(new StorageDescriptor()); 52 | List cols = new ArrayList<>(DATA_COLS); 53 | table.getSd().setCols(cols); 54 | table.getSd().setLocation("hdfs://server:8020/foo/bar/"); 55 | table.setParameters(new HashMap()); 56 | table.getParameters().put("foo", "bar"); 57 | return table; 58 | } 59 | 60 | private static Partition createPartition(String... values) { 61 | Partition partition = new Partition(); 62 | partition.setDbName(DATABASE); 63 | partition.setTableName(TABLE); 64 | partition.setValues(Arrays.asList(values)); 65 | partition.setSd(new StorageDescriptor()); 66 | partition.getSd().setCols(DATA_COLS); 67 | partition.getSd().setLocation("hdfs://server:8020/foo/bar/" + Joiner.on("/").join(values)); 68 | partition.setParameters(new HashMap()); 69 | partition.getParameters().put("foo", "bazz"); 70 | return partition; 71 | } 72 | 73 | public @Rule ExpectedException exception = ExpectedException.none(); 74 | 75 | @Test 76 | public void singlePartitionCol() { 77 | List> pairs = ThriftListenerUtils 78 | .toObjectPairs(createTable(new FieldSchema("part", "string", null)), 79 | Arrays.asList(createPartition("a"), createPartition("b"))); 80 | assertThat(pairs.size()).isEqualTo(2); 81 | for (ObjectPair pair : pairs) { 82 | assertThat(pair.getFirst()).isEqualTo(1); 83 | } 84 | } 85 | 86 | @Test 87 | public void twoPartitionCols() { 88 | List> pairs = ThriftListenerUtils 89 | .toObjectPairs(createTable(new FieldSchema("part_1", "string", null), new FieldSchema("part_2", "int", null)), 90 | Arrays.asList(createPartition("a", "1"), createPartition("b", "2"))); 91 | assertThat(pairs.size()).isEqualTo(2); 92 | for (ObjectPair pair : pairs) { 93 | assertThat(pair.getFirst()).isEqualTo(2); 94 | } 95 | } 96 | 97 | @Test(expected = RuntimeException.class) 98 | public void moreValuesThanPartitionCols() { 99 | ThriftListenerUtils 100 | .toObjectPairs(createTable(new FieldSchema("part", "string", null)), Arrays.asList(createPartition("a", "1"))); 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /shunting-yard-receiver/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | 5 | com.expediagroup 6 | shunting-yard 7 | 3.2.1-SNAPSHOT 8 | 9 | 10 | shunting-yard-receiver 11 | pom 12 | 13 | 14 | shunting-yard-receiver-sqs 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /shunting-yard-receiver/shunting-yard-receiver-sqs/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | 5 | com.expediagroup 6 | shunting-yard-receiver 7 | 3.2.1-SNAPSHOT 8 | 9 | 10 | shunting-yard-receiver-sqs 11 | 12 | 13 | 2.0.3.RELEASE 14 | 15 | 16 | 17 | 18 | com.expediagroup 19 | shunting-yard-common 20 | ${project.version} 21 | 22 | 23 | 24 | 25 | com.expedia.apiary 26 | apiary-receiver-sqs 27 | ${apiary.receiver.sqs.verison} 28 | 29 | 30 | 31 | 32 | com.amazonaws 33 | aws-java-sdk-sqs 34 | 35 | 36 | 37 | 38 | org.apache.hadoop 39 | hadoop-common 40 | 41 | 42 | 43 | 44 | org.apache.hive 45 | hive-metastore 46 | 47 | 48 | 49 | 50 | junit 51 | junit 52 | 53 | 54 | org.assertj 55 | assertj-core 56 | 57 | 58 | org.mockito 59 | mockito-core 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /shunting-yard-receiver/shunting-yard-receiver-sqs/src/main/java/com/expediagroup/shuntingyard/receiver/sqs/SqsConsumerProperty.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.receiver.sqs; 17 | 18 | import static com.amazonaws.regions.Regions.US_WEST_2; 19 | 20 | import com.expediagroup.shuntingyard.common.Property; 21 | 22 | public enum SqsConsumerProperty implements Property { 23 | QUEUE("queue", null), 24 | REGION("region", US_WEST_2.getName()), 25 | WAIT_TIME_SECONDS("wait.time.seconds", 10), 26 | AWS_ACCESS_KEY("aws.access.key", null), 27 | AWS_SECRET_KEY("aws.secret.key", null); 28 | 29 | private static final String PROPERTY_PREFIX = "sqs."; 30 | 31 | private final String unPrefixedKey; 32 | private final Object defaultValue; 33 | 34 | private SqsConsumerProperty(String unPrefixedKey, Object defaultValue) { 35 | this.unPrefixedKey = unPrefixedKey; 36 | this.defaultValue = defaultValue; 37 | } 38 | 39 | @Override 40 | public String key() { 41 | return new StringBuffer(PROPERTY_PREFIX).append(unPrefixedKey).toString(); 42 | } 43 | 44 | @Override 45 | public String unPrefixedKey() { 46 | return unPrefixedKey; 47 | } 48 | 49 | @Override 50 | public Object defaultValue() { 51 | return defaultValue; 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return key(); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /shunting-yard-receiver/shunting-yard-receiver-sqs/src/main/java/com/expediagroup/shuntingyard/receiver/sqs/SqsReceiverUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.receiver.sqs; 17 | 18 | import static com.expediagroup.shuntingyard.common.Preconditions.checkNotNull; 19 | import static com.expediagroup.shuntingyard.common.PropertyUtils.intProperty; 20 | import static com.expediagroup.shuntingyard.common.PropertyUtils.stringProperty; 21 | import static com.expediagroup.shuntingyard.receiver.sqs.SqsConsumerProperty.QUEUE; 22 | import static com.expediagroup.shuntingyard.receiver.sqs.SqsConsumerProperty.REGION; 23 | import static com.expediagroup.shuntingyard.receiver.sqs.SqsConsumerProperty.WAIT_TIME_SECONDS; 24 | 25 | import org.apache.hadoop.conf.Configuration; 26 | 27 | import com.amazonaws.auth.AWSCredentialsProvider; 28 | import com.amazonaws.auth.AWSCredentialsProviderChain; 29 | import com.amazonaws.auth.EC2ContainerCredentialsProviderWrapper; 30 | import com.amazonaws.auth.EnvironmentVariableCredentialsProvider; 31 | import com.amazonaws.auth.InstanceProfileCredentialsProvider; 32 | import com.expediagroup.shuntingyard.receiver.sqs.aws.ConfigurationAwsCredentialsProvider; 33 | 34 | public final class SqsReceiverUtils { 35 | 36 | private SqsReceiverUtils() {} 37 | 38 | public static String queue(Configuration conf) { 39 | return checkNotNull(stringProperty(conf, QUEUE), "Property " + QUEUE + " is not set"); 40 | } 41 | 42 | public static String region(Configuration conf) { 43 | return checkNotNull(stringProperty(conf, REGION), "Property " + REGION + " is not set"); 44 | } 45 | 46 | public static int waitTimeSeconds(Configuration conf) { 47 | return checkNotNull(intProperty(conf, WAIT_TIME_SECONDS), "Property " + WAIT_TIME_SECONDS + " is not set"); 48 | } 49 | 50 | public static AWSCredentialsProvider credentials(final Configuration conf) { 51 | return new AWSCredentialsProviderChain(new EnvironmentVariableCredentialsProvider(), 52 | new InstanceProfileCredentialsProvider(false), new EC2ContainerCredentialsProviderWrapper(), 53 | new ConfigurationAwsCredentialsProvider(conf)); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /shunting-yard-receiver/shunting-yard-receiver-sqs/src/main/java/com/expediagroup/shuntingyard/receiver/sqs/aws/ConfigurationAwsCredentialsProvider.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.receiver.sqs.aws; 17 | 18 | import static com.expediagroup.shuntingyard.common.Preconditions.checkNotNull; 19 | import static com.expediagroup.shuntingyard.common.PropertyUtils.stringProperty; 20 | import static com.expediagroup.shuntingyard.receiver.sqs.SqsConsumerProperty.AWS_ACCESS_KEY; 21 | import static com.expediagroup.shuntingyard.receiver.sqs.SqsConsumerProperty.AWS_SECRET_KEY; 22 | 23 | import java.io.IOException; 24 | 25 | import org.apache.hadoop.conf.Configuration; 26 | 27 | import com.amazonaws.auth.AWSCredentials; 28 | import com.amazonaws.auth.AWSCredentialsProvider; 29 | import com.amazonaws.auth.BasicAWSCredentials; 30 | import com.expediagroup.shuntingyard.receiver.sqs.SqsConsumerProperty; 31 | 32 | public class ConfigurationAwsCredentialsProvider implements AWSCredentialsProvider { 33 | 34 | private final Configuration conf; 35 | 36 | public ConfigurationAwsCredentialsProvider(Configuration conf) { 37 | this.conf = conf; 38 | } 39 | 40 | @Override 41 | public AWSCredentials getCredentials() { 42 | return new BasicAWSCredentials(secret(AWS_ACCESS_KEY), secret(AWS_SECRET_KEY)); 43 | } 44 | 45 | @Override 46 | public void refresh() {} 47 | 48 | private String secret(SqsConsumerProperty property) { 49 | String key = checkNotNull(stringProperty(conf, property), "Property " + property + " is not set"); 50 | try { 51 | return new String(conf.getPassword(key)); 52 | } catch (IOException e) { 53 | throw new RuntimeException("Unable to read property " + property + " from configuration", e); 54 | } 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /shunting-yard-receiver/shunting-yard-receiver-sqs/src/main/java/com/expediagroup/shuntingyard/receiver/sqs/messaging/SqsMessageReaderFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.receiver.sqs.messaging; 17 | 18 | import static com.expediagroup.shuntingyard.receiver.sqs.SqsReceiverUtils.credentials; 19 | import static com.expediagroup.shuntingyard.receiver.sqs.SqsReceiverUtils.queue; 20 | import static com.expediagroup.shuntingyard.receiver.sqs.SqsReceiverUtils.region; 21 | import static com.expediagroup.shuntingyard.receiver.sqs.SqsReceiverUtils.waitTimeSeconds; 22 | 23 | import org.apache.hadoop.conf.Configuration; 24 | 25 | import com.amazonaws.services.sqs.AmazonSQS; 26 | import com.amazonaws.services.sqs.AmazonSQSClientBuilder; 27 | import com.expediagroup.shuntingyard.common.messaging.MessageReaderFactory; 28 | 29 | import com.expedia.apiary.extensions.receiver.common.messaging.MessageReader; 30 | import com.expedia.apiary.extensions.receiver.sqs.messaging.SqsMessageReader; 31 | 32 | public class SqsMessageReaderFactory implements MessageReaderFactory { 33 | 34 | @Override 35 | public MessageReader newInstance(Configuration conf) { 36 | AmazonSQS consumer = AmazonSQSClientBuilder.standard() 37 | .withRegion(region(conf)) 38 | .withCredentials(credentials(conf)) 39 | .build(); 40 | 41 | return new SqsMessageReader.Builder(queue(conf)) 42 | .withConsumer(consumer) 43 | .withWaitTimeSeconds(waitTimeSeconds(conf)) 44 | .build(); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /shunting-yard-receiver/shunting-yard-receiver-sqs/src/test/java/com/expediagroup/shuntingyard/receiver/sqs/SqsConsumerPropertyTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.receiver.sqs; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | 20 | import static com.expediagroup.shuntingyard.receiver.sqs.SqsConsumerProperty.AWS_ACCESS_KEY; 21 | import static com.expediagroup.shuntingyard.receiver.sqs.SqsConsumerProperty.AWS_SECRET_KEY; 22 | import static com.expediagroup.shuntingyard.receiver.sqs.SqsConsumerProperty.QUEUE; 23 | import static com.expediagroup.shuntingyard.receiver.sqs.SqsConsumerProperty.REGION; 24 | import static com.expediagroup.shuntingyard.receiver.sqs.SqsConsumerProperty.WAIT_TIME_SECONDS; 25 | 26 | import org.junit.Test; 27 | 28 | public class SqsConsumerPropertyTest { 29 | 30 | private static String prefixedKey(String key) { 31 | return "sqs." + key; 32 | } 33 | 34 | @Test 35 | public void numberOfProperties() { 36 | assertThat(SqsConsumerProperty.values().length).isEqualTo(5); 37 | } 38 | 39 | @Test 40 | public void queue() { 41 | assertThat(QUEUE.unPrefixedKey()).isEqualTo("queue"); 42 | assertThat(QUEUE.key()).isEqualTo(prefixedKey("queue")); 43 | assertThat(QUEUE.defaultValue()).isNull(); 44 | } 45 | 46 | @Test 47 | public void region() { 48 | assertThat(REGION.unPrefixedKey()).isEqualTo("region"); 49 | assertThat(REGION.key()).isEqualTo(prefixedKey("region")); 50 | assertThat(REGION.defaultValue()).isEqualTo("us-west-2"); 51 | } 52 | 53 | @Test 54 | public void waitTimeSeconds() { 55 | assertThat(WAIT_TIME_SECONDS.unPrefixedKey()).isEqualTo("wait.time.seconds"); 56 | assertThat(WAIT_TIME_SECONDS.key()).isEqualTo(prefixedKey("wait.time.seconds")); 57 | assertThat(WAIT_TIME_SECONDS.defaultValue()).isEqualTo(10); 58 | } 59 | 60 | @Test 61 | public void awsAccessKey() { 62 | assertThat(AWS_ACCESS_KEY.unPrefixedKey()).isEqualTo("aws.access.key"); 63 | assertThat(AWS_ACCESS_KEY.key()).isEqualTo(prefixedKey("aws.access.key")); 64 | assertThat(AWS_ACCESS_KEY.defaultValue()).isNull(); 65 | } 66 | 67 | @Test 68 | public void awsSecretKey() { 69 | assertThat(AWS_SECRET_KEY.unPrefixedKey()).isEqualTo("aws.secret.key"); 70 | assertThat(AWS_SECRET_KEY.key()).isEqualTo(prefixedKey("aws.secret.key")); 71 | assertThat(AWS_SECRET_KEY.defaultValue()).isNull(); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /shunting-yard-receiver/shunting-yard-receiver-sqs/src/test/java/com/expediagroup/shuntingyard/receiver/sqs/SqsReceiverUtilsTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.receiver.sqs; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | 20 | import static com.expediagroup.shuntingyard.receiver.sqs.SqsConsumerProperty.QUEUE; 21 | import static com.expediagroup.shuntingyard.receiver.sqs.SqsConsumerProperty.REGION; 22 | import static com.expediagroup.shuntingyard.receiver.sqs.SqsReceiverUtils.queue; 23 | import static com.expediagroup.shuntingyard.receiver.sqs.SqsReceiverUtils.region; 24 | 25 | import org.apache.hadoop.conf.Configuration; 26 | import org.junit.Test; 27 | 28 | public class SqsReceiverUtilsTest { 29 | 30 | private final Configuration conf = new Configuration(); 31 | 32 | @Test 33 | public void queueIsNotNull() { 34 | conf.set(QUEUE.key(), "queue"); 35 | assertThat(queue(conf)).isEqualTo("queue"); 36 | } 37 | 38 | @Test(expected = IllegalArgumentException.class) 39 | public void queueIsNull() { 40 | conf.set(QUEUE.key(), null); 41 | queue(conf); 42 | } 43 | 44 | @Test 45 | public void regionIsNotNull() { 46 | conf.set(REGION.key(), "region"); 47 | assertThat(region(conf)).isEqualTo("region"); 48 | } 49 | 50 | @Test(expected = IllegalArgumentException.class) 51 | public void regionIsNull() { 52 | conf.set(REGION.key(), null); 53 | region(conf); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /shunting-yard-receiver/shunting-yard-receiver-sqs/src/test/java/com/expediagroup/shuntingyard/receiver/sqs/aws/ConfigurationAwsCredentialsProviderTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.receiver.sqs.aws; 17 | 18 | import static java.lang.Boolean.TRUE; 19 | 20 | import static org.assertj.core.api.Assertions.assertThat; 21 | import static org.mockito.Mockito.when; 22 | 23 | import static com.expediagroup.shuntingyard.receiver.sqs.SqsConsumerProperty.AWS_ACCESS_KEY; 24 | import static com.expediagroup.shuntingyard.receiver.sqs.SqsConsumerProperty.AWS_SECRET_KEY; 25 | 26 | import java.io.IOException; 27 | 28 | import org.apache.hadoop.conf.Configuration; 29 | import org.apache.hadoop.security.alias.CredentialProvider; 30 | import org.junit.Test; 31 | import org.junit.runner.RunWith; 32 | import org.mockito.Spy; 33 | import org.mockito.junit.MockitoJUnitRunner; 34 | 35 | import com.amazonaws.auth.AWSCredentials; 36 | import com.expediagroup.shuntingyard.receiver.sqs.aws.ConfigurationAwsCredentialsProvider; 37 | 38 | @RunWith(MockitoJUnitRunner.class) 39 | public class ConfigurationAwsCredentialsProviderTest { 40 | 41 | private final @Spy Configuration conf = new Configuration(); 42 | 43 | @Test 44 | public void credentialsFallbackToPlainTextConfig() { 45 | conf.set(CredentialProvider.CLEAR_TEXT_FALLBACK, TRUE.toString()); 46 | conf.set(AWS_ACCESS_KEY.key(), "aws_access_key"); 47 | conf.set("aws_access_key", "access"); 48 | conf.set(AWS_SECRET_KEY.key(), "aws_secret_key"); 49 | conf.set("aws_secret_key", "secret"); 50 | ConfigurationAwsCredentialsProvider provider = new ConfigurationAwsCredentialsProvider(conf); 51 | AWSCredentials creds = provider.getCredentials(); 52 | assertThat(creds.getAWSAccessKeyId()).isEqualTo("access"); 53 | assertThat(creds.getAWSSecretKey()).isEqualTo("secret"); 54 | } 55 | 56 | @Test(expected = RuntimeException.class) 57 | public void credentialsCannotBeRead() throws Exception { 58 | conf.set(AWS_ACCESS_KEY.key(), "aws_access_key"); 59 | conf.set(AWS_SECRET_KEY.key(), "aws_secret_key"); 60 | when(conf.getPassword("aws_access_key")).thenThrow(IOException.class); 61 | new ConfigurationAwsCredentialsProvider(conf).getCredentials(); 62 | } 63 | 64 | @Test(expected = RuntimeException.class) 65 | public void credentialsFailure() { 66 | new ConfigurationAwsCredentialsProvider(conf).getCredentials(); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/ConfigFileValidationApplicationListener.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec; 17 | 18 | import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; 19 | import org.springframework.context.ApplicationListener; 20 | 21 | class ConfigFileValidationApplicationListener implements ApplicationListener { 22 | 23 | @Override 24 | public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { 25 | String configFilesString = event.getEnvironment().getProperty("spring.config.location"); 26 | ConfigFileValidator.validate(configFilesString); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/ConfigFileValidationException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | import org.springframework.validation.ObjectError; 22 | 23 | import com.expediagroup.shuntingyard.common.ShuntingYardException; 24 | 25 | public class ConfigFileValidationException extends ShuntingYardException { 26 | 27 | private static final long serialVersionUID = 1L; 28 | 29 | private final List errors = new ArrayList<>(); 30 | 31 | public ConfigFileValidationException(List errors) { 32 | super("Error reading config file(s)."); 33 | for (String error : errors) { 34 | this.errors.add(new ObjectError("config-file-error", error)); 35 | } 36 | } 37 | 38 | List getErrors() { 39 | return errors; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/ConfigFileValidator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec; 17 | 18 | import java.io.File; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | import org.apache.commons.lang3.StringUtils; 23 | 24 | import com.google.common.base.Splitter; 25 | 26 | public class ConfigFileValidator { 27 | public static void validate(String configFileLocation) { 28 | List errors = new ArrayList<>(); 29 | if (StringUtils.isEmpty(configFileLocation)) { 30 | errors.add("No config file was specified."); 31 | } else { 32 | for (String configFileString : Splitter.on(',').split(configFileLocation)) { 33 | File configFile = new File(configFileString); 34 | if (!configFile.exists()) { 35 | errors.add("Config file " + configFileString + " does not exist."); 36 | } else if (!configFile.isFile()) { 37 | errors.add("Config file " + configFileString + " is a directory."); 38 | } else if (!configFile.canRead()) { 39 | errors.add("Config file " + configFileString + " cannot be read."); 40 | } 41 | } 42 | } 43 | if (!errors.isEmpty()) { 44 | throw new ConfigFileValidationException(errors); 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/Constants.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec; 17 | 18 | public final class Constants { 19 | 20 | public static final String CIRCUS_TRAIN_HOME_ENV_VAR = "CIRCUS_TRAIN_HOME"; 21 | public static final String CIRCUS_TRAIN_HOME_SCRIPT = "bin/circus-train.sh"; 22 | 23 | private Constants() {} 24 | 25 | } 26 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/MetaStoreEventReplication.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec; 17 | 18 | import static com.expediagroup.shuntingyard.replicator.exec.Constants.CIRCUS_TRAIN_HOME_ENV_VAR; 19 | 20 | import java.io.IOException; 21 | import java.util.List; 22 | import java.util.Map; 23 | 24 | import org.apache.commons.exec.environment.EnvironmentUtils; 25 | import org.slf4j.Logger; 26 | import org.slf4j.LoggerFactory; 27 | import org.springframework.beans.factory.BeanCreationException; 28 | import org.springframework.boot.SpringApplication; 29 | import org.springframework.boot.autoconfigure.SpringBootApplication; 30 | import org.springframework.boot.builder.SpringApplicationBuilder; 31 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 32 | import org.springframework.context.annotation.Bean; 33 | import org.springframework.validation.BindException; 34 | import org.springframework.validation.ObjectError; 35 | 36 | import com.codahale.metrics.MetricRegistry; 37 | 38 | @SpringBootApplication 39 | @EnableConfigurationProperties 40 | public class MetaStoreEventReplication { 41 | private static final Logger LOG = LoggerFactory.getLogger(MetaStoreEventReplication.class); 42 | 43 | public static void main(String[] args) throws Exception { 44 | // below is output *before* logging is configured so will appear on console 45 | logVersionInfo(); 46 | 47 | verifyCircusTrainInstallation(); 48 | 49 | int exitCode = -1; 50 | try { 51 | exitCode = SpringApplication 52 | .exit(new SpringApplicationBuilder(MetaStoreEventReplication.class) 53 | .properties("spring.config.location:${config:null}") 54 | .properties("instance.home:${user.home}") 55 | .properties("instance.name:${replica-catalog.name}") 56 | .properties("instance.workspace:${instance.home}/.shunting-yard") 57 | .registerShutdownHook(true) 58 | .listeners(new ConfigFileValidationApplicationListener()) 59 | .build() 60 | .run(args)); 61 | } catch (ConfigFileValidationException e) { 62 | LOG.error(e.getMessage(), e); 63 | printHelp(e.getErrors()); 64 | } catch (BeanCreationException e) { 65 | LOG.error(e.getMessage(), e); 66 | if (e.getMostSpecificCause() instanceof BindException) { 67 | printHelp(((BindException) e.getMostSpecificCause()).getAllErrors()); 68 | } 69 | } 70 | 71 | System.exit(exitCode); 72 | } 73 | 74 | private static void verifyCircusTrainInstallation() throws IOException { 75 | Map env = EnvironmentUtils.getProcEnvironment(); 76 | String circusTrainHome = env.get(CIRCUS_TRAIN_HOME_ENV_VAR); 77 | if (circusTrainHome == null || circusTrainHome.isEmpty()) { 78 | throw new RuntimeException("The enviroment variable " + CIRCUS_TRAIN_HOME_ENV_VAR + " is not set"); 79 | } 80 | 81 | } 82 | 83 | private static void printHelp(List allErrors) { 84 | System.out.println(new MetaStoreEventReplicationHelp(allErrors)); 85 | } 86 | 87 | MetaStoreEventReplication() { 88 | // below is output *after* logging is configured so will appear in log file 89 | logVersionInfo(); 90 | } 91 | 92 | private static void logVersionInfo() { 93 | // ManifestAttributes manifestAttributes = new ManifestAttributes(CircusTrain.class); 94 | // LOG.info("{}", manifestAttributes); 95 | LOG.info("MetaStoreEventReplication"); 96 | } 97 | 98 | @Bean 99 | MetricRegistry runningMetricRegistry() { 100 | return new MetricRegistry(); 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/MetaStoreEventReplicationHelp.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec; 17 | 18 | import java.util.List; 19 | 20 | import javax.annotation.Nonnull; 21 | 22 | import org.springframework.validation.ObjectError; 23 | 24 | import com.google.common.base.Function; 25 | import com.google.common.base.Joiner; 26 | import com.google.common.collect.FluentIterable; 27 | 28 | class MetaStoreEventReplicationHelp { 29 | 30 | private static final String TAB = "\t"; 31 | 32 | final static Function OBJECT_ERROR_TO_TABBED_MESSAGE = new Function() { 33 | @Override 34 | public String apply(@Nonnull ObjectError error) { 35 | return TAB + error.getDefaultMessage(); 36 | } 37 | }; 38 | 39 | private final List errors; 40 | 41 | MetaStoreEventReplicationHelp(List errors) { 42 | this.errors = errors; 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | Iterable errorMessages = FluentIterable.from(errors).transform(OBJECT_ERROR_TO_TABBED_MESSAGE); 48 | 49 | StringBuilder help = new StringBuilder(500) 50 | .append("Usage: replicator.sh --config=[,,...]") 51 | .append(System.lineSeparator()) 52 | .append("Errors found in the provided configuration file:") 53 | .append(System.lineSeparator()) 54 | .append(Joiner.on(System.lineSeparator()).join(errorMessages)) 55 | .append(System.lineSeparator()) 56 | .append("Configuration file help:") 57 | .append(System.lineSeparator()) 58 | .append(TAB); 59 | return help.toString(); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/app/ConfigurationVariables.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.app; 17 | 18 | import com.expediagroup.shuntingyard.common.Property; 19 | 20 | public enum ConfigurationVariables implements Property { 21 | 22 | WORKSPACE("workspace", null), 23 | CT_CONFIG("ct-config", null); 24 | 25 | private static final String PROPERTY_PREFIX = "com.hotels.shunting.yard.replicator.exec.app."; 26 | 27 | private final String unPrefixedKey; 28 | private final Object defaultValue; 29 | 30 | private ConfigurationVariables(String unPrefixedKey, Object defaultValue) { 31 | this.unPrefixedKey = unPrefixedKey; 32 | this.defaultValue = defaultValue; 33 | } 34 | 35 | @Override 36 | public String key() { 37 | return new StringBuffer(PROPERTY_PREFIX).append(unPrefixedKey).toString(); 38 | } 39 | 40 | @Override 41 | public String unPrefixedKey() { 42 | return unPrefixedKey; 43 | } 44 | 45 | @Override 46 | public Object defaultValue() { 47 | return defaultValue; 48 | } 49 | 50 | @Override 51 | public String toString() { 52 | return key(); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/app/ReplicationRunner.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.app; 17 | 18 | import java.util.Optional; 19 | 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | import org.springframework.beans.factory.annotation.Autowired; 23 | import org.springframework.boot.ApplicationArguments; 24 | import org.springframework.boot.ApplicationRunner; 25 | import org.springframework.boot.ExitCodeGenerator; 26 | import org.springframework.stereotype.Component; 27 | 28 | import io.micrometer.core.instrument.Counter; 29 | import io.micrometer.core.instrument.Metrics; 30 | 31 | import com.expediagroup.shuntingyard.common.metrics.MetricsConstant; 32 | import com.expediagroup.shuntingyard.replicator.exec.event.MetaStoreEvent; 33 | import com.expediagroup.shuntingyard.replicator.exec.messaging.MetaStoreEventReader; 34 | import com.expediagroup.shuntingyard.replicator.exec.receiver.ReplicationMetaStoreEventListener; 35 | 36 | @Component 37 | class ReplicationRunner implements ApplicationRunner, ExitCodeGenerator { 38 | private static final Logger log = LoggerFactory.getLogger(ReplicationRunner.class); 39 | 40 | private static final Counter SUCCESS_COUNTER = Metrics.counter(MetricsConstant.RECEIVER_SUCCESSES); 41 | private static final Counter FAILURE_COUNTER = Metrics.counter(MetricsConstant.RECEIVER_FAILURES); 42 | private static final Counter EMPTY_COUNTER = Metrics.counter(MetricsConstant.RECEIVER_EMPTY); 43 | 44 | private final ReplicationMetaStoreEventListener listener; 45 | private final MetaStoreEventReader eventReader; 46 | private boolean running = false; 47 | 48 | @Autowired 49 | ReplicationRunner(MetaStoreEventReader eventReader, ReplicationMetaStoreEventListener listener) { 50 | this.listener = listener; 51 | this.eventReader = eventReader; 52 | } 53 | 54 | @Override 55 | public void run(ApplicationArguments args) { 56 | running = true; 57 | log.info("Starting"); 58 | while (running) { 59 | try { 60 | Optional event = eventReader.read(); 61 | if (event.isPresent()) { 62 | MetaStoreEvent metaStoreEvent = event.get(); 63 | log.info("New event received: {}", metaStoreEvent); 64 | listener.onEvent(metaStoreEvent); 65 | SUCCESS_COUNTER.increment(); 66 | } else { 67 | EMPTY_COUNTER.increment(); 68 | } 69 | } catch (Exception e) { 70 | // ERROR, ShuntingYard and Receiver are keywords 71 | log.error("Error in ShuntingYard Receiver", e); 72 | FAILURE_COUNTER.increment(); 73 | } 74 | } 75 | log.info("Exiting run loop"); 76 | } 77 | 78 | @Override 79 | public int getExitCode() { 80 | return 0; 81 | } 82 | 83 | public void stop() { 84 | log.info("Stopping"); 85 | running = false; 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/conf/EventReceiverConfiguration.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.conf; 17 | 18 | import java.util.Map; 19 | 20 | import org.springframework.boot.context.properties.ConfigurationProperties; 21 | import org.springframework.context.annotation.Configuration; 22 | 23 | import com.expediagroup.shuntingyard.receiver.sqs.messaging.SqsMessageReaderFactory; 24 | 25 | @Configuration 26 | @ConfigurationProperties(prefix = "event-receiver") 27 | public class EventReceiverConfiguration { 28 | 29 | private Map configurationProperties; 30 | private String messageReaderFactoryClass = SqsMessageReaderFactory.class.getName(); 31 | 32 | public Map getConfigurationProperties() { 33 | return configurationProperties; 34 | } 35 | 36 | public void setConfigurationProperties(Map configurationProperties) { 37 | this.configurationProperties = configurationProperties; 38 | } 39 | 40 | public String getMessageReaderFactoryClass() { 41 | return messageReaderFactoryClass; 42 | } 43 | 44 | public void setMessageReaderClass(String messageReaderFactoryClass) { 45 | this.messageReaderFactoryClass = messageReaderFactoryClass; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/conf/OrphanedDataStrategyConfiguration.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.conf; 17 | 18 | import org.springframework.boot.context.properties.ConfigurationProperties; 19 | import org.springframework.context.annotation.Configuration; 20 | 21 | import com.hotels.bdp.circustrain.api.conf.OrphanedDataStrategy; 22 | 23 | @Configuration 24 | @ConfigurationProperties 25 | public class OrphanedDataStrategyConfiguration { 26 | private OrphanedDataStrategy orphanedDataStrategy = OrphanedDataStrategy.HOUSEKEEPING; 27 | 28 | public void setOrphanedDataStrategy(OrphanedDataStrategy orphanedDataStrategy) { 29 | this.orphanedDataStrategy = orphanedDataStrategy; 30 | } 31 | 32 | public OrphanedDataStrategy getOrphanedDataStrategy() { 33 | return orphanedDataStrategy; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/conf/ReplicaCatalog.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.conf; 17 | 18 | import java.util.List; 19 | import java.util.Map; 20 | 21 | import org.hibernate.validator.constraints.NotBlank; 22 | import org.springframework.boot.context.properties.ConfigurationProperties; 23 | import org.springframework.context.annotation.Configuration; 24 | 25 | // TODO this may be replaced with the CT equivalent class 26 | @Configuration 27 | @ConfigurationProperties(prefix = "replica-catalog") 28 | public class ReplicaCatalog { 29 | 30 | private @NotBlank String name; 31 | private @NotBlank String hiveMetastoreUris; 32 | private List siteXml; 33 | private Map configurationProperties; 34 | 35 | public String getName() { 36 | return name; 37 | } 38 | 39 | public void setName(String name) { 40 | this.name = name; 41 | } 42 | 43 | public String getHiveMetastoreUris() { 44 | return hiveMetastoreUris; 45 | } 46 | 47 | public void setHiveMetastoreUris(String hiveMetastoreUris) { 48 | this.hiveMetastoreUris = hiveMetastoreUris; 49 | } 50 | 51 | public List getSiteXml() { 52 | return siteXml; 53 | } 54 | 55 | public void setSiteXml(List siteXml) { 56 | this.siteXml = siteXml; 57 | } 58 | 59 | public Map getConfigurationProperties() { 60 | return configurationProperties; 61 | } 62 | 63 | public void setConfigurationProperties(Map configurationProperties) { 64 | this.configurationProperties = configurationProperties; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/conf/ShuntingYardTableReplicationsMap.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.conf; 17 | 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | import com.expediagroup.shuntingyard.replicator.exec.conf.ct.ShuntingYardTableReplication; 22 | import com.expediagroup.shuntingyard.replicator.exec.conf.ct.ShuntingYardTableReplications; 23 | import com.expediagroup.shuntingyard.replicator.util.TableDatabaseNameJoiner; 24 | 25 | public class ShuntingYardTableReplicationsMap { 26 | private final Map tableReplicationsMap = new HashMap<>(); 27 | 28 | public ShuntingYardTableReplicationsMap(ShuntingYardTableReplications tableReplications) { 29 | if ((tableReplications != null) && (tableReplications.getTableReplications() != null)) { 30 | for (ShuntingYardTableReplication tableReplication : tableReplications.getTableReplications()) { 31 | String key = TableDatabaseNameJoiner 32 | .dotJoin(tableReplication.getSourceTable().getDatabaseName().toLowerCase(), 33 | tableReplication.getSourceTable().getTableName().toLowerCase()); 34 | tableReplicationsMap.put(key, tableReplication); 35 | } 36 | } 37 | } 38 | 39 | public ShuntingYardTableReplication getTableReplication(String dbName, String tableName) { 40 | String key = TableDatabaseNameJoiner.dotJoin(dbName.toLowerCase(), tableName.toLowerCase()); 41 | return tableReplicationsMap.get(key); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/conf/SourceCatalog.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.conf; 17 | 18 | import org.hibernate.validator.constraints.NotBlank; 19 | import org.springframework.boot.context.properties.ConfigurationProperties; 20 | import org.springframework.context.annotation.Configuration; 21 | 22 | @Configuration 23 | @ConfigurationProperties(prefix = "source-catalog") 24 | public class SourceCatalog { 25 | private @NotBlank String name; 26 | private @NotBlank String hiveMetastoreUris; 27 | 28 | public String getName() { 29 | return name; 30 | } 31 | 32 | public void setName(String name) { 33 | this.name = name; 34 | } 35 | 36 | public String getHiveMetastoreUris() { 37 | return hiveMetastoreUris; 38 | } 39 | 40 | public void setHiveMetastoreUris(String hiveMetastoreUris) { 41 | this.hiveMetastoreUris = hiveMetastoreUris; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/conf/SourceTableFilter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.conf; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | import org.springframework.boot.context.properties.ConfigurationProperties; 22 | import org.springframework.context.annotation.Configuration; 23 | 24 | @Configuration 25 | @ConfigurationProperties(prefix = "source-table-filter") 26 | public class SourceTableFilter { 27 | private List tableNames = new ArrayList<>(); 28 | 29 | public List getTableNames() { 30 | return tableNames; 31 | } 32 | 33 | public void setTableNames(List tableNames) { 34 | this.tableNames = tableNames; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/conf/ct/ShuntingYardTableReplication.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.conf.ct; 17 | 18 | import javax.validation.Valid; 19 | import javax.validation.constraints.NotNull; 20 | 21 | import com.hotels.bdp.circustrain.api.conf.ReplicaTable; 22 | import com.hotels.bdp.circustrain.api.conf.SourceTable; 23 | 24 | public class ShuntingYardTableReplication { 25 | private @Valid @NotNull SourceTable sourceTable; 26 | private @Valid @NotNull ReplicaTable replicaTable; 27 | 28 | public SourceTable getSourceTable() { 29 | return sourceTable; 30 | } 31 | 32 | public void setSourceTable(SourceTable sourceTable) { 33 | this.sourceTable = sourceTable; 34 | } 35 | 36 | public ReplicaTable getReplicaTable() { 37 | return replicaTable; 38 | } 39 | 40 | public void setReplicaTable(ReplicaTable replicaTable) { 41 | this.replicaTable = replicaTable; 42 | } 43 | 44 | public String getReplicaDatabaseName() { 45 | SourceTable sourceTable = getSourceTable(); 46 | ReplicaTable replicaTable = getReplicaTable(); 47 | String databaseName = replicaTable.getDatabaseName() != null ? replicaTable.getDatabaseName() 48 | : sourceTable.getDatabaseName(); 49 | return databaseName.toLowerCase(); 50 | } 51 | 52 | public String getReplicaTableName() { 53 | SourceTable sourceTable = getSourceTable(); 54 | ReplicaTable replicaTable = getReplicaTable(); 55 | String tableNameName = replicaTable.getTableName() != null ? replicaTable.getTableName() 56 | : sourceTable.getTableName(); 57 | return tableNameName.toLowerCase(); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/conf/ct/ShuntingYardTableReplications.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.conf.ct; 17 | 18 | import java.util.List; 19 | 20 | import javax.validation.Valid; 21 | 22 | import org.springframework.boot.context.properties.ConfigurationProperties; 23 | import org.springframework.context.annotation.Configuration; 24 | 25 | @Configuration("tableReplications") 26 | @ConfigurationProperties(prefix = "") 27 | public class ShuntingYardTableReplications { 28 | 29 | private @Valid List tableReplications; 30 | 31 | public List getTableReplications() { 32 | return tableReplications; 33 | } 34 | 35 | public void setTableReplications(List tableReplications) { 36 | this.tableReplications = tableReplications; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/event/aggregation/DefaultMetaStoreEventAggregator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.event.aggregation; 17 | 18 | import java.util.ArrayList; 19 | import java.util.LinkedHashMap; 20 | import java.util.List; 21 | import java.util.Map; 22 | 23 | import com.expediagroup.shuntingyard.replicator.exec.event.MetaStoreEvent; 24 | 25 | public class DefaultMetaStoreEventAggregator implements MetaStoreEventAggregator { 26 | 27 | private final DefaultMetaStoreEventCompactor compactor; 28 | 29 | public DefaultMetaStoreEventAggregator() { 30 | this(new DefaultMetaStoreEventCompactor()); 31 | } 32 | 33 | public DefaultMetaStoreEventAggregator(DefaultMetaStoreEventCompactor compactor) { 34 | this.compactor = compactor; 35 | } 36 | 37 | @Override 38 | public List aggregate(List events) { 39 | Map> eventsPerTable = mapEvents(events); 40 | List aggregatedEvents = new ArrayList<>(); 41 | for (List tableEvents : eventsPerTable.values()) { 42 | aggregatedEvents.addAll(compactor.compact(tableEvents)); 43 | } 44 | return aggregatedEvents; 45 | } 46 | 47 | private Map> mapEvents(List events) { 48 | Map> eventsPerTable = new LinkedHashMap<>(); 49 | for (MetaStoreEvent e : events) { 50 | String qName = e.getQualifiedTableName(); 51 | List tableEvents = eventsPerTable.get(qName); 52 | if (tableEvents == null) { 53 | tableEvents = new ArrayList<>(); 54 | eventsPerTable.put(qName, tableEvents); 55 | } 56 | tableEvents.add(e); 57 | } 58 | return eventsPerTable; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/event/aggregation/DefaultMetaStoreEventCompactor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.event.aggregation; 17 | 18 | import java.util.LinkedList; 19 | import java.util.List; 20 | import java.util.ListIterator; 21 | import java.util.function.Predicate; 22 | 23 | import org.slf4j.Logger; 24 | import org.slf4j.LoggerFactory; 25 | 26 | import com.expediagroup.shuntingyard.replicator.exec.event.MetaStoreEvent; 27 | 28 | class DefaultMetaStoreEventCompactor { 29 | private static final Logger log = LoggerFactory.getLogger(DefaultMetaStoreEventCompactor.class); 30 | 31 | private final EventMerger eventMerger; 32 | 33 | DefaultMetaStoreEventCompactor() { 34 | this(new EventMerger()); 35 | } 36 | 37 | DefaultMetaStoreEventCompactor(EventMerger eventMerger) { 38 | this.eventMerger = eventMerger; 39 | } 40 | 41 | /** 42 | * Reduces the number of events to the minimum necessary to execute Circus Train as few times as possible. 43 | *

44 | * This method assumes all the events passed in are for the same table and are sorted in chronological order, i.e. 45 | * they come from the same source table. 46 | *

47 | * Basic rules: 48 | *

    49 | *
  1. Drop events are always kept untouched
  2. 50 | *
  3. Any create/alter event before a drop event is removed from the final list of events
  4. 51 | *
  5. Create and alter events are aggregated together
  6. 52 | *
  7. If an alter table has been issue with the CASCADE the CASCADE option of the aggregated event will on as 53 | * well
  8. 54 | *
  9. Parameters from previous events which are also in later events will be overwritten with the most recent value. 55 | * This statement is also true for the environment context
  10. 56 | *
57 | * 58 | * @param tableEvents a chronological ordered list of events on a single table 59 | * @return compacted set of events 60 | */ 61 | public List compact(List tableEvents) { 62 | LinkedList finalEvents = new LinkedList<>(); 63 | for (MetaStoreEvent e : tableEvents) { 64 | switch (e.getEventType()) { 65 | case DROP_TABLE: 66 | // Keep only previous drop events 67 | processDropTable(finalEvents, e); 68 | break; 69 | case DROP_PARTITION: 70 | mergeOrAdd(finalEvents, e, evt -> evt.isDropEvent()); 71 | break; 72 | case CREATE_TABLE: 73 | case ADD_PARTITION: 74 | case ALTER_PARTITION: 75 | case INSERT: 76 | mergeOrAdd(finalEvents, e, evt -> !evt.isDropEvent()); 77 | break; 78 | default: 79 | log.debug("Unknown event type {}: adding to list of event", e.getEventType()); 80 | finalEvents.add(e); 81 | break; 82 | } 83 | } 84 | return finalEvents; 85 | } 86 | 87 | private void processDropTable(LinkedList finalEvents, MetaStoreEvent event) { 88 | ListIterator it = finalEvents.listIterator(); 89 | while (it.hasNext()) { 90 | MetaStoreEvent e = it.next(); 91 | if (e.isDropEvent()) { 92 | continue; 93 | } 94 | it.remove(); 95 | } 96 | finalEvents.add(event); 97 | } 98 | 99 | private void mergeOrAdd( 100 | LinkedList events, 101 | MetaStoreEvent event, 102 | Predicate matchPredicate) { 103 | ListIterator it = events.listIterator(events.size()); 104 | MetaStoreEvent previousEvent = null; 105 | boolean found = false; 106 | while (!found && it.hasPrevious()) { 107 | previousEvent = it.previous(); 108 | found = matchPredicate.test(previousEvent); 109 | } 110 | if (found && eventMerger.canMerge(previousEvent, event)) { 111 | it.set(eventMerger.merge(previousEvent, event)); 112 | } else { 113 | events.add(event); 114 | } 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/event/aggregation/EventMerger.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.event.aggregation; 17 | 18 | import static java.lang.Boolean.TRUE; 19 | 20 | import static org.apache.hadoop.hive.common.StatsSetupConst.CASCADE; 21 | 22 | import static com.google.common.base.Preconditions.checkArgument; 23 | 24 | import java.util.Objects; 25 | 26 | import com.expediagroup.shuntingyard.replicator.exec.event.MetaStoreEvent; 27 | import com.expediagroup.shuntingyard.replicator.exec.event.MetaStoreEvent.Builder; 28 | import com.google.common.collect.ImmutableMap; 29 | 30 | class EventMerger { 31 | 32 | EventMerger() {} 33 | 34 | public boolean canMerge(MetaStoreEvent a, MetaStoreEvent b) { 35 | return (Objects.equals(a.getEventType(), b.getEventType()) || (!a.isDropEvent() && !b.isDropEvent())) 36 | && Objects.equals(a.getQualifiedTableName(), b.getQualifiedTableName()) 37 | && Objects.equals(a.getPartitionColumns(), b.getPartitionColumns()); 38 | } 39 | 40 | // Note this method creates a new object each time it's invoked and this may end-up generating a lot 41 | // of garbage. However the number of expected events to be merged is low so this should not be an issue. Keep an eye 42 | // on this anyway. 43 | public MetaStoreEvent merge(MetaStoreEvent a, MetaStoreEvent b) { 44 | checkArgument(canMerge(a, b), "Events cannot be merged"); 45 | // Event type of the first event is kept for the new event 46 | Builder builder = MetaStoreEvent 47 | .builder(a.getEventType(), a.getDatabaseName(), a.getTableName(), a.getReplicaDatabaseName(), 48 | a.getReplicaTableName()) 49 | .parameters(a.getParameters()) 50 | .parameters(b.getParameters()) 51 | .environmentContext(a.getEnvironmentContext()) 52 | .environmentContext(b.getEnvironmentContext()); 53 | if (a.getPartitionColumns() != null) { 54 | builder.partitionColumns(a.getPartitionColumns()); 55 | } 56 | if (a.getPartitionValues() != null) { 57 | a.getPartitionValues().stream().forEach(builder::partitionValues); 58 | } 59 | if (b.getPartitionValues() != null) { 60 | b.getPartitionValues().stream().forEach(builder::partitionValues); 61 | } 62 | // Ensure that cascade is preserved 63 | if (a.isCascade() || b.isCascade()) { 64 | builder.environmentContext(ImmutableMap.of(CASCADE, TRUE.toString())); 65 | } 66 | return builder.build(); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/event/aggregation/MetaStoreEventAggregator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.event.aggregation; 17 | 18 | import java.util.List; 19 | 20 | import com.expediagroup.shuntingyard.replicator.exec.event.MetaStoreEvent; 21 | 22 | public interface MetaStoreEventAggregator { 23 | 24 | List aggregate(List events); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/external/Marshaller.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.external; 17 | 18 | import java.io.IOException; 19 | import java.io.OutputStreamWriter; 20 | import java.io.Writer; 21 | 22 | import org.apache.commons.vfs2.FileObject; 23 | import org.apache.commons.vfs2.FileSystemException; 24 | import org.apache.commons.vfs2.FileSystemManager; 25 | import org.apache.commons.vfs2.VFS; 26 | import org.yaml.snakeyaml.Yaml; 27 | 28 | import com.expediagroup.shuntingyard.replicator.yaml.YamlFactory; 29 | import com.google.common.base.Charsets; 30 | 31 | public class Marshaller { 32 | 33 | private final FileSystemManager fsManager; 34 | private final Yaml yaml; 35 | 36 | public Marshaller() { 37 | try { 38 | fsManager = VFS.getManager(); 39 | } catch (FileSystemException e) { 40 | throw new RuntimeException("Unable to initialize Virtual File System", e); 41 | } 42 | yaml = YamlFactory.newYaml(); 43 | } 44 | 45 | public void marshall(String configLocation, CircusTrainConfig config) { 46 | try (FileObject target = fsManager.resolveFile(configLocation); 47 | Writer writer = new OutputStreamWriter(target.getContent().getOutputStream(), Charsets.UTF_8)) { 48 | yaml.dump(config, writer); 49 | } catch (IOException e) { 50 | throw new RuntimeException("Unable to write Circus Train config to '" + configLocation + "'", e); 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/launcher/CircusTrainRunner.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.launcher; 17 | 18 | import static org.apache.commons.exec.environment.EnvironmentUtils.getProcEnvironment; 19 | 20 | import static com.expediagroup.shuntingyard.replicator.exec.Constants.CIRCUS_TRAIN_HOME_ENV_VAR; 21 | import static com.expediagroup.shuntingyard.replicator.exec.Constants.CIRCUS_TRAIN_HOME_SCRIPT; 22 | 23 | import java.io.File; 24 | import java.io.FileOutputStream; 25 | import java.io.IOException; 26 | import java.io.OutputStream; 27 | 28 | import org.apache.commons.exec.CommandLine; 29 | import org.apache.commons.exec.DefaultExecutor; 30 | import org.apache.commons.exec.Executor; 31 | import org.apache.commons.exec.LogOutputStream; 32 | import org.apache.commons.exec.PumpStreamHandler; 33 | import org.apache.commons.io.output.TeeOutputStream; 34 | import org.apache.commons.lang3.StringUtils; 35 | import org.slf4j.Logger; 36 | import org.slf4j.LoggerFactory; 37 | 38 | import com.expediagroup.shuntingyard.replicator.exec.receiver.Context; 39 | 40 | import com.hotels.bdp.circustrain.api.CircusTrainException; 41 | import com.hotels.bdp.circustrain.api.conf.OrphanedDataStrategy; 42 | 43 | public class CircusTrainRunner { 44 | private static final Logger log = LoggerFactory.getLogger(CircusTrainRunner.class); 45 | 46 | public void run(Context context) { 47 | try (OutputStream out = outStream(context); OutputStream err = errStream(context)) { 48 | CommandLine cli = CommandLine 49 | .parse(String.format("%s/%s", getProcEnvironment().get(CIRCUS_TRAIN_HOME_ENV_VAR), CIRCUS_TRAIN_HOME_SCRIPT)); 50 | cli.addArgument("--config=" + context.getConfigLocation()); 51 | 52 | if (!StringUtils.isEmpty(context.getCircusTrainConfigLocation())) { 53 | cli.addArgument("--config=" + context.getCircusTrainConfigLocation()); 54 | } 55 | 56 | String modules = "--modules=replication"; 57 | if (context.getOrphanedDataStrategy() == OrphanedDataStrategy.HOUSEKEEPING) { 58 | modules = modules + ", housekeeping"; 59 | } 60 | cli.addArgument(modules); 61 | log.info("Running Circus Train with orphaned data strategy: {}", context.getOrphanedDataStrategy()); 62 | Executor executor = new DefaultExecutor(); 63 | executor.setWorkingDirectory(new File(context.getWorkspace())); 64 | executor.setStreamHandler(new PumpStreamHandler(out, err)); 65 | 66 | log.debug("Executing {} with environment {}", cli, getProcEnvironment()); 67 | int returnValue = executor.execute(cli, getProcEnvironment()); 68 | log.debug("Command exited with value {} ", returnValue); 69 | if (returnValue != 0) { 70 | throw new CircusTrainException("Circus Train exited with error value " + returnValue); 71 | } 72 | } catch (Throwable e) { 73 | log.error("Unable to execute Circus Train", e); 74 | } 75 | } 76 | 77 | private OutputStream errStream(Context context) throws IOException { 78 | OutputStream outputStream = new FileOutputStream(new File(context.getWorkspace(), "stderr.log")); 79 | return new TeeOutputStream(outputStream, logStream()); 80 | } 81 | 82 | private OutputStream outStream(Context context) throws IOException { 83 | OutputStream log = new FileOutputStream(new File(context.getWorkspace(), "stdout.log")); 84 | return new TeeOutputStream(log, logStream()); 85 | } 86 | 87 | private static LogOutputStream logStream() { 88 | return new LogOutputStream() { 89 | @Override 90 | protected void processLine(String line, int level) { 91 | // TODO 92 | System.out.println(line); 93 | } 94 | }; 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/messaging/FilteringMessageReader.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.messaging; 17 | 18 | import java.io.IOException; 19 | import java.util.Optional; 20 | 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | import com.expediagroup.shuntingyard.replicator.exec.receiver.TableSelector; 25 | 26 | import com.expedia.apiary.extensions.receiver.common.messaging.MessageEvent; 27 | import com.expedia.apiary.extensions.receiver.common.messaging.MessageReader; 28 | 29 | public class FilteringMessageReader implements MessageReader { 30 | 31 | private static final Logger log = LoggerFactory.getLogger(FilteringMessageReader.class); 32 | 33 | private final MessageReader delegate; 34 | private final TableSelector tableSelector; 35 | 36 | public FilteringMessageReader(MessageReader delegate, TableSelector tableSelector) { 37 | this.delegate = delegate; 38 | this.tableSelector = tableSelector; 39 | } 40 | 41 | @Override 42 | public Optional read() { 43 | Optional event = delegate.read(); 44 | if (!event.isPresent()) { 45 | return Optional.empty(); 46 | } 47 | MessageEvent messageEvent = event.get(); 48 | if (tableSelector.canProcess(messageEvent.getEvent())) { 49 | return event; 50 | } else { 51 | delete(messageEvent); 52 | return Optional.empty(); 53 | } 54 | } 55 | 56 | @Override 57 | public void delete(MessageEvent event) { 58 | try { 59 | delegate.delete(event); 60 | log.debug("Message deleted successfully"); 61 | } catch (Exception e) { 62 | log.error("Could not delete message from queue: ", e); 63 | } 64 | } 65 | 66 | @Override 67 | public void close() throws IOException { 68 | delegate.close(); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/messaging/MetaStoreEventReader.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.messaging; 17 | 18 | import java.io.Closeable; 19 | import java.io.IOException; 20 | import java.util.Optional; 21 | 22 | import com.expediagroup.shuntingyard.replicator.exec.event.MetaStoreEvent; 23 | 24 | public interface MetaStoreEventReader extends Closeable { 25 | 26 | Optional read(); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/receiver/Context.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.receiver; 17 | 18 | import com.hotels.bdp.circustrain.api.conf.OrphanedDataStrategy; 19 | 20 | public class Context { 21 | 22 | private final String workspace; 23 | private final String configLocation; 24 | private final String circusTrainConfigLocation; 25 | private final OrphanedDataStrategy orphanedDataStrategy; 26 | 27 | Context(String workspace, String configLocation, String circusTrainConfigLocation, 28 | OrphanedDataStrategy orphanedDataStrategy) { 29 | this.workspace = workspace; 30 | this.configLocation = configLocation; 31 | this.circusTrainConfigLocation = circusTrainConfigLocation; 32 | this.orphanedDataStrategy = orphanedDataStrategy; 33 | } 34 | 35 | public String getWorkspace() { 36 | return workspace; 37 | } 38 | 39 | public String getConfigLocation() { 40 | return configLocation; 41 | } 42 | 43 | public String getCircusTrainConfigLocation() { 44 | return circusTrainConfigLocation; 45 | } 46 | 47 | public OrphanedDataStrategy getOrphanedDataStrategy() { 48 | return orphanedDataStrategy; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/receiver/ReplicationMetaStoreEventListener.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.receiver; 17 | 18 | import com.expediagroup.shuntingyard.replicator.exec.event.MetaStoreEvent; 19 | 20 | /** 21 | * A listener interface for processing {@link MetaStoreEvent}s 22 | */ 23 | public interface ReplicationMetaStoreEventListener { 24 | 25 | void onEvent(MetaStoreEvent event); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/exec/receiver/TableSelector.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.receiver; 17 | 18 | import java.util.List; 19 | 20 | import com.expediagroup.shuntingyard.replicator.exec.conf.SourceTableFilter; 21 | 22 | import com.expedia.apiary.extensions.receiver.common.event.ListenerEvent; 23 | 24 | public class TableSelector { 25 | 26 | private final List tableNames; 27 | 28 | public TableSelector(SourceTableFilter targetReplication) { 29 | this.tableNames = targetReplication.getTableNames(); 30 | } 31 | 32 | public boolean canProcess(ListenerEvent listenerEvent) { 33 | String tableNameToBeProcessed = listenerEvent.getDbName() + "." + listenerEvent.getTableName(); 34 | return tableNames.contains(tableNameToBeProcessed); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/metastore/DefaultMetaStoreClientSupplier.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.metastore; 17 | 18 | import org.apache.hadoop.hive.conf.HiveConf; 19 | 20 | import com.google.common.base.Supplier; 21 | 22 | import com.hotels.hcommon.hive.metastore.client.api.CloseableMetaStoreClient; 23 | import com.hotels.hcommon.hive.metastore.client.api.MetaStoreClientFactory; 24 | 25 | /* 26 | * Based on Circus Train 27 | */ 28 | public class DefaultMetaStoreClientSupplier implements Supplier { 29 | 30 | private final MetaStoreClientFactory metaStoreClientFactory; 31 | private final HiveConf hiveConf; 32 | 33 | public DefaultMetaStoreClientSupplier(HiveConf hiveConf, MetaStoreClientFactory metaStoreClientFactory) { 34 | this.hiveConf = hiveConf; 35 | this.metaStoreClientFactory = metaStoreClientFactory; 36 | } 37 | 38 | @Override 39 | public CloseableMetaStoreClient get() { 40 | return metaStoreClientFactory.newInstance(hiveConf, "shunting-yard"); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/util/TableDatabaseNameJoiner.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.util; 17 | 18 | public class TableDatabaseNameJoiner { 19 | 20 | private TableDatabaseNameJoiner() {} 21 | 22 | public static String dotJoin(String x, String y) { 23 | return String.join(".", x, y); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/yaml/AdvancedPropertyUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.yaml; 17 | 18 | import java.beans.IntrospectionException; 19 | import java.beans.PropertyDescriptor; 20 | import java.beans.Transient; 21 | import java.lang.reflect.Field; 22 | import java.util.Collection; 23 | import java.util.Set; 24 | import java.util.TreeSet; 25 | 26 | import org.yaml.snakeyaml.introspector.BeanAccess; 27 | import org.yaml.snakeyaml.introspector.MethodProperty; 28 | import org.yaml.snakeyaml.introspector.Property; 29 | import org.yaml.snakeyaml.introspector.PropertyUtils; 30 | 31 | import com.google.common.base.CaseFormat; 32 | 33 | public class AdvancedPropertyUtils extends PropertyUtils { 34 | 35 | private boolean allowReadOnlyProperties = false; 36 | 37 | @Override 38 | public Property getProperty(Class type, String name) throws IntrospectionException { 39 | if (name.indexOf('-') > -1) { 40 | name = CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL, name); 41 | } 42 | return super.getProperty(type, name); 43 | } 44 | 45 | @Override 46 | protected Set createPropertySet(Class type, BeanAccess beanAccess) 47 | throws IntrospectionException { 48 | Set properties = new TreeSet<>(); 49 | Collection props = getPropertiesMap(type, beanAccess).values(); 50 | for (Property property : props) { 51 | if (include(property)) { 52 | properties.add(property); 53 | } 54 | } 55 | return properties; 56 | } 57 | 58 | private synchronized boolean include(Property property) { 59 | boolean eligible = property.isReadable() && (allowReadOnlyProperties || property.isWritable()); 60 | if (!eligible) { 61 | return false; 62 | } 63 | if (MethodProperty.class.isAssignableFrom(property.getClass())) { 64 | PropertyDescriptor propertyDescriptor = getPropertyDescriptor((MethodProperty) property); 65 | if (propertyDescriptor != null && isTransient(propertyDescriptor)) { 66 | return false; 67 | } 68 | } 69 | return true; 70 | } 71 | 72 | private synchronized PropertyDescriptor getPropertyDescriptor(MethodProperty methodProperty) { 73 | Field propertyField = null; 74 | try { 75 | propertyField = MethodProperty.class.getDeclaredField("property"); 76 | propertyField.setAccessible(true); 77 | return (PropertyDescriptor) propertyField.get(methodProperty); 78 | } catch (Exception e) { 79 | return null; 80 | } finally { 81 | if (propertyField != null) { 82 | propertyField.setAccessible(false); 83 | } 84 | } 85 | } 86 | 87 | private boolean isTransient(PropertyDescriptor propertyDescriptor) { 88 | return (propertyDescriptor.getReadMethod() != null 89 | && propertyDescriptor.getReadMethod().getAnnotation(Transient.class) != null) 90 | || (propertyDescriptor.getWriteMethod() != null 91 | && propertyDescriptor.getWriteMethod().getAnnotation(Transient.class) != null); 92 | } 93 | 94 | @Override 95 | public void setAllowReadOnlyProperties(boolean allowReadOnlyProperties) { 96 | this.allowReadOnlyProperties = allowReadOnlyProperties; 97 | super.setAllowReadOnlyProperties(allowReadOnlyProperties); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/yaml/AdvancedRepresenter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.yaml; 17 | 18 | import org.yaml.snakeyaml.introspector.Property; 19 | import org.yaml.snakeyaml.nodes.CollectionNode; 20 | import org.yaml.snakeyaml.nodes.MappingNode; 21 | import org.yaml.snakeyaml.nodes.Node; 22 | import org.yaml.snakeyaml.nodes.NodeTuple; 23 | import org.yaml.snakeyaml.nodes.SequenceNode; 24 | import org.yaml.snakeyaml.nodes.Tag; 25 | import org.yaml.snakeyaml.representer.Representer; 26 | 27 | import com.google.common.base.CaseFormat; 28 | 29 | public class AdvancedRepresenter extends Representer { 30 | 31 | @Override 32 | protected NodeTuple representJavaBeanProperty( 33 | Object javaBean, 34 | Property property, 35 | Object propertyValue, 36 | Tag customTag) { 37 | NodeTuple nodeTuple = super.representJavaBeanProperty(javaBean, property, propertyValue, customTag); 38 | Node valueNode = nodeTuple.getValueNode(); 39 | if (Tag.NULL.equals(valueNode.getTag())) { 40 | return null; // skip 'null' values 41 | } 42 | if (valueNode instanceof CollectionNode) { 43 | if (Tag.SEQ.equals(valueNode.getTag())) { 44 | SequenceNode seq = (SequenceNode) valueNode; 45 | if (seq.getValue().isEmpty()) { 46 | return null; // skip empty lists 47 | } 48 | } 49 | if (Tag.MAP.equals(valueNode.getTag())) { 50 | MappingNode seq = (MappingNode) valueNode; 51 | if (seq.getValue().isEmpty()) { 52 | return null; // skip empty maps 53 | } 54 | } 55 | } 56 | 57 | Object name = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, property.getName()); 58 | return new NodeTuple(representData(name), valueNode); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/main/java/com/expediagroup/shuntingyard/replicator/yaml/YamlFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.yaml; 17 | 18 | import org.yaml.snakeyaml.DumperOptions; 19 | import org.yaml.snakeyaml.DumperOptions.FlowStyle; 20 | import org.yaml.snakeyaml.Yaml; 21 | import org.yaml.snakeyaml.constructor.Constructor; 22 | import org.yaml.snakeyaml.introspector.PropertyUtils; 23 | import org.yaml.snakeyaml.nodes.Tag; 24 | import org.yaml.snakeyaml.representer.Representer; 25 | 26 | import com.expediagroup.shuntingyard.replicator.exec.external.CircusTrainConfig; 27 | 28 | public final class YamlFactory { 29 | 30 | private YamlFactory() {} 31 | 32 | public static Yaml newYaml() { 33 | PropertyUtils propertyUtils = new AdvancedPropertyUtils(); 34 | propertyUtils.setSkipMissingProperties(true); 35 | propertyUtils.setAllowReadOnlyProperties(false); 36 | 37 | Constructor constructor = new Constructor(); 38 | 39 | Representer representer = new AdvancedRepresenter(); 40 | representer.setPropertyUtils(propertyUtils); 41 | representer.addClassTag(CircusTrainConfig.class, Tag.MAP); 42 | 43 | DumperOptions dumperOptions = new DumperOptions(); 44 | dumperOptions.setIndent(2); 45 | dumperOptions.setDefaultFlowStyle(FlowStyle.BLOCK); 46 | dumperOptions.setAllowReadOnlyProperties(true); 47 | 48 | return new Yaml(constructor, representer, dumperOptions); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/test/java/com/expediagroup/shuntingyard/replicator/exec/ConfigFileValidationApplicationListenerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec; 17 | 18 | import static org.mockito.Mockito.when; 19 | 20 | import java.io.File; 21 | 22 | import org.junit.Before; 23 | import org.junit.Rule; 24 | import org.junit.Test; 25 | import org.junit.rules.TemporaryFolder; 26 | import org.junit.runner.RunWith; 27 | import org.mockito.Mock; 28 | import org.mockito.junit.MockitoJUnitRunner; 29 | import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; 30 | import org.springframework.core.env.ConfigurableEnvironment; 31 | 32 | import com.expediagroup.shuntingyard.replicator.exec.ConfigFileValidationApplicationListener; 33 | import com.expediagroup.shuntingyard.replicator.exec.ConfigFileValidationException; 34 | import com.google.common.base.Joiner; 35 | 36 | @RunWith(MockitoJUnitRunner.class) 37 | public class ConfigFileValidationApplicationListenerTest { 38 | 39 | private static final String SPRING_CONFIG_LOCATION = "spring.config.location"; 40 | private static final Joiner COMMA_JOINER = Joiner.on(","); 41 | 42 | public @Rule TemporaryFolder tmp = new TemporaryFolder(); 43 | 44 | private @Mock ApplicationEnvironmentPreparedEvent event; 45 | private @Mock ConfigurableEnvironment environment; 46 | 47 | private final ConfigFileValidationApplicationListener listener = new ConfigFileValidationApplicationListener(); 48 | 49 | @Before 50 | public void init() { 51 | when(event.getEnvironment()).thenReturn(environment); 52 | } 53 | 54 | @Test(expected = ConfigFileValidationException.class) 55 | public void configFileLocationIsNotSet() throws Exception { 56 | listener.onApplicationEvent(event); 57 | } 58 | 59 | @Test(expected = ConfigFileValidationException.class) 60 | public void configFileLocationIsBlank() throws Exception { 61 | when(environment.getProperty(SPRING_CONFIG_LOCATION)).thenReturn(" "); 62 | listener.onApplicationEvent(event); 63 | } 64 | 65 | @Test 66 | public void singleFileExists() throws Exception { 67 | File location = tmp.newFile(); 68 | when(environment.getProperty(SPRING_CONFIG_LOCATION)).thenReturn(location.getAbsolutePath()); 69 | listener.onApplicationEvent(event); 70 | } 71 | 72 | @Test(expected = ConfigFileValidationException.class) 73 | public void singleFileDoesNotExist() throws Exception { 74 | File location = new File(tmp.getRoot(), "trick"); 75 | when(environment.getProperty(SPRING_CONFIG_LOCATION)).thenReturn(location.getAbsolutePath()); 76 | listener.onApplicationEvent(event); 77 | } 78 | 79 | @Test(expected = ConfigFileValidationException.class) 80 | public void singleFileIsADirectory() throws Exception { 81 | when(environment.getProperty(SPRING_CONFIG_LOCATION)).thenReturn(tmp.getRoot().getAbsolutePath()); 82 | listener.onApplicationEvent(event); 83 | } 84 | 85 | @Test 86 | public void multipleFilesExist() throws Exception { 87 | File location1 = tmp.newFile(); 88 | File location2 = tmp.newFile(); 89 | when(environment.getProperty(SPRING_CONFIG_LOCATION)) 90 | .thenReturn(COMMA_JOINER.join(location1.getAbsolutePath(), location2.getAbsolutePath())); 91 | listener.onApplicationEvent(event); 92 | } 93 | 94 | @Test(expected = ConfigFileValidationException.class) 95 | public void multipleFilesAndOneDoesNotExist() throws Exception { 96 | File location1 = tmp.newFile(); 97 | File location2 = new File(tmp.getRoot(), "trick"); 98 | when(environment.getProperty(SPRING_CONFIG_LOCATION)) 99 | .thenReturn(COMMA_JOINER.join(location1.getAbsolutePath(), location2.getAbsolutePath())); 100 | listener.onApplicationEvent(event); 101 | } 102 | 103 | @Test(expected = ConfigFileValidationException.class) 104 | public void multipleFilesAndOneIsADirectory() throws Exception { 105 | File location1 = tmp.newFile(); 106 | File location2 = tmp.getRoot(); 107 | when(environment.getProperty(SPRING_CONFIG_LOCATION)) 108 | .thenReturn(COMMA_JOINER.join(location1.getAbsolutePath(), location2.getAbsolutePath())); 109 | listener.onApplicationEvent(event); 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/test/java/com/expediagroup/shuntingyard/replicator/exec/ConfigFileValidatorTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec; 17 | 18 | import java.io.File; 19 | 20 | import org.junit.Rule; 21 | import org.junit.Test; 22 | import org.junit.rules.TemporaryFolder; 23 | 24 | import com.expediagroup.shuntingyard.replicator.exec.ConfigFileValidationException; 25 | import com.expediagroup.shuntingyard.replicator.exec.ConfigFileValidator; 26 | import com.google.common.base.Joiner; 27 | 28 | public class ConfigFileValidatorTest { 29 | 30 | private static final Joiner COMMA_JOINER = Joiner.on(","); 31 | 32 | public @Rule TemporaryFolder tmp = new TemporaryFolder(); 33 | 34 | @Test(expected = ConfigFileValidationException.class) 35 | public void configFileLocationIsNotSet() throws Exception { 36 | ConfigFileValidator.validate(null); 37 | } 38 | 39 | @Test(expected = ConfigFileValidationException.class) 40 | public void configFileLocationIsBlank() throws Exception { 41 | ConfigFileValidator.validate(" "); 42 | } 43 | 44 | @Test 45 | public void singleFileExists() throws Exception { 46 | File location = tmp.newFile(); 47 | ConfigFileValidator.validate(location.getAbsolutePath()); 48 | } 49 | 50 | @Test(expected = ConfigFileValidationException.class) 51 | public void singleFileDoesNotExist() throws Exception { 52 | File location = new File(tmp.getRoot(), "trick"); 53 | ConfigFileValidator.validate(location.getAbsolutePath()); 54 | } 55 | 56 | @Test(expected = ConfigFileValidationException.class) 57 | public void singleFileIsADirectory() throws Exception { 58 | ConfigFileValidator.validate(tmp.getRoot().getAbsolutePath()); 59 | } 60 | 61 | @Test 62 | public void multipleFilesExist() throws Exception { 63 | File location1 = tmp.newFile(); 64 | File location2 = tmp.newFile(); 65 | ConfigFileValidator.validate(COMMA_JOINER.join(location1.getAbsolutePath(), location2.getAbsolutePath())); 66 | } 67 | 68 | @Test(expected = ConfigFileValidationException.class) 69 | public void multipleFilesAndOneDoesNotExist() throws Exception { 70 | File location1 = tmp.newFile(); 71 | File location2 = new File(tmp.getRoot(), "trick"); 72 | ConfigFileValidator.validate(COMMA_JOINER.join(location1.getAbsolutePath(), location2.getAbsolutePath())); 73 | } 74 | 75 | @Test(expected = ConfigFileValidationException.class) 76 | public void multipleFilesAndOneIsADirectory() throws Exception { 77 | File location1 = tmp.newFile(); 78 | File location2 = tmp.getRoot(); 79 | ConfigFileValidator.validate(COMMA_JOINER.join(location1.getAbsolutePath(), location2.getAbsolutePath())); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/test/java/com/expediagroup/shuntingyard/replicator/exec/app/ConfigurationVariablesTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.app; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | 20 | import static com.expediagroup.shuntingyard.replicator.exec.app.ConfigurationVariables.CT_CONFIG; 21 | import static com.expediagroup.shuntingyard.replicator.exec.app.ConfigurationVariables.WORKSPACE; 22 | 23 | import org.junit.Test; 24 | 25 | import com.expediagroup.shuntingyard.replicator.exec.app.ConfigurationVariables; 26 | 27 | public class ConfigurationVariablesTest { 28 | 29 | private static String prefixedKey(String key) { 30 | return "com.hotels.shunting.yard.replicator.exec.app." + key; 31 | } 32 | 33 | @Test 34 | public void numberOfProperties() { 35 | assertThat(ConfigurationVariables.values().length).isEqualTo(2); 36 | } 37 | 38 | @Test 39 | public void workspace() { 40 | assertThat(WORKSPACE.unPrefixedKey()).isEqualTo("workspace"); 41 | assertThat(WORKSPACE.key()).isEqualTo(prefixedKey("workspace")); 42 | assertThat(WORKSPACE.defaultValue()).isNull(); 43 | } 44 | 45 | @Test 46 | public void circusTrainConfig() { 47 | assertThat(CT_CONFIG.unPrefixedKey()).isEqualTo("ct-config"); 48 | assertThat(CT_CONFIG.key()).isEqualTo(prefixedKey("ct-config")); 49 | assertThat(CT_CONFIG.defaultValue()).isNull(); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/test/java/com/expediagroup/shuntingyard/replicator/exec/app/ReplicationRunnerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.app; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static org.mockito.Mockito.atLeastOnce; 20 | import static org.mockito.Mockito.verify; 21 | import static org.mockito.Mockito.verifyZeroInteractions; 22 | import static org.mockito.Mockito.when; 23 | 24 | import java.util.Optional; 25 | import java.util.concurrent.ExecutorService; 26 | import java.util.concurrent.Executors; 27 | import java.util.concurrent.TimeUnit; 28 | 29 | import org.junit.Before; 30 | import org.junit.Test; 31 | import org.junit.runner.RunWith; 32 | import org.mockito.Mock; 33 | import org.mockito.junit.MockitoJUnitRunner; 34 | import org.springframework.boot.ApplicationArguments; 35 | 36 | import com.expediagroup.shuntingyard.replicator.exec.app.ReplicationRunner; 37 | import com.expediagroup.shuntingyard.replicator.exec.event.MetaStoreEvent; 38 | import com.expediagroup.shuntingyard.replicator.exec.messaging.MetaStoreEventReader; 39 | import com.expediagroup.shuntingyard.replicator.exec.receiver.ReplicationMetaStoreEventListener; 40 | 41 | @RunWith(MockitoJUnitRunner.class) 42 | public class ReplicationRunnerTest { 43 | 44 | private @Mock ApplicationArguments args; 45 | private @Mock MetaStoreEventReader eventReader; 46 | private @Mock ReplicationMetaStoreEventListener listener; 47 | private @Mock MetaStoreEvent event; 48 | 49 | private final ExecutorService executor = Executors.newFixedThreadPool(1); 50 | private ReplicationRunner runner; 51 | 52 | @Before 53 | public void init() { 54 | runner = new ReplicationRunner(eventReader, listener); 55 | } 56 | 57 | private class Runner implements Runnable { 58 | 59 | @Override 60 | public void run() { 61 | runner.run(args); 62 | } 63 | 64 | } 65 | 66 | private void runRunner() throws InterruptedException { 67 | executor.execute(new Runner()); 68 | Thread.sleep(500); 69 | runner.stop(); 70 | executor.awaitTermination(1, TimeUnit.SECONDS); 71 | } 72 | 73 | @Test 74 | public void exitCode() { 75 | assertThat(runner.getExitCode()).isEqualTo(0); 76 | } 77 | 78 | @Test 79 | public void onEvent() throws InterruptedException { 80 | when(eventReader.read()).thenReturn(Optional.of(event)); 81 | runRunner(); 82 | verify(listener, atLeastOnce()).onEvent(event); 83 | } 84 | 85 | @Test 86 | public void onEmptyEvent() throws InterruptedException { 87 | when(eventReader.read()).thenReturn(Optional.empty()); 88 | runRunner(); 89 | verifyZeroInteractions(listener); 90 | } 91 | 92 | @Test 93 | public void onEventProcessingFailure() throws InterruptedException { 94 | when(eventReader.read()).thenThrow(RuntimeException.class); 95 | runRunner(); 96 | verifyZeroInteractions(listener); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/test/java/com/expediagroup/shuntingyard/replicator/exec/conf/ShuntingYardReplicationsMapTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.conf; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | import org.junit.Before; 24 | import org.junit.Test; 25 | 26 | import com.expediagroup.shuntingyard.replicator.exec.conf.ShuntingYardTableReplicationsMap; 27 | import com.expediagroup.shuntingyard.replicator.exec.conf.ct.ShuntingYardTableReplication; 28 | import com.expediagroup.shuntingyard.replicator.exec.conf.ct.ShuntingYardTableReplications; 29 | 30 | import com.hotels.bdp.circustrain.api.conf.ReplicaTable; 31 | import com.hotels.bdp.circustrain.api.conf.SourceTable; 32 | 33 | public class ShuntingYardReplicationsMapTest { 34 | 35 | private static final String SOURCE_DATABASE = "DATABASE"; 36 | private static final String SOURCE_TABLE = "TABLE"; 37 | private static final String REPLICA_DATABASE = "replica_db"; 38 | private static final String REPLICA_TABLE = "replica_tbl"; 39 | 40 | private ShuntingYardTableReplicationsMap syTableReplications; 41 | 42 | @Before 43 | public void init() { 44 | ShuntingYardTableReplication tableReplication = new ShuntingYardTableReplication(); 45 | 46 | SourceTable sourceTable = new SourceTable(); 47 | sourceTable.setDatabaseName(SOURCE_DATABASE); 48 | sourceTable.setTableName(SOURCE_TABLE); 49 | tableReplication.setSourceTable(sourceTable); 50 | 51 | ReplicaTable replicaTable = new ReplicaTable(); 52 | replicaTable.setDatabaseName(REPLICA_DATABASE); 53 | replicaTable.setTableName(REPLICA_TABLE); 54 | tableReplication.setReplicaTable(replicaTable); 55 | 56 | List tableReplications = new ArrayList<>(); 57 | tableReplications.add(tableReplication); 58 | 59 | ShuntingYardTableReplications tableReplicationsWrapper = new ShuntingYardTableReplications(); 60 | tableReplicationsWrapper.setTableReplications(tableReplications); 61 | syTableReplications = new ShuntingYardTableReplicationsMap(tableReplicationsWrapper); 62 | } 63 | 64 | @Test 65 | public void typical() { 66 | ShuntingYardTableReplication tableReplication = syTableReplications 67 | .getTableReplication(SOURCE_DATABASE, SOURCE_TABLE); 68 | 69 | assertThat(tableReplication.getReplicaDatabaseName()).isEqualTo(REPLICA_DATABASE); 70 | assertThat(tableReplication.getReplicaTableName()).isEqualTo(REPLICA_TABLE); 71 | } 72 | 73 | @Test 74 | public void queryMapWithLowerCaseSourceDatabaseAndTable() { 75 | ShuntingYardTableReplication tableReplication = syTableReplications 76 | .getTableReplication(SOURCE_DATABASE.toLowerCase(), SOURCE_TABLE.toLowerCase()); 77 | 78 | assertThat(tableReplication.getReplicaDatabaseName()).isEqualTo(REPLICA_DATABASE); 79 | assertThat(tableReplication.getReplicaTableName()).isEqualTo(REPLICA_TABLE); 80 | } 81 | 82 | @Test 83 | public void emptyTableReplications() { 84 | syTableReplications = new ShuntingYardTableReplicationsMap(new ShuntingYardTableReplications()); 85 | assertThat(syTableReplications.getTableReplication(SOURCE_DATABASE, SOURCE_TABLE)).isNull(); 86 | } 87 | 88 | @Test 89 | public void nullTableReplications() { 90 | syTableReplications = new ShuntingYardTableReplicationsMap(null); 91 | assertThat(syTableReplications.getTableReplication(SOURCE_DATABASE, SOURCE_TABLE)).isNull(); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/test/java/com/expediagroup/shuntingyard/replicator/exec/event/aggregation/DefaultMetaStoreEventAggregatorTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.event.aggregation; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static org.mockito.Mockito.mock; 20 | import static org.mockito.Mockito.verify; 21 | import static org.mockito.Mockito.when; 22 | 23 | import java.util.ArrayList; 24 | import java.util.Arrays; 25 | import java.util.List; 26 | 27 | import org.junit.Before; 28 | import org.junit.Test; 29 | import org.junit.runner.RunWith; 30 | import org.mockito.Mock; 31 | import org.mockito.junit.MockitoJUnitRunner; 32 | 33 | import com.expediagroup.shuntingyard.replicator.exec.event.MetaStoreEvent; 34 | import com.expediagroup.shuntingyard.replicator.exec.event.aggregation.DefaultMetaStoreEventAggregator; 35 | import com.expediagroup.shuntingyard.replicator.exec.event.aggregation.DefaultMetaStoreEventCompactor; 36 | 37 | @RunWith(MockitoJUnitRunner.class) 38 | public class DefaultMetaStoreEventAggregatorTest { 39 | 40 | private static final String DATABASE = "database"; 41 | private static final String TABLE1 = "table1"; 42 | private static final String TABLE2 = "table2"; 43 | 44 | private @Mock DefaultMetaStoreEventCompactor compactor; 45 | 46 | private DefaultMetaStoreEventAggregator aggregator; 47 | 48 | @Before 49 | public void init() { 50 | aggregator = new DefaultMetaStoreEventAggregator(compactor); 51 | } 52 | 53 | @Test 54 | public void eventsFromSingleTable() { 55 | List events = Arrays.asList(mockEvent(DATABASE, TABLE1), mockEvent(DATABASE, TABLE1)); 56 | when(compactor.compact(events)).thenReturn(events); 57 | List aggregatedEvents = aggregator.aggregate(events); 58 | assertThat(aggregatedEvents).isEqualTo(events); 59 | verify(compactor).compact(events); 60 | } 61 | 62 | @Test 63 | public void eventsFromMultipleTables() { 64 | List table1Events = Arrays.asList(mockEvent(DATABASE, TABLE1)); 65 | List table2Events = Arrays.asList(mockEvent(DATABASE, TABLE2)); 66 | List events = new ArrayList<>(); 67 | events.addAll(table1Events); 68 | events.addAll(table2Events); 69 | when(compactor.compact(table1Events)).thenReturn(table1Events); 70 | when(compactor.compact(table2Events)).thenReturn(table2Events); 71 | List aggregatedEvents = aggregator.aggregate(events); 72 | assertThat(aggregatedEvents).isEqualTo(events); 73 | verify(compactor).compact(table1Events); 74 | verify(compactor).compact(table2Events); 75 | } 76 | 77 | private static MetaStoreEvent mockEvent(String database, String table) { 78 | MetaStoreEvent event = mock(MetaStoreEvent.class); 79 | when(event.getQualifiedTableName()).thenReturn(database + "." + table); 80 | return event; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/test/java/com/expediagroup/shuntingyard/replicator/exec/event/aggregation/DefaultMetaStoreEventCompactorTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.event.aggregation; 17 | 18 | import static java.util.Arrays.asList; 19 | 20 | import static org.assertj.core.api.Assertions.assertThat; 21 | import static org.mockito.Mockito.mock; 22 | import static org.powermock.api.mockito.PowerMockito.when; 23 | 24 | import static com.expedia.apiary.extensions.receiver.common.event.EventType.ADD_PARTITION; 25 | import static com.expedia.apiary.extensions.receiver.common.event.EventType.CREATE_TABLE; 26 | import static com.expedia.apiary.extensions.receiver.common.event.EventType.DROP_PARTITION; 27 | import static com.expedia.apiary.extensions.receiver.common.event.EventType.DROP_TABLE; 28 | 29 | import java.util.List; 30 | 31 | import org.junit.Before; 32 | import org.junit.Test; 33 | import org.junit.runner.RunWith; 34 | import org.mockito.Mock; 35 | import org.mockito.junit.MockitoJUnitRunner; 36 | 37 | import com.expediagroup.shuntingyard.replicator.exec.event.MetaStoreEvent; 38 | import com.expediagroup.shuntingyard.replicator.exec.event.aggregation.DefaultMetaStoreEventCompactor; 39 | import com.expediagroup.shuntingyard.replicator.exec.event.aggregation.EventMerger; 40 | 41 | import com.expedia.apiary.extensions.receiver.common.event.EventType; 42 | 43 | @RunWith(MockitoJUnitRunner.class) 44 | public class DefaultMetaStoreEventCompactorTest { 45 | 46 | private @Mock EventMerger merger; 47 | 48 | private DefaultMetaStoreEventCompactor compactor; 49 | 50 | @Before 51 | public void init() { 52 | compactor = new DefaultMetaStoreEventCompactor(merger); 53 | } 54 | 55 | @Test 56 | public void shouldReturnSameEventsIfEventsCannotBeMerged() { 57 | List events = asList(mockEvent(DROP_PARTITION), mockEvent(DROP_TABLE), 58 | mockEvent(CREATE_TABLE)); 59 | List compactEvents = compactor.compact(events); 60 | assertThat(compactEvents).isEqualTo(events); 61 | } 62 | 63 | @Test 64 | public void mergeEventsIfEventsCanBeMerged() { 65 | MetaStoreEvent eventA = mockEvent(DROP_PARTITION); 66 | MetaStoreEvent eventB = mockEvent(DROP_TABLE); 67 | MetaStoreEvent eventC = mockEvent(CREATE_TABLE); 68 | MetaStoreEvent eventD = mockEvent(ADD_PARTITION); 69 | MetaStoreEvent eventE = mockEvent(ADD_PARTITION); 70 | 71 | when(merger.canMerge(eventC, eventD)).thenReturn(true); 72 | MetaStoreEvent eventCD = mockEvent(CREATE_TABLE); 73 | when(merger.merge(eventC, eventD)).thenReturn(eventCD); 74 | when(merger.canMerge(eventCD, eventE)).thenReturn(true); 75 | MetaStoreEvent eventCDE = mockEvent(CREATE_TABLE); 76 | when(merger.merge(eventCD, eventE)).thenReturn(eventCDE); 77 | 78 | List compactEvents = compactor.compact(asList(eventA, eventB, eventC, eventD, eventE)); 79 | 80 | assertThat(compactEvents).isEqualTo(asList(eventA, eventB, eventCDE)); 81 | } 82 | 83 | @Test 84 | public void mergeDiscardPreviousEventsIfADropTableIsFoundInBetween() { 85 | MetaStoreEvent eventA = mockEvent(CREATE_TABLE); 86 | MetaStoreEvent eventB = mockEvent(ADD_PARTITION); 87 | MetaStoreEvent eventC = mockEvent(DROP_TABLE); 88 | MetaStoreEvent eventD = mockEvent(CREATE_TABLE); 89 | MetaStoreEvent eventE = mockEvent(ADD_PARTITION); 90 | 91 | when(merger.canMerge(eventA, eventB)).thenReturn(true); 92 | MetaStoreEvent eventAB = mockEvent(CREATE_TABLE); 93 | when(merger.merge(eventA, eventB)).thenReturn(eventAB); 94 | 95 | when(merger.canMerge(eventD, eventE)).thenReturn(true); 96 | MetaStoreEvent eventDE = mockEvent(CREATE_TABLE); 97 | when(merger.merge(eventD, eventE)).thenReturn(eventDE); 98 | 99 | List compactEvents = compactor.compact(asList(eventA, eventB, eventC, eventD, eventE)); 100 | 101 | assertThat(compactEvents).isEqualTo(asList(eventC, eventDE)); 102 | } 103 | 104 | private static MetaStoreEvent mockEvent(EventType eventType) { 105 | MetaStoreEvent event = mock(MetaStoreEvent.class); 106 | when(event.getEventType()).thenReturn(eventType); 107 | if (eventType == DROP_PARTITION || eventType == DROP_TABLE) { 108 | when(event.isDropEvent()).thenReturn(true); 109 | } 110 | return event; 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/test/java/com/expediagroup/shuntingyard/replicator/exec/external/MarshallerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.external; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | 20 | import java.io.File; 21 | import java.nio.charset.StandardCharsets; 22 | import java.util.Arrays; 23 | 24 | import org.junit.Rule; 25 | import org.junit.Test; 26 | import org.junit.rules.TemporaryFolder; 27 | 28 | import com.google.common.io.Files; 29 | 30 | import com.hotels.bdp.circustrain.api.conf.OrphanedDataStrategy; 31 | import com.hotels.bdp.circustrain.api.conf.ReplicationMode; 32 | 33 | public class MarshallerTest { 34 | 35 | public @Rule TemporaryFolder tmp = new TemporaryFolder(); 36 | 37 | private final Marshaller marshaller = new Marshaller(); 38 | 39 | @Test 40 | public void typical() throws Exception { 41 | CircusTrainConfig config = CircusTrainConfig 42 | .builder() 43 | .sourceName("sourceName") 44 | .sourceMetaStoreUri("sourceMetaStoreUri") 45 | .replicaName("replicaName") 46 | .replicaMetaStoreUri("replicaMetaStoreUri") 47 | .copierOption("p1", "val1") 48 | .copierOption("p2", "val2") 49 | .replication(ReplicationMode.FULL, "databaseName", "tableName", "replicaDatabaseName", "replicaTableName", 50 | "replicaTableLocation", Arrays.asList("part"), Arrays.asList(Arrays.asList("partval")), 51 | OrphanedDataStrategy.NONE) 52 | .build(); 53 | 54 | File file = tmp.newFile("conif.yml"); 55 | marshaller.marshall(file.getAbsolutePath(), config); 56 | assertThat(Files.readLines(file, StandardCharsets.UTF_8)) 57 | .contains("source-catalog:") 58 | .contains(" disable-snapshots: false") 59 | .contains(" name: sourceName") 60 | .contains(" hive-metastore-uris: sourceMetaStoreUri") 61 | .contains("replica-catalog:") 62 | .contains(" name: replicaName") 63 | .contains(" hive-metastore-uris: replicaMetaStoreUri") 64 | .contains("copier-options:") 65 | .contains(" p1: val1") 66 | .contains(" p2: val2") 67 | .contains("table-replications:") 68 | .contains("- orphaned-data-strategy: NONE") 69 | .contains(" replication-mode: FULL") 70 | .contains(" source-table:") 71 | .contains(" database-name: databaseName") 72 | .contains(" table-name: tableName") 73 | .contains(" partition-filter: (part='partval')") 74 | .contains(" partition-limit: 32767") 75 | .contains(" replica-table:") 76 | .contains(" database-name: replicaDatabaseName") 77 | .contains(" table-name: replicaTableName") 78 | .contains(" table-location: replicaTableLocation"); 79 | } 80 | 81 | @Test 82 | public void generatePartitionFilter() throws Exception { 83 | CircusTrainConfig config = CircusTrainConfig 84 | .builder() 85 | .sourceName("sourceName") 86 | .sourceMetaStoreUri("sourceMetaStoreUri") 87 | .replicaName("replicaName") 88 | .replicaMetaStoreUri("replicaMetaStoreUri") 89 | .copierOption("p1", "val1") 90 | .copierOption("p2", "val2") 91 | .replication(ReplicationMode.FULL, "databaseName", "tableName", "replicaDatabaseName", "replicaTableName", 92 | "replicaTableLocation", OrphanedDataStrategy.HOUSEKEEPING) 93 | .build(); 94 | 95 | File file = tmp.newFile("conif.yml"); 96 | marshaller.marshall(file.getAbsolutePath(), config); 97 | assertThat(Files.readLines(file, StandardCharsets.UTF_8)) 98 | .contains("source-catalog:") 99 | .contains(" disable-snapshots: false") 100 | .contains(" name: sourceName") 101 | .contains(" hive-metastore-uris: sourceMetaStoreUri") 102 | .contains("replica-catalog:") 103 | .contains(" name: replicaName") 104 | .contains(" hive-metastore-uris: replicaMetaStoreUri") 105 | .contains("copier-options:") 106 | .contains(" p1: val1") 107 | .contains(" p2: val2") 108 | .contains("table-replications:") 109 | .contains("- orphaned-data-strategy: HOUSEKEEPING") 110 | .contains(" replication-mode: FULL") 111 | .contains(" source-table:") 112 | .contains(" database-name: databaseName") 113 | .contains(" table-name: tableName") 114 | .contains(" generate-partition-filter: true") 115 | .contains(" partition-limit: 32767") 116 | .contains(" replica-table:") 117 | .contains(" database-name: replicaDatabaseName") 118 | .contains(" table-name: replicaTableName") 119 | .contains(" table-location: replicaTableLocation"); 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/test/java/com/expediagroup/shuntingyard/replicator/exec/receiver/TableSelectorTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.exec.receiver; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static org.mockito.Mockito.when; 20 | 21 | import java.util.Arrays; 22 | 23 | import org.junit.Before; 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | import org.mockito.Mock; 27 | import org.mockito.junit.MockitoJUnitRunner; 28 | 29 | import com.expediagroup.shuntingyard.replicator.exec.conf.SourceTableFilter; 30 | import com.expediagroup.shuntingyard.replicator.exec.receiver.TableSelector; 31 | 32 | import com.expedia.apiary.extensions.receiver.common.event.ListenerEvent; 33 | 34 | @RunWith(MockitoJUnitRunner.class) 35 | public class TableSelectorTest { 36 | 37 | private static final String DB_NAME = "test_db"; 38 | private static final String TABLE_NAME = "test_table"; 39 | 40 | private TableSelector tableSelector; 41 | private @Mock ListenerEvent listenerEvent; 42 | 43 | @Before 44 | public void init() { 45 | when(listenerEvent.getDbName()).thenReturn(DB_NAME); 46 | when(listenerEvent.getTableName()).thenReturn(TABLE_NAME); 47 | 48 | SourceTableFilter targetReplication = new SourceTableFilter(); 49 | targetReplication.setTableNames(Arrays.asList(DB_NAME + "." + TABLE_NAME, "db1.table1")); 50 | 51 | tableSelector = new TableSelector(targetReplication); 52 | } 53 | 54 | @Test 55 | public void returnsTrueWhenTableSelected() { 56 | assertThat(tableSelector.canProcess(listenerEvent)).isTrue(); 57 | } 58 | 59 | @Test 60 | public void returnsFalseEventWhenTableNotSelected() { 61 | when(listenerEvent.getTableName()).thenReturn(TABLE_NAME + "1"); 62 | assertThat(tableSelector.canProcess(listenerEvent)).isFalse(); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/test/java/com/expediagroup/shuntingyard/replicator/metastore/DefaultMetaStoreClientSupplierTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.metastore; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static org.mockito.ArgumentMatchers.anyString; 20 | import static org.mockito.ArgumentMatchers.eq; 21 | import static org.mockito.Mockito.when; 22 | 23 | import org.apache.hadoop.hive.conf.HiveConf; 24 | import org.junit.Before; 25 | import org.junit.Test; 26 | import org.junit.runner.RunWith; 27 | import org.mockito.Mock; 28 | import org.mockito.junit.MockitoJUnitRunner; 29 | 30 | import com.expediagroup.shuntingyard.replicator.metastore.DefaultMetaStoreClientSupplier; 31 | 32 | import com.hotels.hcommon.hive.metastore.client.api.CloseableMetaStoreClient; 33 | import com.hotels.hcommon.hive.metastore.client.api.MetaStoreClientFactory; 34 | 35 | @RunWith(MockitoJUnitRunner.class) 36 | public class DefaultMetaStoreClientSupplierTest { 37 | 38 | private @Mock MetaStoreClientFactory metaStoreClientFactory; 39 | private @Mock CloseableMetaStoreClient metaStoreClient; 40 | 41 | private final HiveConf hiveConf = new HiveConf(); 42 | private DefaultMetaStoreClientSupplier supplier; 43 | 44 | @Before 45 | public void init() { 46 | supplier = new DefaultMetaStoreClientSupplier(hiveConf, metaStoreClientFactory); 47 | } 48 | 49 | @Test 50 | public void get() { 51 | when(metaStoreClientFactory.newInstance(eq(hiveConf), anyString())).thenReturn(metaStoreClient); 52 | assertThat(supplier.get()).isSameAs(metaStoreClient); 53 | } 54 | 55 | @Test(expected = NullPointerException.class) 56 | public void bubbleUpExceptions() { 57 | when(metaStoreClientFactory.newInstance(eq(hiveConf), anyString())).thenThrow(NullPointerException.class); 58 | supplier.get(); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/test/java/com/expediagroup/shuntingyard/replicator/util/TableDatabaseNameJoinerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.util; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | 20 | import org.junit.Test; 21 | 22 | import com.expediagroup.shuntingyard.replicator.util.TableDatabaseNameJoiner; 23 | 24 | public class TableDatabaseNameJoinerTest { 25 | 26 | @Test 27 | public void typical() { 28 | String databaseName = "database"; 29 | String tableName = "table"; 30 | 31 | assertThat(TableDatabaseNameJoiner.dotJoin(databaseName, tableName)).isEqualTo("database.table"); 32 | } 33 | 34 | @Test 35 | public void nullInput() { 36 | String databaseName = "database"; 37 | String tableName = null; 38 | 39 | assertThat(TableDatabaseNameJoiner.dotJoin(databaseName, tableName)).isEqualTo("database.null"); 40 | } 41 | 42 | @Test 43 | public void blankInput() { 44 | String databaseName = "database"; 45 | String tableName = ""; 46 | 47 | assertThat(TableDatabaseNameJoiner.dotJoin(databaseName, tableName)).isEqualTo("database."); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/test/java/com/expediagroup/shuntingyard/replicator/yaml/AdvancedPropertyUtilsTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.yaml; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | 20 | import java.beans.Transient; 21 | import java.util.Iterator; 22 | import java.util.Set; 23 | 24 | import org.junit.Test; 25 | import org.yaml.snakeyaml.error.YAMLException; 26 | import org.yaml.snakeyaml.introspector.BeanAccess; 27 | import org.yaml.snakeyaml.introspector.Property; 28 | 29 | import com.expediagroup.shuntingyard.replicator.yaml.AdvancedPropertyUtils; 30 | 31 | public class AdvancedPropertyUtilsTest { 32 | 33 | static class TestBean { 34 | private String longPropertyName; 35 | public transient int transientField; 36 | private char transientProperty; 37 | 38 | public String getLongPropertyName() { 39 | return longPropertyName; 40 | } 41 | 42 | public void setLongPropertyName(String longPropertyName) { 43 | this.longPropertyName = longPropertyName; 44 | } 45 | 46 | @Transient 47 | public char getTransientProperty() { 48 | return transientProperty; 49 | } 50 | 51 | @Transient 52 | public void setTransientProperty(char transientProperty) { 53 | this.transientProperty = transientProperty; 54 | } 55 | } 56 | 57 | private final AdvancedPropertyUtils properyUtils = new AdvancedPropertyUtils(); 58 | 59 | @Test 60 | public void regularPropertyName() throws Exception { 61 | Property property = properyUtils.getProperty(TestBean.class, "longPropertyName"); 62 | assertThat(property).isNotNull(); 63 | assertThat(property.getName()).isEqualTo("longPropertyName"); 64 | } 65 | 66 | @Test 67 | public void lowerHyphenPropertyName() throws Exception { 68 | Property property = properyUtils.getProperty(TestBean.class, "long-property-name"); 69 | assertThat(property).isNotNull(); 70 | assertThat(property.getName()).isEqualTo("longPropertyName"); 71 | } 72 | 73 | @Test(expected = YAMLException.class) 74 | public void illegalPropertyName() throws Exception { 75 | properyUtils.getProperty(TestBean.class, "unknown"); 76 | } 77 | 78 | @Test 79 | public void createPropertySetWithDefaultBeanAccess() throws Exception { 80 | Set properties = properyUtils.createPropertySet(TestBean.class, BeanAccess.DEFAULT); 81 | assertThat(properties.size()).isEqualTo(1); 82 | assertThat(properties.iterator().next().getName()).isEqualTo("longPropertyName"); 83 | } 84 | 85 | @Test 86 | public void createPropertySetWithFieldBeanAccess() throws Exception { 87 | Set properties = properyUtils.createPropertySet(TestBean.class, BeanAccess.FIELD); 88 | assertThat(properties.size()).isEqualTo(2); 89 | Iterator iterator = properties.iterator(); 90 | assertThat(iterator.next().getName()).isEqualTo("longPropertyName"); 91 | assertThat(iterator.next().getName()).isEqualTo("transientProperty"); 92 | } 93 | 94 | @Test 95 | public void createPropertySetWithPropertyBeanAccess() throws Exception { 96 | Set properties = properyUtils.createPropertySet(TestBean.class, BeanAccess.PROPERTY); 97 | assertThat(properties.size()).isEqualTo(1); 98 | assertThat(properties.iterator().next().getName()).isEqualTo("longPropertyName"); 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/test/java/com/expediagroup/shuntingyard/replicator/yaml/YamlFactoryTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2019 Expedia, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.expediagroup.shuntingyard.replicator.yaml; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | 20 | import java.io.StringWriter; 21 | 22 | import org.junit.Test; 23 | import org.yaml.snakeyaml.Yaml; 24 | 25 | import com.expediagroup.shuntingyard.replicator.exec.external.CircusTrainConfig; 26 | 27 | import com.hotels.bdp.circustrain.api.conf.OrphanedDataStrategy; 28 | import com.hotels.bdp.circustrain.api.conf.ReplicationMode; 29 | 30 | public class YamlFactoryTest { 31 | 32 | @Test 33 | public void typical() { 34 | String expectedYaml = new StringBuffer() 35 | .append("replica-catalog:\n") 36 | .append(" hive-metastore-uris: replicaMetaStoreUri\n") 37 | .append(" name: replica\n") 38 | .append("source-catalog:\n") 39 | .append(" disable-snapshots: false\n") 40 | .append(" hive-metastore-uris: sourceMetaStoreUri\n") 41 | .append(" name: source\n") 42 | .append("table-replications:\n") 43 | .append("- orphaned-data-strategy: HOUSEKEEPING\n") 44 | .append(" partition-fetcher-buffer-size: 1000\n") 45 | .append(" partition-iterator-batch-size: 1000\n") 46 | .append(" qualified-replica-name: replicadatabasename.replicatablename\n") 47 | .append(" replica-database-name: replicadatabasename\n") 48 | .append(" replica-table:\n") 49 | .append(" database-name: replicaDatabaseName\n") 50 | .append(" table-location: replicaTableLocation\n") 51 | .append(" table-name: replicaTableName\n") 52 | .append(" replica-table-name: replicatablename\n") 53 | .append(" replication-mode: FULL\n") 54 | .append(" replication-strategy: UPSERT\n") 55 | .append(" source-table:\n") 56 | .append(" database-name: databaseName\n") 57 | .append(" generate-partition-filter: true\n") 58 | .append(" partition-limit: 32767\n") 59 | .append(" qualified-name: databasename.tablename\n") 60 | .append(" table-name: tableName\n") 61 | .toString(); 62 | StringWriter sw = new StringWriter(); 63 | CircusTrainConfig circusTrainConfig = CircusTrainConfig 64 | .builder() 65 | .sourceMetaStoreUri("sourceMetaStoreUri") 66 | .replicaMetaStoreUri("replicaMetaStoreUri") 67 | .replication(ReplicationMode.FULL, "databaseName", "tableName", "replicaDatabaseName", "replicaTableName", 68 | "replicaTableLocation", OrphanedDataStrategy.HOUSEKEEPING) 69 | .build(); 70 | Yaml yaml = YamlFactory.newYaml(); 71 | yaml.dump(circusTrainConfig, sw); 72 | assertThat(sw.toString()).isEqualTo(expectedYaml); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /shunting-yard-replicator/src/test/resources/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /shunting-yard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpediaGroup/shunting-yard/369ac242640b24121fd661d40ae94a0196ced0fd/shunting-yard.png --------------------------------------------------------------------------------