diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c6942cf --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# https://editorconfig.org + +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 + +[*.java] +indent_style = space +indent_size = 4 + +[*.xml,*.xslt] +indent_style = space +indent_size = 4 diff --git a/.github/workflows/ci-backend-cassandra.yaml b/.github/workflows/ci-backend-cassandra.yaml new file mode 100644 index 0000000..4ec13c9 --- /dev/null +++ b/.github/workflows/ci-backend-cassandra.yaml @@ -0,0 +1,97 @@ +# Copyright 2020 JanusGraph Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: CI Backend Cassandra + +on: + pull_request: + push: + +env: + BUILD_MAVEN_OPTS: "-DskipTests=true --batch-mode --also-make" + VERIFY_MAVEN_OPTS: "-Pcoverage" + +jobs: + tests: + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + include: + - args: "-Pcassandra3-byteordered -Dtest=\"**/diskstorage/cassandra/*\"" + name: byteordered-diskstorage + - args: "-Pcassandra3-murmur -Dtest=\"**/diskstorage/cassandra/*\"" + name: murmur-diskstorage + - args: "-Pcassandra3-byteordered -Dtest=\"**/graphdb/cassandra/*\"" + name: byteordered-graphdb + - args: "-Pcassandra3-murmur -Dtest=\"**/graphdb/cassandra/*\"" + name: murmur-graphdb + - args: "-Pcassandra3-murmur -Dtest=\"**/hadoop/*\"" + name: murmur-hadoop + - args: "-Pcassandra3-byteordered -Dtest=\"**/core/cassandra/*\"" + name: byteordered-core + - args: "-Pcassandra3-murmur -Dtest=\"**/core/cassandra/*\"" + name: murmur-core + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 2 + - uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - uses: actions/setup-java@v1 + with: + java-version: ${{ matrix.java }} + - run: mvn clean install ${{ env.BUILD_MAVEN_OPTS }} ${{ matrix.install-args }} + - run: mvn verify ${{ env.VERIFY_MAVEN_OPTS }} ${{ matrix.args }} + - uses: actions/upload-artifact@v2 + with: + name: jacoco-reports + path: target/jacoco-combined.exec + - uses: codecov/codecov-action@v1 + with: + name: codecov-cassandra-${{ matrix.name }} + + full-tests: + runs-on: ubuntu-20.04 + if: "github.event_name == 'push' && contains(github.event.head_commit.message, '[full-tests]') || github.event_name == 'pull_request' && contains(github.event.pull_request.title, '[full-tests]')" + strategy: + fail-fast: false + matrix: + include: + - args: "-Pcassandra3-byteordered -Dtest=\"**/diskstorage/cql/*\"" + - args: "-Pcassandra3-murmur -Dtest=\"**/diskstorage/cql/*\"" + - args: "-Pcassandra3-byteordered -Dtest=\"**/graphdb/cql/*\"" + - args: "-Pcassandra3-murmur -Dtest=\"**/graphdb/cql/*\"" + - args: "-Pcassandra3-murmur -Dtest=\"**/hadoop/*\"" + - args: "-Pcassandra3-byteordered -Dtest=\"**/core/cql/*\"" + - args: "-Pcassandra3-murmur -Dtest=\"**/core/cql/*\"" + - args: "-Pcassandra3-murmur-ssl -Dtest=\"**/diskstorage/cql/CQLStoreTest.java\"" + - args: "-Pcassandra3-murmur-client-auth -Dtest=\"**/diskstorage/cql/CQLStoreTest.java\"" + steps: + - uses: actions/checkout@v2 + - uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - uses: actions/setup-java@v1 + with: + java-version: 1.8 + - run: mvn clean install ${{ env.BUILD_MAVEN_OPTS }} ${{ matrix.install-args }} + - run: mvn verify -Dcassandra.docker.version='3.0.18' ${{ env.VERIFY_MAVEN_OPTS }} ${{ matrix.args }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa1c71d --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.log +target/ +.idea/ +*.iml +*.DS_Store +.vscode/ +/_bsp/ +/output/ +/docker/*.zip +.surefire-* +/jub.*.xml +.classpath +.factorypath +.project +org.eclipse.* \ No newline at end of file diff --git a/APACHE-2.0.txt b/APACHE-2.0.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/APACHE-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/CC-BY-4.0.txt b/CC-BY-4.0.txt new file mode 100644 index 0000000..06c608d --- /dev/null +++ b/CC-BY-4.0.txt @@ -0,0 +1,395 @@ +Attribution 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More_considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution 4.0 International Public License ("Public License"). To the +extent this Public License may be interpreted as a contract, You are +granted the Licensed Rights in consideration of Your acceptance of +these terms and conditions, and the Licensor grants You such rights in +consideration of benefits the Licensor receives from making the +Licensed Material available under these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + d. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + g. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + i. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + j. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + k. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's + License You apply must not prevent recipients of the Adapted + Material from complying with this Public License. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..7b5cfb8 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,629 @@ +The JanusGraph project uses the following two licenses: + +* Apache Software License 2.0 (APACHE-2.0), the full terms of which can be found + in the file APACHE-2.0.txt in the root of this repository, and included below +* Creative Commons Attribution 4.0 International (CC-BY-4.0), the full terms of + which can be found in the file CC-BY-4.0.txt in the root of this repository, + and included below + +The code of JanusGraph is under the APACHE-2.0 license. + +The documentation of JanusGraph is under a combination of licenses: + +* APACHE-2.0 license for all documentation contributed prior to the JanusGraph + fork (27 Dec 2016), i.e., imported with the initial Titan fork +* CC-BY-4.0 license for all documentation contributed on or after the JanusGraph + fork (27 Dec 2016) + +All new contributions to JanusGraph are accepted under the following licenses: + +* APACHE-2.0 license for code contributions, including code samples in docs +* CC-BY-4.0 license for documentation contributions + +For convenience, copies of APACHE-2.0 and CC-BY-4.0 are included verbatim below. + +/==============================================================================\ +| APACHE-2.0 | +\==============================================================================/ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +/==============================================================================\ +| CC-BY-4.0 | +\==============================================================================/ + +Attribution 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More_considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution 4.0 International Public License ("Public License"). To the +extent this Public License may be interpreted as a contract, You are +granted the Licensed Rights in consideration of Your acceptance of +these terms and conditions, and the Licensor grants You such rights in +consideration of benefits the Licensor receives from making the +Licensed Material available under these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + d. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + g. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + i. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + j. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + k. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's + License You apply must not prevent recipients of the Adapted + Material from complying with this Public License. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/Readme.md b/Readme.md index 09f8253..9d6a1bf 100644 --- a/Readme.md +++ b/Readme.md @@ -1,2 +1,10 @@ # Cassandra Thrift and Embedded Backend Adapter for JanusGraph +> We decided to remove janusgraph-cassandra from core project due to decrepations of Cassandra Thrift, and a massive dependices overhead for Cassandra Embedded. + +## Compatibility Matrix + + +|Cassandra Adapter|JanusGraph| +|-:|-:| +|0.5.0-SNAPSHOT|0.5.0| diff --git a/config/cassandra-filters/localhost-bop.properties b/config/cassandra-filters/localhost-bop.properties new file mode 100644 index 0000000..20d48f7 --- /dev/null +++ b/config/cassandra-filters/localhost-bop.properties @@ -0,0 +1,24 @@ +# Copyright 2019 JanusGraph Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cassandra.ip = 127.0.0.1 +cassandra.seed = 127.0.0.1 +# This must be set to prevent Cassandra from self-killing with BOP: +# https://issues.apache.org/jira/browse/CASSANDRA-5414 +cassandra.token = 0000000000000000000000000000000000 +cassandra.num.tokens = +cassandra.partitioner = org.apache.cassandra.dht.ByteOrderedPartitioner +cassandra.dir = ${project.build.directory}/cassandra/data/localhost-bop +cassandra.clientenc.enabled = false +cassandra.clientenc.ccert = false diff --git a/config/cassandra-filters/localhost-murmur-ssl.properties b/config/cassandra-filters/localhost-murmur-ssl.properties new file mode 100644 index 0000000..ee0a189 --- /dev/null +++ b/config/cassandra-filters/localhost-murmur-ssl.properties @@ -0,0 +1,24 @@ +# Copyright 2019 JanusGraph Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cassandra.ip = 127.0.0.1 +cassandra.seed = 127.0.0.1 +cassandra.token = +cassandra.num.tokens = num_tokens: 4 +cassandra.partitioner = org.apache.cassandra.dht.Murmur3Partitioner +cassandra.dir = ${project.build.directory}/cassandra/data/localhost-murmur-ssl +cassandra.clientenc.enabled = true +cassandra.clientenc.ccert = false +cassandra.clientenc.kspath = ${project.build.directory}/cassandra/conf/localhost-murmur-ssl/test.keystore +cassandra.clientenc.kspw = cassandra diff --git a/config/cassandra-filters/localhost-murmur.properties b/config/cassandra-filters/localhost-murmur.properties new file mode 100644 index 0000000..fc4f86d --- /dev/null +++ b/config/cassandra-filters/localhost-murmur.properties @@ -0,0 +1,22 @@ +# Copyright 2019 JanusGraph Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cassandra.ip = 127.0.0.1 +cassandra.seed = 127.0.0.1 +cassandra.token = +cassandra.num.tokens = num_tokens: 4 +cassandra.partitioner = org.apache.cassandra.dht.Murmur3Partitioner +cassandra.dir = ${project.build.directory}/cassandra/data/localhost-murmur +cassandra.clientenc.enabled = false +cassandra.clientenc.ccert = false diff --git a/config/cassandra/cassandra-env.sh b/config/cassandra/cassandra-env.sh new file mode 100644 index 0000000..c296abf --- /dev/null +++ b/config/cassandra/cassandra-env.sh @@ -0,0 +1,124 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +calculate_heap_size() +{ + case "`uname`" in + Linux) + system_memory_in_mb=`free -m | awk '/Mem:/ {print $2}'` + MAX_HEAP_SIZE=$((system_memory_in_mb / 2))M + return 0 + ;; + FreeBSD) + system_memory_in_bytes=`sysctl hw.physmem | awk '{print $2}'` + MAX_HEAP_SIZE=$((system_memory_in_bytes / 1024 / 1024 / 2))M + return 0 + ;; + *) + MAX_HEAP_SIZE=1024M + return 1 + ;; + esac +} + +# The amount of memory to allocate to the JVM at startup, you almost +# certainly want to adjust this for your environment. If left commented +# out, the heap size will be automatically determined by calculate_heap_size +# MAX_HEAP_SIZE="4G" + +if [ "x$MAX_HEAP_SIZE" = "x" ]; then + calculate_heap_size +fi + +# Specifies the default port over which Cassandra will be available for +# JMX connections. +JMX_PORT="9090" + +# To use mx4j, an HTML interface for JMX, add mx4j-tools.jar to the lib/ directory. +# By default mx4j listens on 0.0.0.0:8081. Uncomment the following lines to control +# its listen address and port. +#MX4J_ADDRESS="-Dmx4jaddress=0.0.0.0" +#MX4J_PORT="-Dmx4jport=8081" + + +# Here we create the arguments that will get passed to the jvm when +# starting cassandra. + +# enable assertions. disabling this in production will give a modest +# performance benefit (around 5%). +#JVM_OPTS="$JVM_OPTS -ea" + +# enable thread priorities, primarily so we can give periodic tasks +# a lower priority to avoid interfering with client workload +JVM_OPTS="$JVM_OPTS -XX:+UseThreadPriorities" +# allows lowering thread priority without being root. see +# http://tech.stolsvik.com/2010/01/linux-java-thread-priorities-workaround.html +JVM_OPTS="$JVM_OPTS -XX:ThreadPriorityPolicy=42" + +# min and max heap sizes should be set to the same value to avoid +# stop-the-world GC pauses during resize, and so that we can lock the +# heap in memory on startup to prevent any of it from being swapped +# out. +JVM_OPTS="$JVM_OPTS -Xms$MAX_HEAP_SIZE" +JVM_OPTS="$JVM_OPTS -Xmx$MAX_HEAP_SIZE" +JVM_OPTS="$JVM_OPTS -XX:+HeapDumpOnOutOfMemoryError" + +if [ "`uname`" = "Linux" ] ; then + # reduce the per-thread stack size to minimize the impact of Thrift + # thread-per-client. (Best practice is for client connections to + # be pooled anyway.) Only do so on Linux where it is known to be + # supported. + JVM_OPTS="$JVM_OPTS -Xss256k" +fi + +# GC tuning options +JVM_OPTS="$JVM_OPTS -XX:+UseParNewGC" +JVM_OPTS="$JVM_OPTS -XX:+UseConcMarkSweepGC" +JVM_OPTS="$JVM_OPTS -XX:+CMSParallelRemarkEnabled" +JVM_OPTS="$JVM_OPTS -XX:SurvivorRatio=8" +JVM_OPTS="$JVM_OPTS -XX:MaxTenuringThreshold=1" +JVM_OPTS="$JVM_OPTS -XX:CMSInitiatingOccupancyFraction=75" +JVM_OPTS="$JVM_OPTS -XX:+UseCMSInitiatingOccupancyOnly" + +# GC logging options -- uncomment to enable +# JVM_OPTS="$JVM_OPTS -XX:+PrintGCDetails" +# JVM_OPTS="$JVM_OPTS -XX:+PrintGCTimeStamps" +# JVM_OPTS="$JVM_OPTS -XX:+PrintClassHistogram" +# JVM_OPTS="$JVM_OPTS -XX:+PrintTenuringDistribution" +# JVM_OPTS="$JVM_OPTS -XX:+PrintGCApplicationStoppedTime" +# JVM_OPTS="$JVM_OPTS -Xloggc:/var/log/cassandra/gc.log" + +# Prefer binding to IPv4 network intefaces (when net.ipv6.bindv6only=1). See +# http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6342561 (short version: +# comment out this entry to enable IPv6 support). +JVM_OPTS="$JVM_OPTS -Djava.net.preferIPv4Stack=true" + +# jmx: metrics and administration interface +# +# add this if you're having trouble connecting: +# JVM_OPTS="$JVM_OPTS -Djava.rmi.server.hostname=" +# +# see +# http://blogs.sun.com/jmxetc/entry/troubleshooting_connection_problems_in_jconsole +# for more on configuring JMX through firewalls, etc. (Short version: +# get it working with no firewall first.) + +# Disable JMX so multiple cassandras can run on 127.0.0.1, .2, .3, etc. +#JVM_OPTS="$JVM_OPTS -Dcom.sun.management.jmxremote.port=$JMX_PORT" +#JVM_OPTS="$JVM_OPTS -Dcom.sun.management.jmxremote.ssl=false" +#JVM_OPTS="$JVM_OPTS -Dcom.sun.management.jmxremote.authenticate=false" +#JVM_OPTS="$JVM_OPTS $MX4J_ADDRESS" +#JVM_OPTS="$JVM_OPTS $MX4J_PORT" diff --git a/config/cassandra/cassandra.in.sh b/config/cassandra/cassandra.in.sh new file mode 100644 index 0000000..17e6e15 --- /dev/null +++ b/config/cassandra/cassandra.in.sh @@ -0,0 +1,46 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +if [ "x$CASSANDRA_HOME" = "x" ]; then + CASSANDRA_HOME=`dirname $0`/.. +fi + +# The directory where Cassandra's configs live (required) +if [ "x$CASSANDRA_CONF" = "x" ]; then + CASSANDRA_CONF=$CASSANDRA_HOME/conf +fi + +export MAX_HEAP_SIZE=256m + +# This can be the path to a jar file, or a directory containing the +# compiled classes. NOTE: This isn't needed by the startup script, +# it's just used here in constructing the classpath. +cassandra_bin=$CASSANDRA_HOME/build/classes +#cassandra_bin=$cassandra_home/build/cassandra.jar + +# JAVA_HOME can optionally be set here +#JAVA_HOME=/usr/local/jdk6 + +# The java classpath (required) +CLASSPATH=$CASSANDRA_CONF:$cassandra_bin + +for jar in $CASSANDRA_HOME/lib/*.jar; do + CLASSPATH=$CLASSPATH:$jar +done + +mkdir -p ${cassandra.dir} + +#export MAX_HEAP_SIZE=2048m diff --git a/config/cassandra/cassandra.yaml b/config/cassandra/cassandra.yaml new file mode 100644 index 0000000..b0fc03f --- /dev/null +++ b/config/cassandra/cassandra.yaml @@ -0,0 +1,678 @@ +# Copyright 2019 JanusGraph Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Cassandra storage config YAML + +# NOTE: +# See https://wiki.apache.org/cassandra/StorageConfiguration for +# full explanations of configuration directives +# /NOTE + +# The name of the cluster. This is mainly used to prevent machines in +# one logical cluster from joining another. +cluster_name: 'Cassandra testing instance' + +# This defines the number of tokens randomly assigned to this node on the ring +# The more tokens, relative to other nodes, the larger the proportion of data +# that this node will store. You probably want all nodes to have the same number +# of tokens assuming they have equal hardware capability. +# +# If you leave this unspecified, Cassandra will use the default of 1 token for legacy compatibility, +# and will use the initial_token as described below. +# +# Specifying initial_token will override this setting. +# +# If you already have a cluster with 1 token per node, and wish to migrate to +# multiple tokens per node, see https://wiki.apache.org/cassandra/Operations +#num_tokens: 256 + +# initial_token allows you to specify tokens manually. While you can use # it with +# vnodes (num_tokens > 1, above) -- in which case you should provide a +# comma-separated list -- it's primarily used when adding nodes # to legacy clusters +# that do not have vnodes enabled. +# initial_token: + +initial_token: ${cassandra.token} + +${cassandra.num.tokens} + +# May either be "true" or "false" to enable globally, or contain a list +# of data centers to enable per-datacenter. +# hinted_handoff_enabled: DC1,DC2 +# See https://wiki.apache.org/cassandra/HintedHandoff +hinted_handoff_enabled: true +# this defines the maximum amount of time a dead host will have hints +# generated. After it has been dead this long, new hints for it will not be +# created until it has been seen alive and gone down again. +max_hint_window_in_ms: 10800000 # 3 hours +# Maximum throttle in KBs per second, per delivery thread. This will be +# reduced proportionally to the number of nodes in the cluster. (If there +# are two nodes in the cluster, each delivery thread will use the maximum +# rate; if there are three, each will throttle to half of the maximum, +# since we expect two nodes to be delivering hints simultaneously.) +hinted_handoff_throttle_in_kb: 1024 +# Number of threads with which to deliver hints; +# Consider increasing this number when you have multi-dc deployments, since +# cross-dc handoff tends to be slower +max_hints_delivery_threads: 2 + +# Maximum throttle in KBs per second, total. This will be +# reduced proportionally to the number of nodes in the cluster. +batchlog_replay_throttle_in_kb: 1024 + +# Authentication backend, implementing IAuthenticator; used to identify users +# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthenticator, +# PasswordAuthenticator}. +# +# - AllowAllAuthenticator performs no checks - set it to disable authentication. +# - PasswordAuthenticator relies on username/password pairs to authenticate +# users. It keeps usernames and hashed passwords in system_auth.credentials table. +# Please increase system_auth keyspace replication factor if you use this authenticator. +authenticator: AllowAllAuthenticator + +# Authorization backend, implementing IAuthorizer; used to limit access/provide permissions +# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthorizer, +# CassandraAuthorizer}. +# +# - AllowAllAuthorizer allows any action to any user - set it to disable authorization. +# - CassandraAuthorizer stores permissions in system_auth.permissions table. Please +# increase system_auth keyspace replication factor if you use this authorizer. +authorizer: AllowAllAuthorizer + +# Validity period for permissions cache (fetching permissions can be an +# expensive operation depending on the authorizer, CassandraAuthorizer is +# one example). Defaults to 2000, set to 0 to disable. +# Will be disabled automatically for AllowAllAuthorizer. +permissions_validity_in_ms: 2000 + +# The partitioner is responsible for distributing groups of rows (by +# partition key) across nodes in the cluster. You should leave this +# alone for new clusters. The partitioner can NOT be changed without +# reloading all data, so when upgrading you should set this to the +# same partitioner you were already using. +# +# Besides Murmur3Partitioner, partitioners included for backwards +# compatibility include RandomPartitioner, ByteOrderedPartitioner, and +# OrderPreservingPartitioner. +# +partitioner: ${cassandra.partitioner} + +# Directories where Cassandra should store data on disk. Cassandra +# will spread data evenly across them, subject to the granularity of +# the configured compaction strategy. +data_file_directories: + - ${cassandra.dir}/data + +# commit log +commitlog_directory: ${cassandra.dir}/commitlog + +# policy for data disk failures: +# stop_paranoid: shut down gossip and Thrift even for single-sstable errors. +# stop: shut down gossip and Thrift, leaving the node effectively dead, but +# can still be inspected via JMX. +# best_effort: stop using the failed disk and respond to requests based on +# remaining available sstables. This means you WILL see obsolete +# data at CL.ONE! +# ignore: ignore fatal errors and let requests fail, as in pre-1.2 Cassandra +disk_failure_policy: stop + +# policy for commit disk failures: +# stop: shut down gossip and Thrift, leaving the node effectively dead, but +# can still be inspected via JMX. +# stop_commit: shutdown the commit log, letting writes collect but +# continuing to service reads, as in pre-2.0.5 Cassandra +# ignore: ignore fatal errors and let the batches fail +commit_failure_policy: stop + +# Maximum size of the key cache in memory. +# +# Each key cache hit saves 1 seek and each row cache hit saves 2 seeks at the +# minimum, sometimes more. The key cache is fairly tiny for the amount of +# time it saves, so it's worthwhile to use it at large numbers. +# The row cache saves even more time, but must contain the entire row, +# so it is extremely space-intensive. It's best to only use the +# row cache if you have hot rows or static rows. +# +# NOTE: if you reduce the size, you may not get you hottest keys loaded on startup. +# +# Default value is empty to make it "auto" (min(5% of Heap (in MB), 100MB)). Set to 0 to disable key cache. +key_cache_size_in_mb: + +# Duration in seconds after which Cassandra should +# save the key cache. Caches are saved to saved_caches_directory as +# specified in this configuration file. +# +# Saved caches greatly improve cold-start speeds, and is relatively cheap in +# terms of I/O for the key cache. Row cache saving is much more expensive and +# has limited use. +# +# Default is 14400 or 4 hours. +key_cache_save_period: 14400 + +# Number of keys from the key cache to save +# Disabled by default, meaning all keys are going to be saved +# key_cache_keys_to_save: 100 + +# Maximum size of the row cache in memory. +# NOTE: if you reduce the size, you may not get you hottest keys loaded on startup. +# +# Default value is 0, to disable row caching. +row_cache_size_in_mb: 0 + +# Duration in seconds after which Cassandra should +# safe the row cache. Caches are saved to saved_caches_directory as specified +# in this configuration file. +# +# Saved caches greatly improve cold-start speeds, and is relatively cheap in +# terms of I/O for the key cache. Row cache saving is much more expensive and +# has limited use. +# +# Default is 0 to disable saving the row cache. +row_cache_save_period: 0 + +# Number of keys from the row cache to save +# Disabled by default, meaning all keys are going to be saved +# row_cache_keys_to_save: 100 + +# The off-heap memory allocator. Affects storage engine metadata as +# well as caches. Experiments show that JEMAlloc saves some memory +# than the native GCC allocator (i.e., JEMalloc is more +# fragmentation-resistant). +# +# Supported values are: NativeAllocator, JEMallocAllocator +# +# If you intend to use JEMallocAllocator you have to install JEMalloc as library and +# modify cassandra-env.sh as directed in the file. +# +# Defaults to NativeAllocator +# memory_allocator: NativeAllocator + +# saved caches +saved_caches_directory: ${cassandra.dir}/saved_caches + +# commitlog_sync may be either "periodic" or "batch." +# When in batch mode, Cassandra won't ack writes until the commit log +# has been fsynced to disk. It will wait up to +# commitlog_sync_batch_window_in_ms milliseconds for other writes, before +# performing the sync. +# +# commitlog_sync: batch +# commitlog_sync_batch_window_in_ms: 50 +# +# the other option is "periodic" where writes may be acked immediately +# and the CommitLog is simply synced every commitlog_sync_period_in_ms +# milliseconds. By default this allows 1024*(CPU cores) pending +# entries on the commitlog queue. If you are writing very large blobs, +# you should reduce that; 16*cores works reasonably well for 1MB blobs. +# It should be at least as large as the concurrent_writes setting. +commitlog_sync: periodic +commitlog_sync_period_in_ms: 10000 +# commitlog_periodic_queue_size: + +# The size of the individual commitlog file segments. A commitlog +# segment may be archived, deleted, or recycled once all the data +# in it (potentially from each columnfamily in the system) has been +# flushed to sstables. +# +# The default size is 32, which is almost always fine, but if you are +# archiving commitlog segments (see commitlog_archiving.properties), +# then you probably want a finer granularity of archiving; 8 or 16 MB +# is reasonable. +commitlog_segment_size_in_mb: 32 + +# any class that implements the SeedProvider interface and has a +# constructor that takes a Map of parameters will do. +seed_provider: + # Addresses of hosts that are deemed contact points. + # Cassandra nodes use this list of hosts to find each other and learn + # the topology of the ring. You must change this if you are running + # multiple nodes! + - class_name: org.apache.cassandra.locator.SimpleSeedProvider + parameters: + # seeds is actually a comma-delimited list of addresses. + # Ex: ",," + - seeds: ${cassandra.seed} + +# For workloads with more data than can fit in memory, Cassandra's +# bottleneck will be reads that need to fetch data from +# disk. "concurrent_reads" should be set to (16 * number_of_drives) in +# order to allow the operations to enqueue low enough in the stack +# that the OS and drives can reorder them. +# +# On the other hand, since writes are almost never IO bound, the ideal +# number of "concurrent_writes" is dependent on the number of cores in +# your system; (8 * number_of_cores) is a good rule of thumb. +concurrent_reads: 32 +concurrent_writes: 32 + +# Total memory to use for sstable-reading buffers. Defaults to +# the smaller of 1/4 of heap or 512MB. +# file_cache_size_in_mb: 512 + +# Total memory to use for memtables. Cassandra will flush the largest +# memtable when this much memory is used. +# If omitted, Cassandra will set it to 1/4 of the heap. +# memtable_total_space_in_mb: 2048 + +# Total space to use for commitlogs. Since commitlog segments are +# mmapped, and hence use up address space, the default size is 32 +# on 32-bit JVMs, and 1024 on 64-bit JVMs. +# +# If space gets above this value (it will round up to the next nearest +# segment multiple), Cassandra will flush every dirty CF in the oldest +# segment and remove it. So a small total commitlog space will tend +# to cause more flush activity on less-active columnfamilies. +# commitlog_total_space_in_mb: 4096 + +# This sets the amount of memtable flush writer threads. These will +# be blocked by disk io, and each one will hold a memtable in memory +# while blocked. If you have a large heap and many data directories, +# you can increase this value for better flush performance. +# By default this will be set to the amount of data directories defined. +#memtable_flush_writers: 1 + +# Whether to, when doing sequential writing, fsync() at intervals in +# order to force the operating system to flush the dirty +# buffers. Enable this to avoid sudden dirty buffer flushing from +# impacting read latencies. Almost always a good idea on SSDs; not +# necessarily on platters. +trickle_fsync: false +trickle_fsync_interval_in_kb: 10240 + +# TCP port, for commands and data +storage_port: 7000 + +# SSL port, for encrypted communication. Unused unless enabled in +# encryption_options +ssl_storage_port: 7001 + +# Address to bind to and tell other Cassandra nodes to connect to. You +# _must_ change this if you want multiple nodes to be able to +# communicate! +# +# Leaving it blank leaves it up to InetAddress.getLocalHost(). This +# will always do the Right Thing _if_ the node is properly configured +# (hostname, name resolution, etc), and the Right Thing is to use the +# address associated with the hostname (it might not be). +# +# Setting this to 0.0.0.0 is always wrong. +listen_address: ${cassandra.ip} + +# Address to broadcast to other Cassandra nodes +# Leaving this blank will set it to the same value as listen_address +# broadcast_address: 1.2.3.4 + +# Internode authentication backend, implementing IInternodeAuthenticator; +# used to allow/disallow connections from peer nodes. +# internode_authenticator: org.apache.cassandra.auth.AllowAllInternodeAuthenticator + +# Whether to start the native transport server. +# Please note that the address on which the native transport is bound is the +# same as the rpc_address. The port however is different and specified below. +start_native_transport: true +# port for the CQL native transport to listen for clients on +native_transport_port: 9042 +# The maximum threads for handling requests when the native transport is used. +# This is similar to rpc_max_threads though the default differs slightly (and +# there is no native_transport_min_threads, idle threads will always be stopped +# after 30 seconds). +# native_transport_max_threads: 128 +# +# The maximum size of allowed frame. Frame (requests) larger than this will +# be rejected as invalid. The default is 256MB. +# native_transport_max_frame_size_in_mb: 256 + +# Whether to start the thrift rpc server. +start_rpc: true + +# The address to bind the Thrift RPC service and native transport +# server -- clients connect here. +# +# Leaving this blank has the same effect it does for ListenAddress, +# (i.e. it will be based on the configured hostname of the node). +# +# Note that unlike ListenAddress above, it is allowed to specify 0.0.0.0 +# here if you want to listen on all interfaces, but that will break clients +# that rely on node auto-discovery. +rpc_address: ${cassandra.ip} +# port for Thrift to listen for clients on +rpc_port: 9160 + +# enable or disable keepalive on rpc/native connections +rpc_keepalive: true + +# Cassandra provides two out-of-the-box options for the RPC Server: +# +# sync -> One thread per thrift connection. For a very large number of clients, memory +# will be your limiting factor. On a 64 bit JVM, 180KB is the minimum stack size +# per thread, and that will correspond to your use of virtual memory (but physical memory +# may be limited depending on use of stack space). +# +# hsha -> Stands for "half synchronous, half asynchronous." All thrift clients are handled +# asynchronously using a small number of threads that does not vary with the amount +# of thrift clients (and thus scales well to many clients). The rpc requests are still +# synchronous (one thread per active request). +# +# The default is sync because on Windows hsha is about 30% slower. On Linux, +# sync/hsha performance is about the same, with hsha of course using less memory. +# +# Alternatively, can provide your own RPC server by providing the fully-qualified class name +# of an o.a.c.t.TServerFactory that can create an instance of it. +rpc_server_type: sync + +# Uncomment rpc_min|max_thread to set request pool size limits. +# +# Regardless of your choice of RPC server (see above), the number of maximum requests in the +# RPC thread pool dictates how many concurrent requests are possible (but if you are using the sync +# RPC server, it also dictates the number of clients that can be connected at all). +# +# The default is unlimited and thus provides no protection against clients overwhelming the server. You are +# encouraged to set a maximum that makes sense for you in production, but do keep in mind that +# rpc_max_threads represents the maximum number of client requests this server may execute concurrently. +# +# rpc_min_threads: 16 +# rpc_max_threads: 2048 + +# uncomment to set socket buffer sizes on rpc connections +# rpc_send_buff_size_in_bytes: +# rpc_recv_buff_size_in_bytes: + +# Uncomment to set socket buffer size for internode communication +# Note that when setting this, the buffer size is limited by net.core.wmem_max +# and when not setting it it is defined by net.ipv4.tcp_wmem +# See: +# /proc/sys/net/core/wmem_max +# /proc/sys/net/core/rmem_max +# /proc/sys/net/ipv4/tcp_wmem +# /proc/sys/net/ipv4/tcp_wmem +# and: man tcp +# internode_send_buff_size_in_bytes: +# internode_recv_buff_size_in_bytes: + +# Frame size for thrift (maximum message length). +thrift_framed_transport_size_in_mb: 15 + +# Set to true to have Cassandra create a hard link to each sstable +# flushed or streamed locally in a backups/ subdirectory of the +# keyspace data. Removing these links is the operator's +# responsibility. +incremental_backups: false + +# Whether or not to take a snapshot before each compaction. Be +# careful using this option, since Cassandra won't clean up the +# snapshots for you. Mostly useful if you're paranoid when there +# is a data format change. +snapshot_before_compaction: false + +# Whether or not a snapshot is taken of the data before keyspace truncation +# or dropping of column families. The STRONGLY advised default of true +# should be used to provide data safety. If you set this flag to false, you will +# lose data on truncation or drop. +auto_snapshot: true + +# When executing a scan, within or across a partition, we need to keep the +# tombstones seen in memory so we can return them to the coordinator, which +# will use them to make sure other replicas also know about the deleted rows. +# With workloads that generate a lot of tombstones, this can cause performance +# problems and even exaust the server heap. +# (https://www.datastax.com/dev/blog/cassandra-anti-patterns-queues-and-queue-like-datasets) +# Adjust the thresholds here if you understand the dangers and want to +# scan more tombstones anyway. These thresholds may also be adjusted at runtime +# using the StorageService mbean. +tombstone_warn_threshold: 1000 +tombstone_failure_threshold: 100000 + +# Add column indexes to a row after its contents reach this size. +# Increase if your column values are large, or if you have a very large +# number of columns. The competing causes are, Cassandra has to +# deserialize this much of the row to read a single column, so you want +# it to be small - at least if you do many partial-row reads - but all +# the index data is read for each access, so you don't want to generate +# that wastefully either. +column_index_size_in_kb: 64 + + +# Log WARN on any batch size exceeding this value. 5kb per batch by default. +# Caution should be taken on increasing the size of this threshold as it can lead to node instability. +batch_size_warn_threshold_in_kb: 5 + +# Number of simultaneous compactions to allow, NOT including +# validation "compactions" for anti-entropy repair. Simultaneous +# compactions can help preserve read performance in a mixed read/write +# workload, by mitigating the tendency of small sstables to accumulate +# during a single long running compactions. The default is usually +# fine and if you experience problems with compaction running too +# slowly or too fast, you should look at +# compaction_throughput_mb_per_sec first. +# +# concurrent_compactors defaults to the number of cores. +# Uncomment to make compaction mono-threaded, the pre-0.8 default. +#concurrent_compactors: 1 + +# Throttles compaction to the given total throughput across the entire +# system. The faster you insert data, the faster you need to compact in +# order to keep the sstable count down, but in general, setting this to +# 16 to 32 times the rate you are inserting data is more than sufficient. +# Setting this to 0 disables throttling. Note that this account for all types +# of compaction, including validation compaction. +compaction_throughput_mb_per_sec: 16 + +# Throttles all outbound streaming file transfers on this node to the +# given total throughput in Mbps. This is necessary because Cassandra does +# mostly sequential IO when streaming data during bootstrap or repair, which +# can lead to saturating the network connection and degrading rpc performance. +# When unset, the default is 200 Mbps or 25 MB/s. +# stream_throughput_outbound_megabits_per_sec: 200 + +# How long the coordinator should wait for read operations to complete +read_request_timeout_in_ms: 30000 +# How long the coordinator should wait for seq or index scans to complete +range_request_timeout_in_ms: 30000 +# How long the coordinator should wait for writes to complete +write_request_timeout_in_ms: 30000 +# How long a coordinator should continue to retry a CAS operation +# that contends with other proposals for the same row +cas_contention_timeout_in_ms: 30000 +# How long the coordinator should wait for truncates to complete +# (This can be much longer, because unless auto_snapshot is disabled +# we need to flush first so we can snapshot before removing the data.) +truncate_request_timeout_in_ms: 60000 +# The default timeout for other, miscellaneous operations +request_timeout_in_ms: 30000 + +# Enable operation timeout information exchange between nodes to accurately +# measure request timeouts. If disabled, replicas will assume that requests +# were forwarded to them instantly by the coordinator, which means that +# under overload conditions we will waste that much extra time processing +# already-timed-out requests. +# +# Warning: before enabling this property make sure to ntp is installed +# and the times are synchronized between the nodes. +cross_node_timeout: false + +# Enable socket timeout for streaming operation. +# When a timeout occurs during streaming, streaming is retried from the start +# of the current file. This _can_ involve re-streaming an important amount of +# data, so you should avoid setting the value too low. +# Default value is 0, which never timeout streams. +# streaming_socket_timeout_in_ms: 0 + +# phi value that must be reached for a host to be marked down. +# most users should never need to adjust this. +# phi_convict_threshold: 8 + +# endpoint_snitch -- Set this to a class that implements +# IEndpointSnitch. The snitch has two functions: +# - it teaches Cassandra enough about your network topology to route +# requests efficiently +# - it allows Cassandra to spread replicas around your cluster to avoid +# correlated failures. It does this by grouping machines into +# "datacenters" and "racks." Cassandra will do its best not to have +# more than one replica on the same "rack" (which may not actually +# be a physical location) +# +# IF YOU CHANGE THE SNITCH AFTER DATA IS INSERTED INTO THE CLUSTER, +# YOU MUST RUN A FULL REPAIR, SINCE THE SNITCH AFFECTS WHERE REPLICAS +# ARE PLACED. +# +# Out of the box, Cassandra provides +# - SimpleSnitch: +# Treats Strategy order as proximity. This can improve cache +# locality when disabling read repair. Only appropriate for +# single-datacenter deployments. +# - GossipingPropertyFileSnitch +# This should be your go-to snitch for production use. The rack +# and datacenter for the local node are defined in +# cassandra-rackdc.properties and propagated to other nodes via +# gossip. If cassandra-topology.properties exists, it is used as a +# fallback, allowing migration from the PropertyFileSnitch. +# - PropertyFileSnitch: +# Proximity is determined by rack and data center, which are +# explicitly configured in cassandra-topology.properties. +# - Ec2Snitch: +# Appropriate for EC2 deployments in a single Region. Loads Region +# and Availability Zone information from the EC2 API. The Region is +# treated as the datacenter, and the Availability Zone as the rack. +# Only private IPs are used, so this will not work across multiple +# Regions. +# - Ec2MultiRegionSnitch: +# Uses public IPs as broadcast_address to allow cross-region +# connectivity. (Thus, you should set seed addresses to the public +# IP as well.) You will need to open the storage_port or +# ssl_storage_port on the public IP firewall. (For intra-Region +# traffic, Cassandra will switch to the private IP after +# establishing a connection.) +# - RackInferringSnitch: +# Proximity is determined by rack and data center, which are +# assumed to correspond to the 3rd and 2nd octet of each node's IP +# address, respectively. Unless this happens to match your +# deployment conventions, this is best used as an example of +# writing a custom Snitch class and is provided in that spirit. +# +# You can use a custom Snitch by setting this to the full class name +# of the snitch, which will be assumed to be on your classpath. +endpoint_snitch: SimpleSnitch + +# controls how often to perform the more expensive part of host score +# calculation +dynamic_snitch_update_interval_in_ms: 100 +# controls how often to reset all host scores, allowing a bad host to +# possibly recover +dynamic_snitch_reset_interval_in_ms: 600000 +# if set greater than zero and read_repair_chance is < 1.0, this will allow +# 'pinning' of replicas to hosts in order to increase cache capacity. +# The badness threshold will control how much worse the pinned host has to be +# before the dynamic snitch will prefer other replicas over it. This is +# expressed as a double which represents a percentage. Thus, a value of +# 0.2 means Cassandra would continue to prefer the static snitch values +# until the pinned host was 20% worse than the fastest. +dynamic_snitch_badness_threshold: 0.1 + +# request_scheduler -- Set this to a class that implements +# RequestScheduler, which will schedule incoming client requests +# according to the specific policy. This is useful for multi-tenancy +# with a single Cassandra cluster. +# NOTE: This is specifically for requests from the client and does +# not affect inter node communication. +# org.apache.cassandra.scheduler.NoScheduler - No scheduling takes place +# org.apache.cassandra.scheduler.RoundRobinScheduler - Round robin of +# client requests to a node with a separate queue for each +# request_scheduler_id. The scheduler is further customized by +# request_scheduler_options as described below. +request_scheduler: org.apache.cassandra.scheduler.NoScheduler + +# Scheduler Options vary based on the type of scheduler +# NoScheduler - Has no options +# RoundRobin +# - throttle_limit -- The throttle_limit is the number of in-flight +# requests per client. Requests beyond +# that limit are queued up until +# running requests can complete. +# The value of 80 here is twice the number of +# concurrent_reads + concurrent_writes. +# - default_weight -- default_weight is optional and allows for +# overriding the default which is 1. +# - weights -- Weights are optional and will default to 1 or the +# overridden default_weight. The weight translates into how +# many requests are handled during each turn of the +# RoundRobin, based on the scheduler id. +# +# request_scheduler_options: +# throttle_limit: 80 +# default_weight: 5 +# weights: +# Keyspace1: 1 +# Keyspace2: 5 + +# request_scheduler_id -- An identifier based on which to perform +# the request scheduling. Currently the only valid option is keyspace. +# request_scheduler_id: keyspace + +# Enable or disable inter-node encryption +# Default settings are TLS v1, RSA 1024-bit keys (it is imperative that +# users generate their own keys) TLS_RSA_WITH_AES_128_CBC_SHA as the cipher +# suite for authentication, key exchange and encryption of the actual data transfers. +# Use the DHE/ECDHE ciphers if running in FIPS 140 compliant mode. +# NOTE: No custom encryption options are enabled at the moment +# The available internode options are : all, none, dc, rack +# +# If set to dc cassandra will encrypt the traffic between the DCs +# If set to rack cassandra will encrypt the traffic between the racks +# +# The passwords used in these options must match the passwords used when generating +# the keystore and truststore. For instructions on generating these files, see: +# https://download.oracle.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html#CreateKeystore +# +server_encryption_options: + internode_encryption: none + keystore: conf/.keystore + keystore_password: cassandra + truststore: conf/.truststore + truststore_password: cassandra + # More advanced defaults below: + # protocol: TLS + # algorithm: SunX509 + # store_type: JKS + # cipher_suites: [TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA] + # require_client_auth: false + +# enable or disable client/server encryption. + +client_encryption_options: + enabled: ${cassandra.clientenc.enabled} + keystore: ${cassandra.clientenc.kspath} + keystore_password: ${cassandra.clientenc.kspw} + require_client_auth: ${cassandra.clientenc.ccert} + # Set trustore and truststore_password if require_client_auth is true + truststore: ${cassandra.clientenc.tspath} #conf/.truststore + truststore_password: ${cassandra.clientenc.tspw} + # More advanced defaults below: + # protocol: TLS + # algorithm: SunX509 + # store_type: JKS + cipher_suites: [TLS_RSA_WITH_AES_128_CBC_SHA] + # cipher_suites: [TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_ + +# internode_compression controls whether traffic between nodes is +# compressed. +# can be: all - all traffic is compressed +# dc - traffic between different datacenters is compressed +# none - nothing is compressed. +internode_compression: all + +# Enable or disable tcp_nodelay for inter-dc communication. +# Disabling it will result in larger (but fewer) network packets being sent, +# reducing overhead from the TCP protocol itself, at the cost of increasing +# latency if you block for cross-datacenter responses. +inter_dc_tcp_nodelay: false diff --git a/config/cassandra/log4j-server.properties b/config/cassandra/log4j-server.properties new file mode 100644 index 0000000..5940a91 --- /dev/null +++ b/config/cassandra/log4j-server.properties @@ -0,0 +1,41 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# for production, you should probably set pattern to %c instead of %l. +# (%l is slower.) + +# output messages into a rolling log file as well as stdout +log4j.rootLogger=INFO,stdout,R + +# stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%5p %d{HH:mm:ss,SSS} %m%n + +# rolling log file +log4j.appender.R=org.apache.log4j.RollingFileAppender +log4j.appender.R.maxFileSize=20MB +log4j.appender.R.maxBackupIndex=50 +log4j.appender.R.layout=org.apache.log4j.PatternLayout +log4j.appender.R.layout.ConversionPattern=%5p [%t] %d{ISO8601} %F (line %L) %m%n +# Edit the next line to point to your logs directory +log4j.appender.R.File=${cassandra.dir}/system.log + +# Application logging options +#log4j.logger.org.apache.cassandra=DEBUG +#log4j.logger.org.apache.cassandra.graph=DEBUG +#log4j.logger.org.apache.cassandra.service.StorageProxy=DEBUG + diff --git a/config/cassandra/log4j-tools.properties b/config/cassandra/log4j-tools.properties new file mode 100644 index 0000000..a8f4d9f --- /dev/null +++ b/config/cassandra/log4j-tools.properties @@ -0,0 +1,27 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# for production, you should probably set the root to INFO +# and the pattern to %c instead of %l. (%l is slower.) + +# output messages into a rolling log file as well as stdout +log4j.rootLogger=WARN,stderr + +# stderr +log4j.appender.stderr=org.apache.log4j.ConsoleAppender +log4j.appender.stderr.target=System.err +log4j.appender.stderr.layout=org.apache.log4j.PatternLayout +log4j.appender.stderr.layout.ConversionPattern=%5p %d{HH:mm:ss,SSS} %m%n diff --git a/config/static/README.md b/config/static/README.md new file mode 100644 index 0000000..961fb89 --- /dev/null +++ b/config/static/README.md @@ -0,0 +1,3 @@ +This keypair is just for testing. Under no circumstances should it be +used on any system that matters. It's been public on github and +compromised since the day it was generated. \ No newline at end of file diff --git a/config/static/test.crt b/config/static/test.crt new file mode 100644 index 0000000..607b3d1 --- /dev/null +++ b/config/static/test.crt @@ -0,0 +1,21 @@ +Bag Attributes + friendlyName: cassandra + 2.16.840.1.113894.746875.1.1: +subject=/C=Unknown/ST=Unknown/L=Unknown/O=Unknown/OU=Unknown/CN=localhost +issuer=/C=Unknown/ST=Unknown/L=Unknown/O=Unknown/OU=Unknown/CN=localhost +-----BEGIN CERTIFICATE----- +MIICdjCCAd+gAwIBAgIEctIFMzANBgkqhkiG9w0BAQsFADBuMRAwDgYDVQQGEwdV +bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD +VQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRIwEAYDVQQDEwlsb2NhbGhv +c3QwHhcNMTQwOTAxMDczNTI2WhcNMTQxMTMwMDczNTI2WjBuMRAwDgYDVQQGEwdV +bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD +VQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRIwEAYDVQQDEwlsb2NhbGhv +c3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMcvbCEQJfqxZJoGSLZSrpf1 +1BGnhDLZLorkCcERNKyduiHRWE62M9Y0h7mNcQ4EXaNIeJS9G9LvQrO3ojSyU2bx +vYEgiySnabcZbC7tR4Y9VdW7WRb/qkzmCXRCnj6merQXxsc3UxIRQOBaSnjfDMwv +OaXMG9pJddP1rSYKVLcPAgMBAAGjITAfMB0GA1UdDgQWBBSosZvuR4EuYrpf3mop +uif6CXEUWzANBgkqhkiG9w0BAQsFAAOBgQAAS54akNJUnlfj3a/vCM+Zf0C5wJIy +KMGj+hg1sSs6hrvH4xjti+p8fqMffjazxtjhho2orWd/A0/5pFMzGg1hy+OMu02K +mF9EyveKm5Dw35Y/6o2IXIeAz1/zdQfoPbEniu7MSjyX8E0ZmVWqnombOusOR/tE +mc2ku/RrFXlOUA== +-----END CERTIFICATE----- diff --git a/config/static/test.keystore b/config/static/test.keystore new file mode 100644 index 0000000..729078c Binary files /dev/null and b/config/static/test.keystore differ diff --git a/config/static/test.truststore b/config/static/test.truststore new file mode 100644 index 0000000..005300c Binary files /dev/null and b/config/static/test.truststore differ diff --git a/disabled.travis.yml b/disabled.travis.yml new file mode 100644 index 0000000..b9383c8 --- /dev/null +++ b/disabled.travis.yml @@ -0,0 +1,116 @@ +# Copyright 2019 JanusGraph Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +language: java +os: linux +dist: xenial +services: + - docker +jdk: + - openjdk8 +addons: + apt: + update: true + +git: + depth: false + +cache: + directories: + - ${HOME}/.m2 + +stages: + - test + - documentation + - deploy + +x-template-full: + &FULL_BUILD_JOB + stage: test + install: travis_wait mvn clean install -DskipTests=true -Dmaven.javadoc.skip=true --show-version ${INSTALL_ARGS}; + script: travis_wait 50 mvn verify ${ARGS}; + if: type = cron OR commit_message =~ /\[full build\]/ + +x-template-standard: + &STANDARD_TEST_JOB + stage: test + install: travis_wait mvn clean install -DskipTests=true -Dmaven.javadoc.skip=true --show-version ${INSTALL_ARGS}; + script: travis_wait 50 mvn verify -Pcoverage ${ARGS}; + after_success: bash <(curl -s https://codecov.io/bash); + if: commit_message !~ /\[doc only\]/ + +jobs: + include: + - <<: *STANDARD_TEST_JOB + env: ARGS='-Pcassandra2-byteordered -Dtest=**/diskstorage/cassandra/thrift/*' + - <<: *STANDARD_TEST_JOB + env: ARGS='-Pcassandra2-murmur -Dtest=**/diskstorage/cassandra/thrift/*' + - <<: *STANDARD_TEST_JOB + env: ARGS='-Pcassandra2-murmur-ssl -Dtest=**/diskstorage/cassandra/thrift/ThriftStoreTest.java' + - <<: *STANDARD_TEST_JOB + env: ARGS='-Pcassandra2-byteordered -Dtest=**/graphdb/thrift/*' + - <<: *STANDARD_TEST_JOB + env: ARGS='-Pcassandra2-murmur -Dtest=**/graphdb/thrift/*' + + - <<: *STANDARD_TEST_JOB + env: ARGS='-Pcassandra2-byteordered -Dtest=**/diskstorage/cassandra/astyanax/*' + - <<: *STANDARD_TEST_JOB + env: ARGS='-Pcassandra2-murmur -Dtest=**/diskstorage/cassandra/astyanax/*' + - <<: *STANDARD_TEST_JOB + env: ARGS='-Pcassandra2-murmur-ssl -Dtest=**/diskstorage/cassandra/astyanax/AstyanaxStoreTest.java' + - <<: *STANDARD_TEST_JOB + env: ARGS='-Pcassandra2-byteordered -Dtest=**/graphdb/astyanax/*' + - <<: *STANDARD_TEST_JOB + env: ARGS='-Pcassandra2-murmur -Dtest=**/graphdb/astyanax/*' + - <<: *STANDARD_TEST_JOB + env: ARGS='-Dtest=**/diskstorage/cassandra/embedded/*' + - <<: *STANDARD_TEST_JOB + env: ARGS='-Dtest=***/cassandra/*,*/graphdb/embedded/*' + - <<: *STANDARD_TEST_JOB + env: ARGS='-Pcassandra2-murmur -Dtest=**/hadoop/*' + + - <<: *FULL_BUILD_JOB + env: ARGS='-Pcassandra3-byteordered -Dtest=**/graphdb/thrift/* -Dcassandra.docker.version=3.11.4' + - <<: *FULL_BUILD_JOB + env: ARGS='-Pcassandra3-murmur -Dtest=**/graphdb/thrift/* -Dcassandra.docker.version=3.11.4' + - <<: *FULL_BUILD_JOB + env: ARGS='-Pcassandra3-byteordered -Dtest=**/diskstorage/cassandra/astyanax/* -Dcassandra.docker.version=3.11.4' + - <<: *FULL_BUILD_JOB + env: ARGS='-Pcassandra3-murmur -Dtest=**/diskstorage/cassandra/astyanax/* -Dcassandra.docker.version=3.11.4' + - <<: *FULL_BUILD_JOB + env: ARGS='-Pcassandra3-murmur-ssl -Dtest=**/diskstorage/cassandra/astyanax/AstyanaxStoreTest.java -Dcassandra.docker.version=3.11.4' + - <<: *FULL_BUILD_JOB + env: ARGS='-Pcassandra3-byteordered -Dtest=**/graphdb/astyanax/* -Dcassandra.docker.version=3.11.4' + - <<: *FULL_BUILD_JOB + env: ARGS='-Pcassandra3-murmur -Dtest=**/graphdb/astyanax/* -Dcassandra.docker.version=3.11.4' + + - <<: *FULL_BUILD_JOB + env: ARGS='-Pcassandra3-byteordered -Dtest=**/graphdb/thrift/* -Dcassandra.docker.version=3.0.18' + - <<: *FULL_BUILD_JOB + env: ARGS='-Pcassandra3-murmur -Dtest=**/graphdb/thrift/* -Dcassandra.docker.version=3.0.18' + - <<: *FULL_BUILD_JOB + env: ARGS='-Pcassandra3-byteordered -Dtest=**/diskstorage/cassandra/astyanax/* -Dcassandra.docker.version=3.0.18' + - <<: *FULL_BUILD_JOB + env: ARGS='-Pcassandra3-murmur -Dtest=**/diskstorage/cassandra/astyanax/* -Dcassandra.docker.version=3.0.18' + - <<: *FULL_BUILD_JOB + env: ARGS='-Pcassandra3-murmur-ssl -Dtest=**/diskstorage/cassandra/astyanax/AstyanaxStoreTest.java -Dcassandra.docker.version=3.0.18' + - <<: *FULL_BUILD_JOB + env: ARGS='-Pcassandra3-byteordered -Dtest=**/graphdb/astyanax/* -Dcassandra.docker.version=3.0.18' + - <<: *FULL_BUILD_JOB + env: ARGS='-Pcassandra3-murmur -Dtest=**/graphdb/astyanax/* -Dcassandra.docker.version=3.0.18' + +# Syntax and more info: https://docs.travis-ci.com/user/notifications +notifications: + email: + - janusgraph-ci@googlegroups.com diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..dc66f2c --- /dev/null +++ b/pom.xml @@ -0,0 +1,503 @@ + + 4.0.0 + + org.janusgraph + janusgraph-cassandra + 0.6.0-SNAPSHOT + jar + JanusGraph-Cassandra: Distributed Graph Database + https://janusgraph.org + + + + 0.6.0 + 3.10.2 + 2.8 + 3.2.2 + 4.0.0 + 2.2.13 + 1.12.4 + 3.9.0 + + 1.8 + 1.8 + + 0.3.0 + com.github.jbellis + -javaagent:${com.github.jbellis:jamm:jar} + ${test.javaagent.opts} + -Xms256m -Xmx1280m -ea -XX:+HeapDumpOnOutOfMemoryError ${test.extra.jvm.opts} + -Xms256m -Xmx768m -ea -XX:+HeapDumpOnOutOfMemoryError ${test.extra.jvm.opts} + ${basedir}/.. + ${default.test.jvm.opts} + byteordered + murmur + 2.2.14 + 3.11.4 + + + + + + org.janusgraph + janusgraph-core + ${janusgraph.version} + + + org.janusgraph + janusgraph-hadoop + ${janusgraph.version} + true + + + javax.annotation + javax.annotation-api + + + + + com.datastax.cassandra + cassandra-driver-core + ${cassandra-driver.version} + true + + + org.janusgraph + janusgraph-hadoop + ${janusgraph.version} + tests + test + + + org.janusgraph + janusgraph-backend-testutils + ${janusgraph.version} + test + + + io.dropwizard.metrics + metrics-jvm + ${metrics.version} + + + + org.slf4j + slf4j-log4j12 + 1.7.12 + true + + + ch.qos.logback + logback-classic + 1.1.3 + + + + + net.java.dev.jna + jna + ${jna.version} + true + + + org.apache.cassandra + cassandra-all + ${cassandra.version} + + + + + org.mindrot + jbcrypt + + + net.java.dev.jna + jna + + + org.slf4j + jcl-over-slf4j + + + io.dropwizard.metrics + metrics-core + + + io.dropwizard.metrics + metrics-jvm + + + + org.slf4j + log4j-over-slf4j + + + junit + junit + + + com.ning + compress-lzf + + + + + commons-pool + commons-pool + 1.6 + + + com.netflix.astyanax + astyanax-core + ${astyanax.version} + + + com.netflix.astyanax + astyanax-thrift + ${astyanax.version} + + + com.netflix.astyanax + astyanax-cassandra + ${astyanax.version} + + + com.netflix.astyanax + astyanax-recipes + ${astyanax.version} + + + org.xerial.snappy + snappy-java + 1.0.5-M3 + + + javax.validation + validation-api + 1.1.0.Final + + + net.jpountz.lz4 + lz4 + 1.3.0 + + + org.testcontainers + testcontainers + ${testcontainers.version} + test + + + org.testcontainers + cassandra + ${testcontainers.version} + test + + + org.testcontainers + junit-jupiter + ${testcontainers.version} + test + + + + + + + ${basedir}/src/main/resources + true + + + + + ${basedir}/src/test/resources + + + + + + maven-compiler-plugin + 3.1 + + ${compiler.source} + ${compiler.target} + + 500 + + + -Xlint:unchecked + + + + + maven-resources-plugin + + + filter-cassandra-bop-config + process-test-resources + + copy-resources + + + ${project.build.directory}/cassandra/conf/localhost-bop + + ${basedir}/config/cassandra-filters/localhost-bop.properties + + + + ${basedir}/config/cassandra + true + + + + + + filter-cassandra-murmur-config + process-test-resources + + copy-resources + + + ${project.build.directory}/cassandra/conf/localhost-murmur + + ${basedir}/config/cassandra-filters/localhost-murmur.properties + + + + ${basedir}/config/cassandra + true + + + + + + + + maven-surefire-plugin + 2.22.1 + + ${test.cassandra.excludes} + + ${test.jvm.opts} + 1 + false + none + false + 1 + + file:${project.build.testOutputDirectory}/log4j.properties + ${cassandra.docker.image} + ${cassandra.docker.version} + ${cassandra.docker.partitioner} + ${cassandra.docker.useSSL} + ${cassandra.docker.useDefaultConfigFromImage} + + + + + default-test + none + + + cassandra-test + + test + + test + + + ${test.murmur}-serial-test + + test + + test + + + SERIAL_TESTS + none + false + 1 + alphabetical + ${test.skip.murmur-serial} + + + + + + maven-dependency-plugin + + + set-dependency-properties + process-test-classes + + properties + + + + + copy + process-resources + + copy + + + + + ${jamm.group} + jamm + ${jamm.version} + ${project.build.directory} + + + + + + + + maven-jar-plugin + + + pack-test-jar + + prepare-package + + test-jar + + + + + + + + + + coverage + + false + + + ${jacoco.opts} ${test.javaagent.opts} + + + + + cassandra2-byteordered + + false + + + ${default.test.jvm.opts} -Dtest.cassandra.confdir=${project.build.directory}/cassandra/conf/localhost-bop -Dtest.cassandra.datadir=${project.build.directory}/cassandra/data/localhost-bop + ${test.docker.version.cassandra2} + ${test.byteordered} + true + ${test.excluded.groups},SERIAL_TESTS + + + + cassandra2-murmur + + true + + + ${default.test.jvm.opts} -Dtest.cassandra.confdir=${project.build.directory}/cassandra/conf/localhost-murmur -Dtest.cassandra.datadir=${project.build.directory}/cassandra/data/localhost-murmur + ${test.docker.version.cassandra2} + ${test.murmur} + false + ${test.excluded.groups},SERIAL_TESTS + + + + cassandra2-murmur-ssl + + false + + + ${test.docker.version.cassandra2} + ${test.murmur} + true + false + ${test.excluded.groups},SERIAL_TESTS + + + + + cassandra3-byteordered + + false + + + ${default.test.jvm.opts} -Dtest.cassandra.confdir=${project.build.directory}/cassandra/conf/localhost-bop -Dtest.cassandra.datadir=${project.build.directory}/cassandra/data/localhost-bop + ${test.docker.version.cassandra3} + ${test.byteordered} + true + ${test.excluded.groups},SERIAL_TESTS + + + + cassandra3-murmur + + false + + + ${default.test.jvm.opts} -Dtest.cassandra.confdir=${project.build.directory}/cassandra/conf/localhost-murmur -Dtest.cassandra.datadir=${project.build.directory}/cassandra/data/localhost-murmur + ${test.docker.version.cassandra3} + ${test.murmur} + false + ${test.excluded.groups},SERIAL_TESTS + + + + cassandra3-murmur-ssl + + false + + + ${test.docker.version.cassandra3} + ${test.murmur} + true + false + ${test.excluded.groups},SERIAL_TESTS + + + + + scylladb + + 3.0.9 + scylladb/scylla + true + true + true + true + true + + + + diff --git a/src/main/java/org/apache/cassandra/thrift/TBinaryProtocol.java b/src/main/java/org/apache/cassandra/thrift/TBinaryProtocol.java new file mode 100644 index 0000000..efb7c13 --- /dev/null +++ b/src/main/java/org/apache/cassandra/thrift/TBinaryProtocol.java @@ -0,0 +1,34 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.apache.cassandra.thrift; + +import org.apache.thrift.transport.TTransport; + +/** + * This is necessary until Astyanax is updated to "officially" support Cassandra 2.0.x. + * + * The story is as follows: Cassandra 2.0.x moved to the new version of Thrift (0.9.x) + * where problem with TBinaryProtocol was fixed, so TBinaryProtocol class was removed as no longer necessary. + * Astyanax in it's current state still wants to use TBinaryProtocol bundled with Cassandra, + * so this class is essentially tricking it (Astyanax) into believing that class is still there. + * + * No other changes necessary to make Astyanax work with Cassandra 2.0.x because Thrift API is completely in-tact. + */ + +public class TBinaryProtocol extends org.apache.thrift.protocol.TBinaryProtocol { + public TBinaryProtocol(TTransport trans) { + super(trans); + } +} diff --git a/src/main/java/org/janusgraph/diskstorage/cassandra/AbstractCassandraStoreManager.java b/src/main/java/org/janusgraph/diskstorage/cassandra/AbstractCassandraStoreManager.java new file mode 100644 index 0000000..bf8b8b2 --- /dev/null +++ b/src/main/java/org/janusgraph/diskstorage/cassandra/AbstractCassandraStoreManager.java @@ -0,0 +1,340 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra; + +import java.util.*; + +import com.google.common.collect.ImmutableMap; +import org.janusgraph.core.JanusGraphException; +import org.janusgraph.diskstorage.BackendException; +import org.janusgraph.diskstorage.BaseTransactionConfig; +import org.janusgraph.diskstorage.common.DistributedStoreManager; +import org.janusgraph.diskstorage.configuration.ConfigElement; +import org.janusgraph.diskstorage.configuration.ConfigNamespace; +import org.janusgraph.diskstorage.configuration.ConfigOption; +import org.janusgraph.diskstorage.configuration.Configuration; +import org.janusgraph.diskstorage.keycolumnvalue.KeyColumnValueStoreManager; +import org.janusgraph.diskstorage.keycolumnvalue.StandardStoreFeatures; +import org.janusgraph.diskstorage.keycolumnvalue.StoreFeatures; +import org.janusgraph.diskstorage.keycolumnvalue.StoreTransaction; +import org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration; +import org.janusgraph.graphdb.configuration.PreInitializeConfigOptions; + +import static org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration.*; + +import org.apache.cassandra.dht.IPartitioner; +import org.janusgraph.hadoop.CassandraHadoopStoreManager; + +/** + * @author Matthias Broecheler (me@matthiasb.com) + */ +@PreInitializeConfigOptions +public abstract class AbstractCassandraStoreManager extends DistributedStoreManager implements KeyColumnValueStoreManager { + + public enum Partitioner { + + RANDOM, BYTEORDER; + + public static Partitioner getPartitioner(IPartitioner partitioner) { + return getPartitioner(partitioner.getClass().getSimpleName()); + } + + public static Partitioner getPartitioner(String className) { + if (className.endsWith("RandomPartitioner") || className.endsWith("Murmur3Partitioner")) + return Partitioner.RANDOM; + else if (className.endsWith("ByteOrderedPartitioner")) return Partitioner.BYTEORDER; + else throw new IllegalArgumentException("Unsupported partitioner: " + className); + } + } + + //################### CASSANDRA SPECIFIC CONFIGURATION OPTIONS ###################### + public static final ConfigNamespace CASSANDRA_NS = + new ConfigNamespace(GraphDatabaseConfiguration.STORAGE_NS, "cassandra", "Cassandra storage backend options"); + + public static final ConfigOption CASSANDRA_KEYSPACE = + new ConfigOption<>(CASSANDRA_NS, "keyspace", + "The name of JanusGraph's keyspace. It will be created if it does not exist. " + + "If it is not supplied, but graph.graphname is, then the the keyspace will be set to that.", + ConfigOption.Type.LOCAL, "janusgraph"); + + // Consistency Levels and Atomic Batch + public static final ConfigOption CASSANDRA_READ_CONSISTENCY = + new ConfigOption<>(CASSANDRA_NS, "read-consistency-level", + "The consistency level of read operations against Cassandra", + ConfigOption.Type.MASKABLE, "QUORUM"); + + public static final ConfigOption CASSANDRA_WRITE_CONSISTENCY = + new ConfigOption<>(CASSANDRA_NS, "write-consistency-level", + "The consistency level of write operations against Cassandra", + ConfigOption.Type.MASKABLE, "QUORUM"); + + public static final ConfigOption ATOMIC_BATCH_MUTATE = + new ConfigOption<>(CASSANDRA_NS, "atomic-batch-mutate", + "True to use Cassandra atomic batch mutation, false to use non-atomic batches", + ConfigOption.Type.MASKABLE, true); + + // Replication + public static final ConfigOption REPLICATION_FACTOR = + new ConfigOption<>(CASSANDRA_NS, "replication-factor", + "The number of data replicas (including the original copy) that should be kept. " + + "This is only meaningful for storage backends that natively support data replication.", + ConfigOption.Type.GLOBAL_OFFLINE, 1); + + public static final ConfigOption REPLICATION_STRATEGY = + new ConfigOption<>(CASSANDRA_NS, "replication-strategy-class", + "The replication strategy to use for JanusGraph keyspace", + ConfigOption.Type.FIXED, "org.apache.cassandra.locator.SimpleStrategy"); + + public static final ConfigOption REPLICATION_OPTIONS = + new ConfigOption<>(CASSANDRA_NS, "replication-strategy-options", + "Replication strategy options, e.g. factor or replicas per datacenter. This list is interpreted as a " + + "map. It must have an even number of elements in [key,val,key,val,...] form. A replication_factor set " + + "here takes precedence over one set with " + ConfigElement.getPath(REPLICATION_FACTOR), + ConfigOption.Type.FIXED, String[].class); + + public static final ConfigOption COMPACTION_STRATEGY = + new ConfigOption<>(CASSANDRA_NS, "compaction-strategy-class", + "The compaction strategy to use for JanusGraph tables", + ConfigOption.Type.FIXED, String.class); + + public static final ConfigOption COMPACTION_OPTIONS = + new ConfigOption<>(CASSANDRA_NS, "compaction-strategy-options", + "Compaction strategy options. This list is interpreted as a " + + "map. It must have an even number of elements in [key,val,key,val,...] form.", + ConfigOption.Type.FIXED, String[].class); + + // Compression + public static final ConfigOption CF_COMPRESSION = + new ConfigOption<>(CASSANDRA_NS, "compression", + "Whether the storage backend should use compression when storing the data", ConfigOption.Type.FIXED, true); + + public static final ConfigOption CF_COMPRESSION_TYPE = + new ConfigOption<>(CASSANDRA_NS, "compression-type", + "The sstable_compression value JanusGraph uses when creating column families. " + + "This accepts any value allowed by Cassandra's sstable_compression option. " + + "Leave this unset to disable sstable_compression on JanusGraph-created CFs.", + ConfigOption.Type.MASKABLE, "LZ4Compressor"); + + public static final ConfigOption CF_COMPRESSION_BLOCK_SIZE = + new ConfigOption<>(CASSANDRA_NS, "compression-block-size", + "The size of the compression blocks in kilobytes", ConfigOption.Type.FIXED, 64); + + // SSL + public static final ConfigNamespace SSL_NS = + new ConfigNamespace(CASSANDRA_NS, "ssl", "Configuration options for SSL"); + + public static final ConfigNamespace SSL_TRUSTSTORE_NS = + new ConfigNamespace(SSL_NS, "truststore", "Configuration options for SSL Truststore."); + + public static final ConfigOption SSL_ENABLED = + new ConfigOption<>(SSL_NS, "enabled", + "Controls use of the SSL connection to Cassandra", ConfigOption.Type.LOCAL, false); + + public static final ConfigOption SSL_TRUSTSTORE_LOCATION = + new ConfigOption<>(SSL_TRUSTSTORE_NS, "location", + "Marks the location of the SSL Truststore.", ConfigOption.Type.LOCAL, ""); + + public static final ConfigOption SSL_TRUSTSTORE_PASSWORD = + new ConfigOption<>(SSL_TRUSTSTORE_NS, "password", + "The password to access SSL Truststore.", ConfigOption.Type.LOCAL, ""); + + // Thrift transport + public static final ConfigOption THRIFT_FRAME_SIZE_MB = + new ConfigOption<>(CASSANDRA_NS, "frame-size-mb", + "The thrift frame size in megabytes", ConfigOption.Type.MASKABLE, 15, (p -> p != null && p > 0 && p < 2048)); + + /** + * The default Thrift port used by Cassandra. Set + * {@link GraphDatabaseConfiguration#STORAGE_PORT} to override. + *

+ * Value = {@value} + */ + public static final int PORT_DEFAULT = 9160; + + public static final String SYSTEM_KS = "system"; + + protected final String keySpaceName; + protected final Map strategyOptions; + protected final Map compactionOptions; + + protected final boolean compressionEnabled; + protected final int compressionChunkSizeKB; + protected final String compressionClass; + + protected final boolean atomicBatch; + + protected final int thriftFrameSizeBytes; + + private volatile StoreFeatures features = null; + private Partitioner partitioner = null; + + public AbstractCassandraStoreManager(Configuration config) { + super(config, PORT_DEFAULT); + + this.keySpaceName = determineKeyspaceName(config); + this.compressionEnabled = config.get(CF_COMPRESSION); + this.compressionChunkSizeKB = config.get(CF_COMPRESSION_BLOCK_SIZE); + this.compressionClass = config.get(CF_COMPRESSION_TYPE); + this.atomicBatch = config.get(ATOMIC_BATCH_MUTATE); + this.thriftFrameSizeBytes = config.get(THRIFT_FRAME_SIZE_MB) * 1024 * 1024; + + // SSL truststore location sanity check + if (config.get(SSL_ENABLED) && config.get(SSL_TRUSTSTORE_LOCATION).isEmpty()) + throw new IllegalArgumentException(SSL_TRUSTSTORE_LOCATION.getName() + " could not be empty when SSL is enabled."); + + if (config.has(REPLICATION_OPTIONS)) { + this.strategyOptions = convertOptionsToMap(REPLICATION_OPTIONS, config); + } else { + this.strategyOptions = ImmutableMap.of("replication_factor", String.valueOf(config.get(REPLICATION_FACTOR))); + } + + if (config.has(COMPACTION_OPTIONS)) { + this.compactionOptions = convertOptionsToMap(COMPACTION_OPTIONS, config); + } else { + this.compactionOptions = ImmutableMap.of(); + } + } + + public final Partitioner getPartitioner() { + if (partitioner == null) { + try { + partitioner = Partitioner.getPartitioner(getCassandraPartitioner()); + } catch (BackendException e) { + throw new JanusGraphException("Could not connect to Cassandra to read partitioner information. Please check the connection", e); + } + } + assert partitioner != null; + return partitioner; + } + + public abstract IPartitioner getCassandraPartitioner() throws BackendException; + + @Override + public StoreTransaction beginTransaction(final BaseTransactionConfig config) { + return new CassandraTransaction(config); + } + + @Override + public String toString() { + return "[" + keySpaceName + "@" + super.toString() + "]"; + } + + @Override + public StoreFeatures getFeatures() { + + if (features == null) { + + Configuration global = GraphDatabaseConfiguration.buildGraphConfiguration() + .set(CASSANDRA_READ_CONSISTENCY, "QUORUM") + .set(CASSANDRA_WRITE_CONSISTENCY, "QUORUM") + .set(METRICS_PREFIX, GraphDatabaseConfiguration.METRICS_SYSTEM_PREFIX_DEFAULT); + + Configuration local = GraphDatabaseConfiguration.buildGraphConfiguration() + .set(CASSANDRA_READ_CONSISTENCY, "LOCAL_QUORUM") + .set(CASSANDRA_WRITE_CONSISTENCY, "LOCAL_QUORUM") + .set(METRICS_PREFIX, GraphDatabaseConfiguration.METRICS_SYSTEM_PREFIX_DEFAULT); + + StandardStoreFeatures.Builder fb = new StandardStoreFeatures.Builder(); + + fb.batchMutation(true).distributed(true); + fb.timestamps(true).cellTTL(true); + fb.keyConsistent(global, local); + fb.optimisticLocking(true); + + boolean keyOrdered; + + switch (getPartitioner()) { + case RANDOM: + keyOrdered = false; + fb.keyOrdered(keyOrdered).orderedScan(false).unorderedScan(true); + break; + + case BYTEORDER: + keyOrdered = true; + fb.keyOrdered(keyOrdered).orderedScan(true).unorderedScan(false); + break; + + default: + throw new IllegalArgumentException("Unrecognized partitioner: " + getPartitioner()); + } + + switch (getDeployment()) { + case REMOTE: + fb.multiQuery(true); + break; + + case LOCAL: + fb.multiQuery(true).localKeyPartition(keyOrdered); + break; + + case EMBEDDED: + fb.multiQuery(false).localKeyPartition(keyOrdered); + break; + + default: + throw new IllegalArgumentException("Unrecognized deployment mode: " + getDeployment()); + } + + features = fb.build(); + } + + return features; + } + + /** + * Returns a map of compression options for the column family {@code cf}. + * The contents of the returned map must be identical to the contents of the + * map returned by + * {@link org.apache.cassandra.thrift.CfDef#getCompression_options()}, even + * for implementations of this method that don't use Thrift. + * + * @param cf the name of the column family for which to return compression + * options + * @return map of compression option names to compression option values + * @throws org.janusgraph.diskstorage.BackendException if reading from Cassandra fails + */ + public abstract Map getCompressionOptions(String cf) throws BackendException; + + public String getName() { + return getClass().getSimpleName() + keySpaceName; + } + + // Opened up for testing purposes + protected String determineKeyspaceName(Configuration config) { + if ((!config.has(CASSANDRA_KEYSPACE) && (config.has(GRAPH_NAME)))) return config.get(GRAPH_NAME); + return config.get(CASSANDRA_KEYSPACE); + } + + private Map convertOptionsToMap(ConfigOption optionsKey, Configuration config){ + + String[] options = config.get(optionsKey); + + if (options.length % 2 != 0) + throw new IllegalArgumentException(optionsKey.getName() + " should have even number of elements."); + + final Map converted = new HashMap<>(options.length / 2); + + for (int i = 0; i < options.length; i += 2) { + converted.put(options[i], options[i + 1]); + } + + return ImmutableMap.copyOf(converted); + } + + @Override + public Object getHadoopManager() throws BackendException { + return new CassandraHadoopStoreManager(this.getCassandraPartitioner()); + } +} \ No newline at end of file diff --git a/src/main/java/org/janusgraph/diskstorage/cassandra/CLevel.java b/src/main/java/org/janusgraph/diskstorage/cassandra/CLevel.java new file mode 100644 index 0000000..e93c3b7 --- /dev/null +++ b/src/main/java/org/janusgraph/diskstorage/cassandra/CLevel.java @@ -0,0 +1,85 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra; + +import com.google.common.base.Preconditions; + +import java.util.Arrays; +import java.util.Optional; + +/** + * This enum unites different libraries' consistency level enums, streamlining + * configuration and processing in {@link AbstractCassandraStoreManager}. + * + */ +public enum CLevel implements CLevelInterface { // One ring to rule them all + ANY, + ONE, + TWO, + THREE, + QUORUM, + ALL, + LOCAL_ONE, + LOCAL_QUORUM, + EACH_QUORUM; + + private final org.apache.cassandra.db.ConsistencyLevel db; + private final org.apache.cassandra.thrift.ConsistencyLevel thrift; + private final com.netflix.astyanax.model.ConsistencyLevel astyanax; + + CLevel() { + db = org.apache.cassandra.db.ConsistencyLevel.valueOf(toString()); + thrift = org.apache.cassandra.thrift.ConsistencyLevel.valueOf(toString()); + astyanax = com.netflix.astyanax.model.ConsistencyLevel.valueOf("CL_" + toString()); + } + + @Override + public org.apache.cassandra.db.ConsistencyLevel getDB() { + return db; + } + + @Override + public org.apache.cassandra.thrift.ConsistencyLevel getThrift() { + return thrift; + } + + @Override + public com.netflix.astyanax.model.ConsistencyLevel getAstyanax() { + return astyanax; + } + + public static CLevel parse(final String value) { + Preconditions.checkArgument(value != null && !value.isEmpty()); + final String trimmed = value.trim(); + switch (trimmed) { + case "1": + return ONE; + case "2": + return TWO; + case "3": + return THREE; + default: + final Optional level = Arrays.stream(values()) + .filter(c -> c.toString().equalsIgnoreCase(trimmed) + || ("CL_" + c.toString()).equalsIgnoreCase(trimmed)) + .findFirst(); + if (level.isPresent()) { + return level.get(); + } else { + throw new IllegalArgumentException("Unrecognized cassandra consistency level: " + value); + } + } + } +} diff --git a/src/main/java/org/janusgraph/diskstorage/cassandra/CLevelInterface.java b/src/main/java/org/janusgraph/diskstorage/cassandra/CLevelInterface.java new file mode 100644 index 0000000..dfa6c29 --- /dev/null +++ b/src/main/java/org/janusgraph/diskstorage/cassandra/CLevelInterface.java @@ -0,0 +1,24 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra; + +public interface CLevelInterface { + + org.apache.cassandra.db.ConsistencyLevel getDB(); + + org.apache.cassandra.thrift.ConsistencyLevel getThrift(); + + com.netflix.astyanax.model.ConsistencyLevel getAstyanax(); +} diff --git a/src/main/java/org/janusgraph/diskstorage/cassandra/CassandraTransaction.java b/src/main/java/org/janusgraph/diskstorage/cassandra/CassandraTransaction.java new file mode 100644 index 0000000..3ebc978 --- /dev/null +++ b/src/main/java/org/janusgraph/diskstorage/cassandra/CassandraTransaction.java @@ -0,0 +1,59 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra; + +import static org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreManager.CASSANDRA_READ_CONSISTENCY; +import static org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreManager.CASSANDRA_WRITE_CONSISTENCY; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; +import org.janusgraph.diskstorage.BaseTransactionConfig; +import org.janusgraph.diskstorage.common.AbstractStoreTransaction; +import org.janusgraph.diskstorage.keycolumnvalue.StoreTransaction; + +public class CassandraTransaction extends AbstractStoreTransaction { + + private static final Logger log = LoggerFactory.getLogger(CassandraTransaction.class); + + private final CLevel read; + private final CLevel write; + + public CassandraTransaction(BaseTransactionConfig c) { + super(c); + read = CLevel.parse(getConfiguration().getCustomOption(CASSANDRA_READ_CONSISTENCY)); + write = CLevel.parse(getConfiguration().getCustomOption(CASSANDRA_WRITE_CONSISTENCY)); + log.debug("Created {}", this.toString()); + } + + public CLevel getReadConsistencyLevel() { + return read; + } + + public CLevel getWriteConsistencyLevel() { + return write; + } + + public static CassandraTransaction getTx(StoreTransaction txh) { + Preconditions.checkArgument(txh != null); + Preconditions.checkArgument(txh instanceof CassandraTransaction, "Unexpected transaction type %s", txh.getClass().getName()); + return (CassandraTransaction) txh; + } + + public String toString() { + return "CassandraTransaction@" + Integer.toHexString(hashCode()) + "[read=" + read + ",write=" + write + "]"; + } +} diff --git a/src/main/java/org/janusgraph/diskstorage/cassandra/astyanax/AstyanaxKeyColumnValueStore.java b/src/main/java/org/janusgraph/diskstorage/cassandra/astyanax/AstyanaxKeyColumnValueStore.java new file mode 100644 index 0000000..5f6fc88 --- /dev/null +++ b/src/main/java/org/janusgraph/diskstorage/cassandra/astyanax/AstyanaxKeyColumnValueStore.java @@ -0,0 +1,464 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.astyanax; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterators; +import com.google.common.collect.Iterables; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.netflix.astyanax.ExceptionCallback; +import com.netflix.astyanax.Keyspace; +import com.netflix.astyanax.connectionpool.OperationResult; +import com.netflix.astyanax.connectionpool.exceptions.ConnectionException; +import com.netflix.astyanax.model.Column; +import com.netflix.astyanax.model.ColumnFamily; +import com.netflix.astyanax.model.ColumnList; +import com.netflix.astyanax.model.Rows; +import com.netflix.astyanax.model.Row; +import com.netflix.astyanax.query.AllRowsQuery; +import com.netflix.astyanax.query.RowSliceQuery; +import com.netflix.astyanax.retry.RetryPolicy; +import com.netflix.astyanax.serializers.ByteBufferSerializer; +import com.netflix.astyanax.query.RowQuery; +import org.janusgraph.diskstorage.BackendException; +import org.janusgraph.diskstorage.PermanentBackendException; +import org.janusgraph.diskstorage.TemporaryBackendException; +import org.janusgraph.diskstorage.StaticBuffer; +import org.janusgraph.diskstorage.Entry; +import org.janusgraph.diskstorage.EntryList; +import org.janusgraph.diskstorage.EntryMetaData; +import org.janusgraph.diskstorage.cassandra.utils.CassandraHelper; +import org.janusgraph.diskstorage.keycolumnvalue.KeySliceQuery; +import org.janusgraph.diskstorage.keycolumnvalue.StoreTransaction; +import org.janusgraph.diskstorage.keycolumnvalue.SliceQuery; +import org.janusgraph.diskstorage.keycolumnvalue.KeyIterator; +import org.janusgraph.diskstorage.keycolumnvalue.KeyRangeQuery; +import org.janusgraph.diskstorage.keycolumnvalue.KeyColumnValueStore; +import org.janusgraph.diskstorage.keycolumnvalue.KCVMutation; +import org.janusgraph.diskstorage.util.RecordIterator; +import org.janusgraph.diskstorage.util.StaticArrayBuffer; +import org.janusgraph.diskstorage.util.StaticArrayEntry; +import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalInterruptedException; + +import javax.annotation.Nullable; +import java.nio.ByteBuffer; +import java.util.*; + +import static org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreManager.Partitioner; +import static org.janusgraph.diskstorage.cassandra.CassandraTransaction.getTx; + +public class AstyanaxKeyColumnValueStore implements KeyColumnValueStore { + + private final Keyspace keyspace; + private final String columnFamilyName; + private final ColumnFamily columnFamily; + private final RetryPolicy retryPolicy; + private final AstyanaxStoreManager storeManager; + private final int readPageSize; + private final AstyanaxGetter entryGetter; + + AstyanaxKeyColumnValueStore(String columnFamilyName, + Keyspace keyspace, + AstyanaxStoreManager storeManager, + RetryPolicy retryPolicy) { + this.keyspace = keyspace; + this.columnFamilyName = columnFamilyName; + this.retryPolicy = retryPolicy; + this.storeManager = storeManager; + this.readPageSize = storeManager.readPageSize; + + this.entryGetter = new AstyanaxGetter(storeManager.getMetaDataSchema(columnFamilyName)); + + this.columnFamily = new ColumnFamily<>(this.columnFamilyName, ByteBufferSerializer.get(), + ByteBufferSerializer.get()); + + } + + + ColumnFamily getColumnFamily() { + return columnFamily; + } + + @Override + public void close() throws BackendException { + //Do nothing + } + + @Override + public EntryList getSlice(KeySliceQuery query, StoreTransaction txh) throws BackendException { + Map result = getNamesSlice(query.getKey(), query, txh); + return Iterables.getOnlyElement(result.values(),EntryList.EMPTY_LIST); + } + + @Override + public Map getSlice(List keys, SliceQuery query, StoreTransaction txh) throws BackendException { + return getNamesSlice(keys, query, txh); + } + + public Map getNamesSlice(StaticBuffer key, + SliceQuery query, StoreTransaction txh) throws BackendException { + return getNamesSlice(ImmutableList.of(key),query,txh); + } + + + public Map getNamesSlice(List keys, + SliceQuery query, StoreTransaction txh) throws BackendException { + /* + * RowQuery should be parameterized as + * RowQuery. However, this causes the following + * compilation error when attempting to call withColumnRange on a + * RowQuery instance: + * + * java.lang.Error: Unresolved compilation problem: The method + * withColumnRange(ByteBuffer, ByteBuffer, boolean, int) is ambiguous + * for the type RowQuery + * + * The compiler substitutes ByteBuffer=C for both startColumn and + * endColumn, compares it to its identical twin with that type + * hard-coded, and dies. + * + */ + + // Add one for last column potentially removed in CassandraHelper.makeEntryList + final int queryLimit = query.getLimit() + (query.hasLimit() ? 1 : 0); + final int pageLimit = Math.min(this.readPageSize, queryLimit); + + ByteBuffer sliceStart = query.getSliceStart().asByteBuffer(); + final ByteBuffer sliceEnd = query.getSliceEnd().asByteBuffer(); + + final RowSliceQuery rq = keyspace.prepareQuery(columnFamily) + .setConsistencyLevel(getTx(txh).getReadConsistencyLevel().getAstyanax()) + .withRetryPolicy(retryPolicy.duplicate()) + .getKeySlice(CassandraHelper.convert(keys)); + + // Don't directly chain due to ambiguity resolution; see top comment + rq.withColumnRange( + sliceStart, + sliceEnd, + false, + pageLimit + ); + + final OperationResult> r; + try { + r = (OperationResult>) rq.execute(); + } catch (ConnectionException e) { + throw new TemporaryBackendException(e); + } + + final Rows rows = r.getResult(); + final Map result = new HashMap<>(rows.size()); + + for (Row row : rows) { + assert !result.containsKey(row.getKey()); + final ByteBuffer key = row.getKey(); + ColumnList pageColumns = row.getColumns(); + final List> queryColumns = new ArrayList(); + Iterables.addAll(queryColumns, pageColumns); + while (pageColumns.size() == pageLimit && queryColumns.size() < queryLimit) { + final Column lastColumn = queryColumns.get(queryColumns.size() - 1); + sliceStart = lastColumn.getName(); + // No possibility of two values at the same column name, so start the + // next slice one bit after the last column found by the previous query. + // byte[] is little-endian + Integer position = null; + for (int i = sliceStart.array().length - 1; i >= 0; i--) { + if (sliceStart.array()[i] < Byte.MAX_VALUE) { + position = i; + sliceStart.array()[i]++; + break; + } + } + + if (null == position) { + throw new PermanentBackendException("Column was not incrementable"); + } + + final RowQuery pageQuery = keyspace.prepareQuery(columnFamily) + .setConsistencyLevel(getTx(txh).getReadConsistencyLevel().getAstyanax()) + .withRetryPolicy(retryPolicy.duplicate()).getKey(row.getKey()); + + // Don't directly chain due to ambiguity resolution; see top comment + pageQuery.withColumnRange( + sliceStart, + sliceEnd, + false, + pageLimit + ); + + final OperationResult> pageResult; + try { + pageResult = (OperationResult>) pageQuery.execute(); + } catch (ConnectionException e) { + throw new TemporaryBackendException(e); + } + + if (Thread.interrupted()) { + throw new TraversalInterruptedException(); + } + + // Reset the incremented position to avoid leaking mutations up the + // stack to callers - sliceStart.array() in fact refers to a column name + // that will be later read to deserialize an edge (since we assigned it + // via de-referencing a column from the previous query). + sliceStart.array()[position]--; + + pageColumns = pageResult.getResult(); + Iterables.addAll(queryColumns, pageColumns); + } + result.put( + StaticArrayBuffer.of(key), + CassandraHelper.makeEntryList(queryColumns, + entryGetter, + query.getSliceEnd(), + query.getLimit() + ) + ); + } + + return result; + } + + private static class AstyanaxGetter implements StaticArrayEntry.GetColVal,ByteBuffer> { + + private final EntryMetaData[] schema; + + private AstyanaxGetter(EntryMetaData[] schema) { + this.schema = schema; + } + + + @Override + public ByteBuffer getColumn(Column element) { + return element.getName(); + } + + @Override + public ByteBuffer getValue(Column element) { + return element.getByteBufferValue(); + } + + @Override + public EntryMetaData[] getMetaSchema(Column element) { + return schema; + } + + @Override + public Object getMetaData(Column element, EntryMetaData meta) { + switch(meta) { + case TIMESTAMP: + return element.getTimestamp(); + case TTL: + return element.getTtl(); + default: + throw new UnsupportedOperationException("Unsupported meta data: " + meta); + } + } + } + + @Override + public void mutate(StaticBuffer key, List additions, List deletions, StoreTransaction txh) throws BackendException { + mutateMany(ImmutableMap.of(key, new KCVMutation(additions, deletions)), txh); + } + + public void mutateMany(Map mutations, StoreTransaction txh) throws BackendException { + storeManager.mutateMany(ImmutableMap.of(columnFamilyName, mutations), txh); + } + + @Override + public void acquireLock(StaticBuffer key, StaticBuffer column, StaticBuffer expectedValue, StoreTransaction txh) throws BackendException { + throw new UnsupportedOperationException(); + } + + @Override + public KeyIterator getKeys(@Nullable SliceQuery sliceQuery, StoreTransaction txh) throws BackendException { + if (storeManager.getPartitioner() != Partitioner.RANDOM) + throw new PermanentBackendException("This operation is only allowed when random partitioner (md5 or murmur3) is used."); + + AllRowsQuery allRowsQuery = keyspace.prepareQuery(columnFamily).getAllRows(); + + if (sliceQuery != null) { + allRowsQuery.withColumnRange(sliceQuery.getSliceStart().asByteBuffer(), + sliceQuery.getSliceEnd().asByteBuffer(), + false, + sliceQuery.getLimit()); + } + + Rows result; + try { + /* Note: we need to fetch columns for each row as well to remove "range ghosts" */ + OperationResult op = allRowsQuery.setRowLimit(storeManager.getPageSize()) // pre-fetch that many rows at a time + .setConcurrencyLevel(1) // one execution thread for fetching portion of rows + .setExceptionCallback(new ExceptionCallback() { + private int retries = 0; + + @Override + public boolean onException(ConnectionException e) { + try { + return retries > 2; // make 3 re-tries + } finally { + retries++; + } + } + }).execute(); + + result = ((OperationResult>) op).getResult(); + } catch (ConnectionException e) { + throw new PermanentBackendException(e); + } + + return new RowIterator(result.iterator(), sliceQuery); + } + + @Override + public KeyIterator getKeys(KeyRangeQuery query, StoreTransaction txh) throws BackendException { + // this query could only be done when byte-ordering partitioner is used + // because Cassandra operates on tokens internally which means that even contiguous + // range of keys (e.g. time slice) with random partitioner could produce disjoint set of tokens + // returning ambiguous results to the user. + Partitioner partitioner = storeManager.getPartitioner(); + if (partitioner != Partitioner.BYTEORDER) + throw new PermanentBackendException("getKeys(KeyRangeQuery could only be used with byte-ordering partitioner."); + + ByteBuffer start = query.getKeyStart().asByteBuffer(), end = query.getKeyEnd().asByteBuffer(); + + RowSliceQuery rowSlice = keyspace.prepareQuery(columnFamily) + .setConsistencyLevel(getTx(txh).getReadConsistencyLevel().getAstyanax()) + .withRetryPolicy(retryPolicy.duplicate()) + .getKeyRange(start, end, null, null, Integer.MAX_VALUE); + + // Astyanax is bad at builder pattern :( + rowSlice.withColumnRange(query.getSliceStart().asByteBuffer(), + query.getSliceEnd().asByteBuffer(), + false, + query.getLimit()); + + // Omit final the query's key end from the result, if present in result + final Rows r; + try { + r = ((OperationResult>) rowSlice.execute()).getResult(); + } catch (ConnectionException e) { + throw new TemporaryBackendException(e); + } + Iterator> i = + Iterators.filter(r.iterator(), new KeySkipPredicate(query.getKeyEnd().asByteBuffer())); + return new RowIterator(i, query); + } + + @Override + public String getName() { + return columnFamilyName; + } + + private static class KeyIterationPredicate implements Predicate> { + @Override + public boolean apply(@Nullable Row row) { + return (row != null) && row.getColumns().size() > 0; + } + } + + private static class KeySkipPredicate implements Predicate> { + + private final ByteBuffer skip; + + public KeySkipPredicate(ByteBuffer skip) { + this.skip = skip; + } + + @Override + public boolean apply(@Nullable Row row) { + return (row != null) && !row.getKey().equals(skip); + } + } + + private class RowIterator implements KeyIterator { + private final Iterator> rows; + private Row currentRow; + private final SliceQuery sliceQuery; + private boolean isClosed; + + public RowIterator(Iterator> rowIterator, SliceQuery sliceQuery) { + this.rows = Iterators.filter(rowIterator, new KeyIterationPredicate()); + this.sliceQuery = sliceQuery; + } + + @Override + public RecordIterator getEntries() { + ensureOpen(); + + if (sliceQuery == null) + throw new IllegalStateException("getEntries() requires SliceQuery to be set."); + + return new RecordIterator() { + private final Iterator columns = + CassandraHelper.makeEntryIterator(currentRow.getColumns(), + entryGetter, + sliceQuery.getSliceEnd(),sliceQuery.getLimit()); + + @Override + public boolean hasNext() { + ensureOpen(); + return columns.hasNext(); + } + + @Override + public Entry next() { + ensureOpen(); + return columns.next(); + } + + @Override + public void close() { + isClosed = true; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + public boolean hasNext() { + ensureOpen(); + return rows.hasNext(); + } + + @Override + public StaticBuffer next() { + ensureOpen(); + + currentRow = rows.next(); + return StaticArrayBuffer.of(currentRow.getKey()); + } + + @Override + public void close() { + isClosed = true; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + private void ensureOpen() { + if (isClosed) + throw new IllegalStateException("Iterator has been closed."); + } + } +} diff --git a/src/main/java/org/janusgraph/diskstorage/cassandra/astyanax/AstyanaxStoreManager.java b/src/main/java/org/janusgraph/diskstorage/cassandra/astyanax/AstyanaxStoreManager.java new file mode 100644 index 0000000..141cfb6 --- /dev/null +++ b/src/main/java/org/janusgraph/diskstorage/cassandra/astyanax/AstyanaxStoreManager.java @@ -0,0 +1,708 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.astyanax; + +import com.google.common.base.Preconditions; +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableMap; +import com.netflix.astyanax.AstyanaxContext; +import com.netflix.astyanax.Cluster; +import com.netflix.astyanax.ColumnListMutation; +import com.netflix.astyanax.Keyspace; +import com.netflix.astyanax.MutationBatch; +import com.netflix.astyanax.connectionpool.Host; +import com.netflix.astyanax.connectionpool.NodeDiscoveryType; +import com.netflix.astyanax.connectionpool.RetryBackoffStrategy; +import com.netflix.astyanax.connectionpool.SSLConnectionContext; +import com.netflix.astyanax.connectionpool.exceptions.ConnectionException; +import com.netflix.astyanax.connectionpool.impl.ConnectionPoolConfigurationImpl; +import com.netflix.astyanax.connectionpool.impl.ConnectionPoolType; +import com.netflix.astyanax.connectionpool.impl.CountingConnectionPoolMonitor; +import com.netflix.astyanax.connectionpool.impl.ExponentialRetryBackoffStrategy; +import com.netflix.astyanax.connectionpool.impl.SimpleAuthenticationCredentials; +import com.netflix.astyanax.ddl.ColumnFamilyDefinition; +import com.netflix.astyanax.ddl.KeyspaceDefinition; +import com.netflix.astyanax.impl.AstyanaxConfigurationImpl; +import com.netflix.astyanax.model.ColumnFamily; +import com.netflix.astyanax.retry.RetryPolicy; +import com.netflix.astyanax.thrift.ThriftFamilyFactory; +import org.janusgraph.diskstorage.BackendException; +import org.janusgraph.diskstorage.Entry; +import org.janusgraph.diskstorage.EntryMetaData; +import org.janusgraph.diskstorage.PermanentBackendException; +import org.janusgraph.diskstorage.StaticBuffer; +import org.janusgraph.diskstorage.StoreMetaData; +import org.janusgraph.diskstorage.TemporaryBackendException; +import org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreManager; +import org.janusgraph.diskstorage.configuration.ConfigNamespace; +import org.janusgraph.diskstorage.configuration.ConfigOption; +import org.janusgraph.diskstorage.configuration.Configuration; +import org.janusgraph.diskstorage.keycolumnvalue.KCVMutation; +import org.janusgraph.diskstorage.keycolumnvalue.KeyRange; +import org.janusgraph.diskstorage.keycolumnvalue.StoreTransaction; +import org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration; +import org.janusgraph.graphdb.configuration.PreInitializeConfigOptions; +import org.apache.cassandra.dht.IPartitioner; +import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.utils.FBUtilities; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Constructor; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import static org.janusgraph.diskstorage.cassandra.CassandraTransaction.getTx; + +@PreInitializeConfigOptions +public class AstyanaxStoreManager extends AbstractCassandraStoreManager { + + private static final Logger log = LoggerFactory.getLogger(AstyanaxStoreManager.class); + + //################### ASTYANAX SPECIFIC CONFIGURATION OPTIONS ###################### + + public static final ConfigNamespace ASTYANAX_NS = + new ConfigNamespace(CASSANDRA_NS, "astyanax", "Astyanax-specific Cassandra options"); + + /** + * Default name for the Cassandra cluster + */ + public static final ConfigOption CLUSTER_NAME = + new ConfigOption<>(ASTYANAX_NS, "cluster-name", + "Default name for the Cassandra cluster", + ConfigOption.Type.MASKABLE, "JanusGraph Cluster"); + + /** + * Maximum pooled connections per host. + */ + public static final ConfigOption MAX_CONNECTIONS_PER_HOST = + new ConfigOption<>(ASTYANAX_NS, "max-connections-per-host", + "Maximum pooled connections per host", + ConfigOption.Type.MASKABLE, 32); + + /** + * Maximum open connections allowed in the pool (counting all hosts). + */ + public static final ConfigOption MAX_CONNECTIONS = + new ConfigOption<>(ASTYANAX_NS, "max-connections", + "Maximum open connections allowed in the pool (counting all hosts)", + ConfigOption.Type.MASKABLE, -1); + + /** + * Maximum number of operations allowed per connection before the connection is closed. + */ + public static final ConfigOption MAX_OPERATIONS_PER_CONNECTION = + new ConfigOption<>(ASTYANAX_NS, "max-operations-per-connection", + "Maximum number of operations allowed per connection before the connection is closed", + ConfigOption.Type.MASKABLE, 100 * 1000); + + /** + * Maximum pooled "cluster" connections per host. + *

+ * These connections are mostly idle and only used for DDL operations + * (like creating keyspaces). JanusGraph doesn't need many of these connections + * in ordinary operation. + */ + public static final ConfigOption MAX_CLUSTER_CONNECTIONS_PER_HOST = + new ConfigOption<>(ASTYANAX_NS, "max-cluster-connections-per-host", + "Maximum pooled \"cluster\" connections per host", + ConfigOption.Type.MASKABLE, 3); + + /** + * The page size for Cassandra read operations. + */ + public static final ConfigOption READ_PAGE_SIZE = + new ConfigOption<>(ASTYANAX_NS, "read-page-size", + "The page size for Cassandra read operations", + ConfigOption.Type.MASKABLE, Integer.class, 4096); + + /** + * How Astyanax discovers Cassandra cluster nodes. This must be one of the + * values of the Astyanax NodeDiscoveryType enum. + */ + public static final ConfigOption NODE_DISCOVERY_TYPE = + new ConfigOption<>(ASTYANAX_NS, "node-discovery-type", + "How Astyanax discovers Cassandra cluster nodes", + ConfigOption.Type.MASKABLE, "RING_DESCRIBE"); + + /** + * Astyanax specific host supplier useful only when discovery type set to DISCOVERY_SERVICE or TOKEN_AWARE. + * Excepts fully qualified class name which extends google.common.base.Supplier<List<Host>>. + */ + public static final ConfigOption HOST_SUPPLIER = + new ConfigOption<>(ASTYANAX_NS, "host-supplier", + "Host supplier to use when discovery type is set to DISCOVERY_SERVICE or TOKEN_AWARE", + ConfigOption.Type.MASKABLE, String.class); + + /** + * Astyanax's connection pooler implementation. This must be one of the + * values of the Astyanax ConnectionPoolType enum. + */ + public static final ConfigOption CONNECTION_POOL_TYPE = + new ConfigOption<>(ASTYANAX_NS, "connection-pool-type", + "Astyanax's connection pooler implementation", + ConfigOption.Type.MASKABLE, "TOKEN_AWARE"); + + /** + * In Astyanax, RetryPolicy and RetryBackoffStrategy sound and look similar + * but are used for distinct purposes. RetryPolicy is for retrying failed + * operations. RetryBackoffStrategy is for retrying attempts to talk to + * uncommunicative hosts. This config option controls RetryPolicy. + */ + public static final ConfigOption RETRY_POLICY = + new ConfigOption<>(ASTYANAX_NS, "retry-policy", + "Astyanax's retry policy implementation with configuration parameters", + ConfigOption.Type.MASKABLE, "com.netflix.astyanax.retry.BoundedExponentialBackoff,100,25000,8"); + + /** + * If non-null, this must be the fully-qualified classname (i.e. the + * complete package name, a dot, and then the class name) of an + * implementation of Astyanax's RetryBackoffStrategy interface. This string + * may be followed by a sequence of integers, separated from the full + * classname and from each other by commas; in this case, the integers are + * cast to native Java ints and passed to the class constructor as + * arguments. Here's an example setting that would instantiate an Astyanax + * FixedRetryBackoffStrategy with an delay interval of 1s and suspend time + * of 5s: + *

+ * + * com.netflix.astyanax.connectionpool.impl.FixedRetryBackoffStrategy,1000,5000 + * + *

+ * If null, then Astyanax uses its default strategy, which is an + * ExponentialRetryBackoffStrategy instance. The instance parameters take + * Astyanax's built-in default values, which can be overridden via the + * following config keys: + *

    + *
  • {@link #RETRY_DELAY_SLICE}
  • + *
  • {@link #RETRY_MAX_DELAY_SLICE}
  • + *
  • {@link #RETRY_SUSPEND_WINDOW}
  • + *
+ *

+ * In Astyanax, RetryPolicy and RetryBackoffStrategy sound and look similar + * but are used for distinct purposes. RetryPolicy is for retrying failed + * operations. RetryBackoffStrategy is for retrying attempts to talk to + * uncommunicative hosts. This config option controls RetryBackoffStrategy. + */ + public static final ConfigOption RETRY_BACKOFF_STRATEGY = + new ConfigOption<>(ASTYANAX_NS, "retry-backoff-strategy", + "Astyanax's retry backoff strategy with configuration parameters", + ConfigOption.Type.MASKABLE, "com.netflix.astyanax.connectionpool.impl.FixedRetryBackoffStrategy,1000,5000"); + + /** + * Controls the retryDelaySlice parameter on Astyanax + * ConnectionPoolConfigurationImpl objects, which is in turn used by + * ExponentialRetryBackoffStrategy. See the code for + * {@link ConnectionPoolConfigurationImpl}, + * {@link ExponentialRetryBackoffStrategy}, and the javadoc for + * {@link #RETRY_BACKOFF_STRATEGY} for more information. + *

+ * This parameter is not meaningful for and has no effect on + * FixedRetryBackoffStrategy. + */ + public static final ConfigOption RETRY_DELAY_SLICE = + new ConfigOption<>(ASTYANAX_NS, "retry-delay-slice", + "Astyanax's connection pool \"retryDelaySlice\" parameter", + ConfigOption.Type.MASKABLE, ConnectionPoolConfigurationImpl.DEFAULT_RETRY_DELAY_SLICE); + /** + * Controls the retryMaxDelaySlice parameter on Astyanax + * ConnectionPoolConfigurationImpl objects, which is in turn used by + * ExponentialRetryBackoffStrategy. See the code for + * {@link ConnectionPoolConfigurationImpl}, + * {@link ExponentialRetryBackoffStrategy}, and the javadoc for + * {@link #RETRY_BACKOFF_STRATEGY} for more information. + *

+ * This parameter is not meaningful for and has no effect on + * FixedRetryBackoffStrategy. + */ + public static final ConfigOption RETRY_MAX_DELAY_SLICE = + new ConfigOption<>(ASTYANAX_NS, "retry-max-delay-slice", + "Astyanax's connection pool \"retryMaxDelaySlice\" parameter", + ConfigOption.Type.MASKABLE, ConnectionPoolConfigurationImpl.DEFAULT_RETRY_MAX_DELAY_SLICE); + + /** + * Controls the retrySuspendWindow parameter on Astyanax + * ConnectionPoolConfigurationImpl objects, which is in turn used by + * ExponentialRetryBackoffStrategy. See the code for + * {@link ConnectionPoolConfigurationImpl}, + * {@link ExponentialRetryBackoffStrategy}, and the javadoc for + * {@link #RETRY_BACKOFF_STRATEGY} for more information. + *

+ * This parameter is not meaningful for and has no effect on + * FixedRetryBackoffStrategy. + */ + public static final ConfigOption RETRY_SUSPEND_WINDOW = + new ConfigOption<>(ASTYANAX_NS, "retry-suspend-window", + "Astyanax's connection pool \"retryMaxDelaySlice\" parameter", + ConfigOption.Type.MASKABLE, ConnectionPoolConfigurationImpl.DEFAULT_RETRY_SUSPEND_WINDOW); + + /** + * Controls the frame size of thrift sockets created by Astyanax. + */ + public static final ConfigOption THRIFT_FRAME_SIZE = + new ConfigOption<>(ASTYANAX_NS, "frame-size", + "The thrift frame size in mega bytes", ConfigOption.Type.MASKABLE, 15); + + public static final ConfigOption LOCAL_DATACENTER = + new ConfigOption<>(ASTYANAX_NS, "local-datacenter", + "The name of the local or closest Cassandra datacenter. When set and not whitespace, " + + "this value will be passed into ConnectionPoolConfigurationImpl.setLocalDatacenter. " + + "When unset or set to whitespace, setLocalDatacenter will not be invoked.", + /* It's between either LOCAL or MASKABLE. MASKABLE could be useful for cases where + all the JanusGraph instances are closest to the same Cassandra DC. */ + ConfigOption.Type.MASKABLE, String.class); + + private final String clusterName; + + private final AstyanaxContext keyspaceContext; + private final AstyanaxContext clusterContext; + + private final RetryPolicy retryPolicy; + + final int readPageSize; + private final int retryDelaySlice; + private final int retryMaxDelaySlice; + private final int retrySuspendWindow; + private final RetryBackoffStrategy retryBackoffStrategy; + + private final String localDatacenter; + + private final Map openStores; + + public AstyanaxStoreManager(Configuration config) throws BackendException { + super(config); + log.warn("Astyanax is deprecated and will be removed with JanusGraph 0.5.0. Please switch to the CQL backend."); + + this.clusterName = config.get(CLUSTER_NAME); + + readPageSize = config.get(READ_PAGE_SIZE); + retryDelaySlice = config.get(RETRY_DELAY_SLICE); + retryMaxDelaySlice = config.get(RETRY_MAX_DELAY_SLICE); + retrySuspendWindow = config.get(RETRY_SUSPEND_WINDOW); + retryBackoffStrategy = getRetryBackoffStrategy(config.get(RETRY_BACKOFF_STRATEGY)); + retryPolicy = getRetryPolicy(config.get(RETRY_POLICY)); + + localDatacenter = config.has(LOCAL_DATACENTER) ? + config.get(LOCAL_DATACENTER) : ""; + + final int maxConnsPerHost = config.get(MAX_CONNECTIONS_PER_HOST); + + final int maxClusterConnsPerHost = config.get(MAX_CLUSTER_CONNECTIONS_PER_HOST); + + this.clusterContext = createCluster(getContextBuilder(config, maxClusterConnsPerHost, "Cluster")); + + ensureKeyspaceExists(clusterContext.getClient()); + + this.keyspaceContext = getContextBuilder(config, maxConnsPerHost, "Keyspace").buildKeyspace(ThriftFamilyFactory.getInstance()); + this.keyspaceContext.start(); + + openStores = new HashMap<>(8); + } + + @Override + public Deployment getDeployment() { + return Deployment.REMOTE; // TODO + } + + @Override + public IPartitioner getCassandraPartitioner() throws BackendException { + Cluster cl = clusterContext.getClient(); + try { + return FBUtilities.newPartitioner(cl.describePartitioner()); + } catch (ConnectionException e) { + throw new TemporaryBackendException(e); + } catch (ConfigurationException e) { + throw new PermanentBackendException(e); + } + } + + @Override + public String toString() { + return "astyanax" + super.toString(); + } + + @Override + public void close() { + // Shutdown the Astyanax contexts + openStores.clear(); + keyspaceContext.shutdown(); + clusterContext.shutdown(); + } + + @Override + public synchronized AstyanaxKeyColumnValueStore openDatabase(String name, StoreMetaData.Container metaData) throws BackendException { + if (openStores.containsKey(name)) return openStores.get(name); + else { + ensureColumnFamilyExists(name); + AstyanaxKeyColumnValueStore store = new AstyanaxKeyColumnValueStore(name, keyspaceContext.getClient(), this, retryPolicy); + openStores.put(name, store); + return store; + } + } + + @Override + public void mutateMany(Map> batch, StoreTransaction txh) throws BackendException { + MutationBatch m = keyspaceContext.getClient().prepareMutationBatch().withAtomicBatch(atomicBatch) + .setConsistencyLevel(getTx(txh).getWriteConsistencyLevel().getAstyanax()) + .withRetryPolicy(retryPolicy.duplicate()); + + final MaskedTimestamp commitTime = new MaskedTimestamp(txh); + + for (Map.Entry> batchentry : batch.entrySet()) { + String storeName = batchentry.getKey(); + Preconditions.checkArgument(openStores.containsKey(storeName), "Store cannot be found: " + storeName); + + ColumnFamily columnFamily = openStores.get(storeName).getColumnFamily(); + + Map mutations = batchentry.getValue(); + for (Map.Entry ent : mutations.entrySet()) { + // The CLMs for additions and deletions are separated because + // Astyanax's operation timestamp cannot be set on a per-delete + // or per-addition basis. + KCVMutation janusgraphMutation = ent.getValue(); + ByteBuffer key = ent.getKey().asByteBuffer(); + + if (janusgraphMutation.hasDeletions()) { + ColumnListMutation deletions = m.withRow(columnFamily, key); + deletions.setTimestamp(commitTime.getDeletionTime(times)); + + for (StaticBuffer b : janusgraphMutation.getDeletions()) + deletions.deleteColumn(b.as(StaticBuffer.BB_FACTORY)); + } + + if (janusgraphMutation.hasAdditions()) { + ColumnListMutation updates = m.withRow(columnFamily, key); + updates.setTimestamp(commitTime.getAdditionTime(times)); + + for (Entry e : janusgraphMutation.getAdditions()) { + Integer ttl = (Integer) e.getMetaData().get(EntryMetaData.TTL); + + if (null != ttl && ttl > 0) { + updates.putColumn(e.getColumnAs(StaticBuffer.BB_FACTORY), e.getValueAs(StaticBuffer.BB_FACTORY), ttl); + } else { + updates.putColumn(e.getColumnAs(StaticBuffer.BB_FACTORY), e.getValueAs(StaticBuffer.BB_FACTORY)); + } + } + } + } + } + + try { + m.execute(); + } catch (ConnectionException e) { + throw new TemporaryBackendException(e); + } + + sleepAfterWrite(txh, commitTime); + } + + @Override + public List getLocalKeyPartition() throws BackendException { + throw new UnsupportedOperationException(); + } + + @Override + public void clearStorage() throws BackendException { + try { + Cluster cluster = clusterContext.getClient(); + + Keyspace ks = cluster.getKeyspace(keySpaceName); + + // Not a big deal if Keyspace doesn't not exist (dropped manually by user or tests). + // This is called on per test setup basis to make sure that previous test cleaned + // everything up, so first invocation would always fail as Keyspace doesn't yet exist. + if (ks == null) + return; + + if (this.storageConfig.get(GraphDatabaseConfiguration.DROP_ON_CLEAR)) { + ks.dropKeyspace(); + } else { + final KeyspaceDefinition keyspaceDefinition = cluster.describeKeyspace(keySpaceName); + if (keyspaceDefinition == null) { + return; + } + for (final ColumnFamilyDefinition cf : keyspaceDefinition.getColumnFamilyList()) { + ks.truncateColumnFamily(new ColumnFamily<>(cf.getName(), null, null)); + } + } + } catch (ConnectionException e) { + throw new PermanentBackendException(e); + } + } + + @Override + public boolean exists() throws BackendException { + try { + return clusterContext.getClient().describeKeyspace(keySpaceName) != null; + } catch (ConnectionException e) { + throw new PermanentBackendException(e); + } + } + + private void ensureColumnFamilyExists(String name) throws BackendException { + Cluster cl = clusterContext.getClient(); + try { + KeyspaceDefinition ksDef = cl.describeKeyspace(keySpaceName); + boolean found = false; + if (null != ksDef) { + for (ColumnFamilyDefinition cfDef : ksDef.getColumnFamilyList()) { + found |= cfDef.getName().equals(name); + } + } + if (!found) { + ColumnFamilyDefinition cfDef = + cl.makeColumnFamilyDefinition() + .setName(name) + .setKeyspace(keySpaceName) + .setComparatorType("org.apache.cassandra.db.marshal.BytesType"); + + final ImmutableMap.Builder compressionOptions = new ImmutableMap.Builder<>(); + + if (storageConfig.has(COMPACTION_STRATEGY)) { + cfDef.setCompactionStrategy(storageConfig.get(COMPACTION_STRATEGY)); + } + + if (!compactionOptions.isEmpty()) { + cfDef.setCompactionStrategyOptions(compactionOptions); + } + + if (compressionEnabled) { + compressionOptions.put("sstable_compression", compressionClass) + .put("chunk_length_kb", Integer.toString(compressionChunkSizeKB)); + } + + cl.addColumnFamily(cfDef.setCompressionOptions(compressionOptions.build())); + } + } catch (ConnectionException e) { + throw new TemporaryBackendException(e); + } + } + + private static AstyanaxContext createCluster(AstyanaxContext.Builder cb) { + AstyanaxContext clusterCtx = cb.buildCluster(ThriftFamilyFactory.getInstance()); + clusterCtx.start(); + + return clusterCtx; + } + + private AstyanaxContext.Builder getContextBuilder(Configuration config, int maxConnsPerHost, String usedFor) { + + final ConnectionPoolType poolType = ConnectionPoolType.valueOf(config.get(CONNECTION_POOL_TYPE)); + + final NodeDiscoveryType discType = NodeDiscoveryType.valueOf(config.get(NODE_DISCOVERY_TYPE)); + + final int maxConnections = config.get(MAX_CONNECTIONS); + + final int maxOperationsPerConnection = config.get(MAX_OPERATIONS_PER_CONNECTION); + + final int connectionTimeout = (int) connectionTimeoutMS.toMillis(); + + ConnectionPoolConfigurationImpl cpool = + new ConnectionPoolConfigurationImpl(usedFor + "JanusGraphConnectionPool") + .setPort(port) + .setMaxOperationsPerConnection(maxOperationsPerConnection) + .setMaxConnsPerHost(maxConnsPerHost) + .setRetryDelaySlice(retryDelaySlice) + .setRetryMaxDelaySlice(retryMaxDelaySlice) + .setRetrySuspendWindow(retrySuspendWindow) + .setSocketTimeout(connectionTimeout) + .setConnectTimeout(connectionTimeout) + .setSeeds(StringUtils.join(hostnames, ",")); + + if (null != retryBackoffStrategy) { + cpool.setRetryBackoffStrategy(retryBackoffStrategy); + log.debug("Custom RetryBackoffStrategy {}", cpool.getRetryBackoffStrategy()); + } else { + log.debug("Default RetryBackoffStrategy {}", cpool.getRetryBackoffStrategy()); + } + + if (StringUtils.isNotBlank(localDatacenter)) { + cpool.setLocalDatacenter(localDatacenter); + log.debug("Set local datacenter: {}", cpool.getLocalDatacenter()); + } + + AstyanaxConfigurationImpl astyanaxConfiguration = + new AstyanaxConfigurationImpl() + .setConnectionPoolType(poolType) + .setDiscoveryType(discType) + .setTargetCassandraVersion("1.2") + .setMaxThriftSize(thriftFrameSizeBytes); + + if (0 < maxConnections) { + cpool.setMaxConns(maxConnections); + } + + if (hasAuthentication()) { + cpool.setAuthenticationCredentials(new SimpleAuthenticationCredentials(username, password)); + } + + if (config.get(SSL_ENABLED)) { + cpool.setSSLConnectionContext(new SSLConnectionContext(config.get(SSL_TRUSTSTORE_LOCATION), config.get(SSL_TRUSTSTORE_PASSWORD))); + } + + AstyanaxContext.Builder ctxBuilder = new AstyanaxContext.Builder(); + + // Standard context builder options + ctxBuilder + .forCluster(clusterName) + .forKeyspace(keySpaceName) + .withAstyanaxConfiguration(astyanaxConfiguration) + .withConnectionPoolConfiguration(cpool) + .withConnectionPoolMonitor(new CountingConnectionPoolMonitor()); + + // Conditional context builder option: host supplier + if (config.has(HOST_SUPPLIER)) { + String hostSupplier = config.get(HOST_SUPPLIER); + final Supplier> supplier; + if (hostSupplier != null) { + try { + supplier = (Supplier>) Class.forName(hostSupplier).newInstance(); + ctxBuilder.withHostSupplier(supplier); + } catch (Exception e) { + log.warn("Problem with host supplier class " + hostSupplier + ", going to use default.", e); + } + } + } + + return ctxBuilder; + } + + private void ensureKeyspaceExists(Cluster cl) throws BackendException { + KeyspaceDefinition ksDef; + + try { + ksDef = cl.describeKeyspace(keySpaceName); + + if (null != ksDef && ksDef.getName().equals(keySpaceName)) { + log.debug("Found keyspace {}", keySpaceName); + return; + } + } catch (ConnectionException e) { + log.debug("Failed to describe keyspace {}", keySpaceName); + } + + log.debug("Creating keyspace {}...", keySpaceName); + try { + ksDef = cl.makeKeyspaceDefinition() + .setName(keySpaceName) + .setStrategyClass(storageConfig.get(REPLICATION_STRATEGY)) + .setStrategyOptions(strategyOptions); + cl.addKeyspace(ksDef); + + log.debug("Created keyspace {}", keySpaceName); + } catch (ConnectionException e) { + log.debug("Failed to create keyspace {}", keySpaceName); + throw new TemporaryBackendException(e); + } + } + + private static RetryBackoffStrategy getRetryBackoffStrategy(String desc) throws PermanentBackendException { + if (null == desc) + return null; + + String[] tokens = desc.split(","); + String policyClassName = tokens[0]; + int argCount = tokens.length - 1; + Integer[] args = new Integer[argCount]; + + for (int i = 1; i < tokens.length; i++) { + args[i - 1] = Integer.valueOf(tokens[i]); + } + + try { + RetryBackoffStrategy rbs = instantiate(policyClassName, args, desc); + log.debug("Instantiated RetryBackoffStrategy object {} from config string \"{}\"", rbs, desc); + return rbs; + } catch (Exception e) { + throw new PermanentBackendException("Failed to instantiate Astyanax RetryBackoffStrategy implementation", e); + } + } + + private static RetryPolicy getRetryPolicy(String serializedRetryPolicy) throws BackendException { + String[] tokens = serializedRetryPolicy.split(","); + String policyClassName = tokens[0]; + int argCount = tokens.length - 1; + Integer[] args = new Integer[argCount]; + for (int i = 1; i < tokens.length; i++) { + args[i - 1] = Integer.valueOf(tokens[i]); + } + + try { + RetryPolicy rp = instantiate(policyClassName, args, serializedRetryPolicy); + log.debug("Instantiated RetryPolicy object {} from config string \"{}\"", rp, serializedRetryPolicy); + return rp; + } catch (Exception e) { + throw new PermanentBackendException("Failed to instantiate Astyanax Retry Policy class", e); + } + } + + @SuppressWarnings("unchecked") + private static V instantiate(String policyClassName, Integer[] args, String raw) throws Exception { + for (Constructor con : Class.forName(policyClassName).getConstructors()) { + Class[] parameterTypes = con.getParameterTypes(); + + // match constructor by number of arguments first + if (args.length != parameterTypes.length) + continue; + + // check if the constructor parameter types are compatible with argument types (which are integer) + // note that we allow long.class arguments too because integer is cast to long by runtime. + boolean intsOrLongs = true; + for (Class pc : parameterTypes) { + if (!pc.equals(int.class) && !pc.equals(long.class)) { + intsOrLongs = false; + break; + } + } + + // we found a constructor with required number of parameters but times didn't match, let's carry on + if (!intsOrLongs) + continue; + + if (log.isDebugEnabled()) + log.debug("About to instantiate class {} with {} arguments", con.toString(), args.length); + + return (V) con.newInstance(args); + } + + throw new Exception("Failed to identify a class matching the Astyanax Retry Policy config string \"" + raw + "\""); + } + + @Override + public Map getCompressionOptions(String cf) throws BackendException { + try { + Keyspace k = keyspaceContext.getClient(); + + KeyspaceDefinition keyspaceDefinition = k.describeKeyspace(); + + if (null == keyspaceDefinition) { + throw new PermanentBackendException("Keyspace " + k.getKeyspaceName() + " is undefined"); + } + + ColumnFamilyDefinition columnFamilyDefinition = keyspaceDefinition.getColumnFamily(cf); + + if (null == columnFamilyDefinition) { + throw new PermanentBackendException("Column family " + cf + " is undefined"); + } + + return columnFamilyDefinition.getCompressionOptions(); + } catch (ConnectionException e) { + throw new PermanentBackendException(e); + } + } +} + diff --git a/src/main/java/org/janusgraph/diskstorage/cassandra/embedded/CassandraEmbeddedKeyColumnValueStore.java b/src/main/java/org/janusgraph/diskstorage/cassandra/embedded/CassandraEmbeddedKeyColumnValueStore.java new file mode 100644 index 0000000..32b1abf --- /dev/null +++ b/src/main/java/org/janusgraph/diskstorage/cassandra/embedded/CassandraEmbeddedKeyColumnValueStore.java @@ -0,0 +1,496 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.embedded; + +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Iterators; +import org.janusgraph.diskstorage.util.time.TimestampProvider; +import org.janusgraph.diskstorage.*; +import org.janusgraph.diskstorage.cassandra.utils.CassandraHelper; +import org.janusgraph.diskstorage.keycolumnvalue.*; +import org.janusgraph.diskstorage.util.RecordIterator; +import org.janusgraph.diskstorage.util.StaticArrayBuffer; +import org.janusgraph.diskstorage.util.StaticArrayEntry; + +import org.apache.cassandra.config.CFMetaData; +import org.apache.cassandra.config.Schema; +import org.apache.cassandra.db.*; +import org.apache.cassandra.db.ConsistencyLevel; +import org.apache.cassandra.db.composites.CellNames; +import org.apache.cassandra.db.composites.Composite; +import org.apache.cassandra.db.filter.IDiskAtomFilter; +import org.apache.cassandra.db.filter.SliceQueryFilter; +import org.apache.cassandra.dht.*; +import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.exceptions.IsBootstrappingException; +import org.apache.cassandra.exceptions.RequestTimeoutException; +import org.apache.cassandra.exceptions.UnavailableException; +import org.apache.cassandra.service.StorageProxy; +import org.apache.cassandra.service.StorageService; +import org.apache.cassandra.thrift.SlicePredicate; +import org.apache.cassandra.thrift.SliceRange; +import org.apache.cassandra.thrift.ThriftValidation; +import org.apache.commons.lang.ArrayUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; + +import java.nio.ByteBuffer; +import java.util.*; +import static org.janusgraph.diskstorage.cassandra.CassandraTransaction.getTx; + +public class CassandraEmbeddedKeyColumnValueStore implements KeyColumnValueStore { + + private static final Logger log = LoggerFactory.getLogger(CassandraEmbeddedKeyColumnValueStore.class); + + private final String keyspace; + private final String columnFamily; + private final CassandraEmbeddedStoreManager storeManager; + private final TimestampProvider times; + private final CassandraEmbeddedGetter entryGetter; + + public CassandraEmbeddedKeyColumnValueStore( + String keyspace, + String columnFamily, + CassandraEmbeddedStoreManager storeManager) throws RuntimeException { + this.keyspace = keyspace; + this.columnFamily = columnFamily; + this.storeManager = storeManager; + this.times = this.storeManager.getTimestampProvider(); + entryGetter = new CassandraEmbeddedGetter(storeManager.getMetaDataSchema(columnFamily),times); + } + + @Override + public void close() throws BackendException { + } + + @Override + public void acquireLock(StaticBuffer key, StaticBuffer column, + StaticBuffer expectedValue, StoreTransaction txh) throws BackendException { + throw new UnsupportedOperationException(); + } + + @Override + public KeyIterator getKeys(KeyRangeQuery keyRangeQuery, StoreTransaction txh) throws BackendException { + IPartitioner partitioner = StorageService.getPartitioner(); + + // see rant about this in Astyanax implementation + if (partitioner instanceof RandomPartitioner || partitioner instanceof Murmur3Partitioner) + throw new PermanentBackendException("This operation is only supported when byte-ordered partitioner is used."); + + return new RowIterator(keyRangeQuery, storeManager.getPageSize(), txh); + } + + @Override + public KeyIterator getKeys(SliceQuery query, StoreTransaction txh) throws BackendException { + return new RowIterator(getMinimumToken(), getMaximumToken(), query, storeManager.getPageSize(), txh); + } + + + /** + * Create a RangeSliceCommand and run it against the StorageProxy. + *

+ * To match the behavior of the standard Cassandra thrift API endpoint, the + * {@code nowMillis} argument should be the number of milliseconds since the + * UNIX Epoch (e.g. System.currentTimeMillis() or equivalent obtained + * through a {@link TimestampProvider}). + */ + private List getKeySlice(Token start, + Token end, + @Nullable SliceQuery sliceQuery, + int pageSize, + long nowMillis) throws BackendException { + + SliceRange columnSlice = new SliceRange(); + if (sliceQuery == null) { + columnSlice.setStart(ArrayUtils.EMPTY_BYTE_ARRAY) + .setFinish(ArrayUtils.EMPTY_BYTE_ARRAY) + .setCount(5); + } else { + columnSlice.setStart(sliceQuery.getSliceStart().asByteBuffer()) + .setFinish(sliceQuery.getSliceEnd().asByteBuffer()) + .setCount(sliceQuery.hasLimit() ? sliceQuery.getLimit() : Integer.MAX_VALUE); + } + /* Note: we need to fetch columns for each row as well to remove "range ghosts" */ + SlicePredicate predicate = new SlicePredicate().setSlice_range(columnSlice); + + RowPosition startPosition = start.minKeyBound(); + RowPosition endPosition = end.minKeyBound(); + + List rows; + + try { + CFMetaData cfm = Schema.instance.getCFMetaData(keyspace, columnFamily); + IDiskAtomFilter filter = ThriftValidation.asIFilter(predicate, cfm, null); + + final RangeSliceCommand cmd = new RangeSliceCommand(keyspace, columnFamily, nowMillis, filter, + new Bounds<>(startPosition, endPosition), pageSize); + + rows = StorageProxy.getRangeSlice(cmd, ConsistencyLevel.QUORUM); + } catch (Exception e) { + throw new PermanentBackendException(e); + } + + return rows; + } + + @Override + public String getName() { + return columnFamily; + } + + @Override + public EntryList getSlice(KeySliceQuery query, StoreTransaction txh) throws BackendException { + + /** + * This timestamp mimics the timestamp used by + * {@link org.apache.cassandra.thrift.CassandraServer#get(ByteBuffer,ColumnPath,ConsistencyLevel)}. + * + * That method passes the server's System.currentTimeMillis() to + * {@link ReadCommand#create(String, ByteBuffer, String, long, IDiskAtomFilter)}. + * {@code create(...)} in turn passes that timestamp to the SliceFromReadCommand constructor. + */ + final long nowMillis = times.getTime().toEpochMilli(); + Composite startComposite = CellNames.simpleDense(query.getSliceStart().asByteBuffer()); + Composite endComposite = CellNames.simpleDense(query.getSliceEnd().asByteBuffer()); + SliceQueryFilter sqf = new SliceQueryFilter(startComposite, endComposite, + false, query.getLimit() + (query.hasLimit()?1:0)); + ReadCommand sliceCmd = new SliceFromReadCommand(keyspace, query.getKey().asByteBuffer(), columnFamily, nowMillis, sqf); + + List slice = read(sliceCmd, getTx(txh).getReadConsistencyLevel().getDB()); + + if (null == slice || 0 == slice.size()) + return EntryList.EMPTY_LIST; + + int sliceSize = slice.size(); + if (1 < sliceSize) + throw new PermanentBackendException("Received " + sliceSize + " rows for single key"); + + Row r = slice.get(0); + + if (null == r) { + log.warn("Null Row object retrieved from Cassandra StorageProxy"); + return EntryList.EMPTY_LIST; + } + + ColumnFamily cf = r.cf; + + if (null == cf) { + log.debug("null ColumnFamily (\"{}\")", columnFamily); + return EntryList.EMPTY_LIST; + } + + if (cf.isMarkedForDelete()) + return EntryList.EMPTY_LIST; + + return CassandraHelper.makeEntryList( + Iterables.filter(cf.getSortedColumns(), new FilterDeletedColumns(nowMillis)), + entryGetter, + query.getSliceEnd(), + query.getLimit()); + + } + + private static class FilterDeletedColumns implements Predicate { + + private final long tsMillis; + private final int tsSeconds; + + private FilterDeletedColumns(long tsMillis) { + this.tsMillis = tsMillis; + this.tsSeconds = (int)(this.tsMillis / 1000L); + } + + @Override + public boolean apply(Cell input) { + return input.isLive(tsMillis); + } + } + + @Override + public Map getSlice(List keys, SliceQuery query, StoreTransaction txh) throws BackendException { + throw new UnsupportedOperationException(); + } + + @Override + public void mutate(StaticBuffer key, List additions, + List deletions, StoreTransaction txh) throws BackendException { + Map mutations = ImmutableMap.of(key, new + KCVMutation(additions, deletions)); + mutateMany(mutations, txh); + } + + + public void mutateMany(Map mutations, + StoreTransaction txh) throws BackendException { + storeManager.mutateMany(ImmutableMap.of(columnFamily, mutations), txh); + } + + private static List read(ReadCommand cmd, org.apache.cassandra.db.ConsistencyLevel consistencyLevel) throws BackendException { + final ArrayList cmdHolder = new ArrayList<>(1); + cmdHolder.add(cmd); + return read(cmdHolder, consistencyLevel); + } + + private static List read(List commands, org.apache.cassandra.db.ConsistencyLevel consistencyLevel) throws BackendException { + try { + return StorageProxy.read(commands, consistencyLevel); + } catch (UnavailableException | IsBootstrappingException e) { + throw new TemporaryBackendException(e); + } catch (RequestTimeoutException | InvalidRequestException e) { + throw new PermanentBackendException(e); + } + } + + private static class CassandraEmbeddedGetter implements StaticArrayEntry.GetColVal { + + private final EntryMetaData[] schema; + private final TimestampProvider times; + + private CassandraEmbeddedGetter(EntryMetaData[] schema, TimestampProvider times) { + this.schema = schema; + this.times = times; + } + + @Override + public ByteBuffer getColumn(Cell element) { + return org.apache.cassandra.utils.ByteBufferUtil.clone(element.name().toByteBuffer()); + } + + @Override + public ByteBuffer getValue(Cell element) { + return org.apache.cassandra.utils.ByteBufferUtil.clone(element.value()); + } + + @Override + public EntryMetaData[] getMetaSchema(Cell element) { + return schema; + } + + @Override + public Object getMetaData(Cell element, EntryMetaData meta) { + switch (meta) { + case TIMESTAMP: + return element.timestamp(); + case TTL: + return ((element instanceof ExpiringCell) + ? ((ExpiringCell) element).getTimeToLive() + : 0); + default: + throw new UnsupportedOperationException("Unsupported meta data: " + meta); + } + } + } + + private class RowIterator implements KeyIterator { + private final Token maximumToken; + private final SliceQuery sliceQuery; + private final StoreTransaction txh; + + /** + * This RowIterator will use this timestamp for its entire lifetime, + * even if the iterator runs more than one distinct slice query while + * paging. This field must be in units of milliseconds since + * the UNIX Epoch. + * When this value needs to be compared to TTL seconds expressed in seconds, + * Cassandra internals do the conversion. + */ + private final long nowMillis; + + private Iterator keys; + private ByteBuffer lastSeenKey = null; + private Row currentRow; + private final int pageSize; + + private boolean isClosed; + + public RowIterator(KeyRangeQuery keyRangeQuery, int pageSize, StoreTransaction txh) throws BackendException { + this(StorageService.getPartitioner().getToken(keyRangeQuery.getKeyStart().asByteBuffer()), + StorageService.getPartitioner().getToken(keyRangeQuery.getKeyEnd().asByteBuffer()), + keyRangeQuery, + pageSize, + txh); + } + + public RowIterator(Token minimum, Token maximum, SliceQuery sliceQuery, int pageSize, StoreTransaction txh) throws BackendException { + this.pageSize = pageSize; + this.sliceQuery = sliceQuery; + this.maximumToken = maximum; + this.txh = txh; + this.nowMillis = times.getTime().toEpochMilli(); + this.keys = getRowsIterator(getKeySlice(minimum, maximum, sliceQuery, pageSize, nowMillis)); + } + + @Override + public boolean hasNext() { + try { + return hasNextInternal(); + } catch (BackendException e) { + throw new RuntimeException(e); + } + } + + @Override + public StaticBuffer next() { + ensureOpen(); + + if (!hasNext()) + throw new NoSuchElementException(); + + currentRow = keys.next(); + ByteBuffer currentKey = currentRow.key.getKey().duplicate(); + + try { + return StaticArrayBuffer.of(currentKey); + } finally { + lastSeenKey = currentKey; + } + } + + @Override + public void close() { + isClosed = true; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public RecordIterator getEntries() { + ensureOpen(); + + if (sliceQuery == null) + throw new IllegalStateException("getEntries() requires SliceQuery to be set."); + + return new RecordIterator() { + final Iterator columns = CassandraHelper.makeEntryIterator( + Iterables.filter(currentRow.cf.getSortedColumns(), new FilterDeletedColumns(nowMillis)), + entryGetter, + sliceQuery.getSliceEnd(), + sliceQuery.getLimit()); + + //cfToEntries(currentRow.cf, sliceQuery).iterator(); + + @Override + public boolean hasNext() { + ensureOpen(); + return columns.hasNext(); + } + + @Override + public Entry next() { + ensureOpen(); + return columns.next(); + } + + @Override + public void close() { + isClosed = true; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + + } + + private boolean hasNextInternal() throws BackendException { + ensureOpen(); + + if (keys == null) + return false; + + boolean hasNext = keys.hasNext(); + + if (!hasNext && lastSeenKey != null) { + Token lastSeenToken = StorageService.getPartitioner().getToken(lastSeenKey.duplicate()); + + // let's check if we reached key upper bound already so we can skip one useless call to Cassandra + if (maximumToken != getMinimumToken() && lastSeenToken.equals(maximumToken)) { + return false; + } + + List newKeys = getKeySlice(StorageService.getPartitioner().getToken(lastSeenKey), maximumToken, sliceQuery, pageSize, nowMillis); + + keys = getRowsIterator(newKeys, lastSeenKey); + hasNext = keys.hasNext(); + } + + return hasNext; + } + + private void ensureOpen() { + if (isClosed) + throw new IllegalStateException("Iterator has been closed."); + } + + private Iterator getRowsIterator(List rows) { + if (rows == null) + return null; + + return Iterators.filter(rows.iterator(), row -> { + // The hasOnlyTombstones(x) call below ultimately calls Column.isMarkedForDelete(x) + return !(row == null || row.cf == null || row.cf.isMarkedForDelete() + || row.cf.hasOnlyTombstones(nowMillis)); + }); + } + + private Iterator getRowsIterator(List rows, final ByteBuffer exceptKey) { + Iterator rowIterator = getRowsIterator(rows); + + if (rowIterator == null) + return null; + + return Iterators.filter(rowIterator, row -> row != null && !row.key.getKey().equals(exceptKey)); + } + } + + private static Token getMinimumToken() throws PermanentBackendException { + IPartitioner partitioner = StorageService.getPartitioner(); + + if (partitioner instanceof RandomPartitioner) { + return ((RandomPartitioner) partitioner).getMinimumToken(); + } else if (partitioner instanceof Murmur3Partitioner) { + return ((Murmur3Partitioner) partitioner).getMinimumToken(); + } else if (partitioner instanceof ByteOrderedPartitioner) { + //TODO: This makes the assumption that its an EdgeStore (i.e. 8 byte keys) + return new ByteOrderedPartitioner.BytesToken(org.janusgraph.diskstorage.util.ByteBufferUtil.zeroByteBuffer(8)); + } else { + throw new PermanentBackendException("Unsupported partitioner: " + partitioner); + } + } + + private static Token getMaximumToken() throws PermanentBackendException { + IPartitioner partitioner = StorageService.getPartitioner(); + + if (partitioner instanceof RandomPartitioner) { + return new RandomPartitioner.BigIntegerToken(RandomPartitioner.MAXIMUM); + } else if (partitioner instanceof Murmur3Partitioner) { + return new Murmur3Partitioner.LongToken(Murmur3Partitioner.MAXIMUM); + } else if (partitioner instanceof ByteOrderedPartitioner) { + //TODO: This makes the assumption that its an EdgeStore (i.e. 8 byte keys) + return new ByteOrderedPartitioner.BytesToken(org.janusgraph.diskstorage.util.ByteBufferUtil.oneByteBuffer(8)); + } else { + throw new PermanentBackendException("Unsupported partitioner: " + partitioner); + } + } +} diff --git a/src/main/java/org/janusgraph/diskstorage/cassandra/embedded/CassandraEmbeddedStoreManager.java b/src/main/java/org/janusgraph/diskstorage/cassandra/embedded/CassandraEmbeddedStoreManager.java new file mode 100644 index 0000000..0f999cd --- /dev/null +++ b/src/main/java/org/janusgraph/diskstorage/cassandra/embedded/CassandraEmbeddedStoreManager.java @@ -0,0 +1,419 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.embedded; + +import static org.janusgraph.diskstorage.cassandra.CassandraTransaction.getTx; + +import java.io.File; +import java.util.*; +import java.util.concurrent.TimeoutException; + +import org.janusgraph.diskstorage.*; +import org.janusgraph.diskstorage.cassandra.utils.CassandraHelper; +import org.janusgraph.diskstorage.configuration.Configuration; +import org.janusgraph.diskstorage.keycolumnvalue.*; +import org.janusgraph.diskstorage.util.ByteBufferUtil; +import org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration; + +import org.apache.cassandra.cache.CachingOptions; +import org.apache.cassandra.config.CFMetaData; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.config.KSMetaData; +import org.apache.cassandra.config.Schema; +import org.apache.cassandra.db.ColumnFamilyType; +import org.apache.cassandra.db.ConsistencyLevel; +import org.apache.cassandra.db.SliceByNamesReadCommand; +import org.apache.cassandra.db.composites.CellName; +import org.apache.cassandra.db.composites.CellNames; +import org.apache.cassandra.db.filter.NamesQueryFilter; +import org.apache.cassandra.db.marshal.BytesType; +import org.apache.cassandra.dht.IPartitioner; +import org.apache.cassandra.dht.Range; +import org.apache.cassandra.dht.Token; +import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.exceptions.RequestExecutionException; +import org.apache.cassandra.io.compress.CompressionParameters; +import org.apache.cassandra.scheduler.IRequestScheduler; +import org.apache.cassandra.service.MigrationManager; +import org.apache.cassandra.service.StorageProxy; +import org.apache.cassandra.service.StorageService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreManager; +import org.janusgraph.diskstorage.cassandra.utils.CassandraDaemonWrapper; + +public class CassandraEmbeddedStoreManager extends AbstractCassandraStoreManager { + + private static final Logger log = LoggerFactory.getLogger(CassandraEmbeddedStoreManager.class); + + /** + * The default value for + * {@link GraphDatabaseConfiguration#STORAGE_CONF_FILE}. + */ + public static final String CASSANDRA_YAML_DEFAULT = "./conf/cassandra.yaml"; + + private final Map openStores; + + private final IRequestScheduler requestScheduler; + + public CassandraEmbeddedStoreManager(Configuration config) { + super(config); + + String cassandraConfig = CASSANDRA_YAML_DEFAULT; + if (config.has(GraphDatabaseConfiguration.STORAGE_CONF_FILE)) { + cassandraConfig = config.get(GraphDatabaseConfiguration.STORAGE_CONF_FILE); + } + + assert cassandraConfig != null && !cassandraConfig.isEmpty(); + + File ccf = new File(cassandraConfig); + + if (ccf.exists() && ccf.isAbsolute()) { + cassandraConfig = "file://" + cassandraConfig; + log.debug("Set cassandra config string \"{}\"", cassandraConfig); + } + + CassandraDaemonWrapper.start(cassandraConfig); + + this.openStores = new HashMap<>(8); + this.requestScheduler = DatabaseDescriptor.getRequestScheduler(); + } + + @Override + public Deployment getDeployment() { + return Deployment.EMBEDDED; + } + + @Override + public IPartitioner getCassandraPartitioner() + throws BackendException { + try { + return StorageService.getPartitioner(); + } catch (Exception e) { + log.warn("Could not read local token range: {}", e); + throw new PermanentBackendException("Could not read partitioner information on cluster", e); + } + } + + @Override + public String toString() { + return "embeddedCassandra" + super.toString(); + } + + @Override + public void close() { + openStores.clear(); + } + + @Override + public synchronized KeyColumnValueStore openDatabase(String name, StoreMetaData.Container metaData) + throws BackendException { + if (openStores.containsKey(name)) + return openStores.get(name); + + // Ensure that both the keyspace and column family exist + ensureKeyspaceExists(keySpaceName); + ensureColumnFamilyExists(keySpaceName, name); + + final CassandraEmbeddedKeyColumnValueStore store = new CassandraEmbeddedKeyColumnValueStore(keySpaceName, name, + this); + openStores.put(name, store); + return store; + } + + /* + * Raw type warnings are suppressed in this method because + * {@link StorageService#getLocalPrimaryRanges(String)} returns a raw + * (not parameterized) type. + */ + public List getLocalKeyPartition() throws BackendException { + ensureKeyspaceExists(keySpaceName); + + + final Collection> ranges = StorageService.instance.getPrimaryRanges(keySpaceName); + + final List keyRanges = new ArrayList<>(ranges.size()); + + for (Range range : ranges) { + keyRanges.add(CassandraHelper.transformRange(range)); + } + + return keyRanges; + } + + /* + * This implementation can't handle counter columns. + * + * The private method internal_batch_mutate in CassandraServer as of 1.2.0 + * provided most of the following method after transaction handling. + */ + @Override + public void mutateMany(Map> mutations, StoreTransaction txh) + throws BackendException { + Preconditions.checkNotNull(mutations); + + final MaskedTimestamp commitTime = new MaskedTimestamp(txh); + + int size = 0; + for (Map mutation : mutations.values()) size += mutation.size(); + Map rowMutations = new HashMap<>(size); + + for (Map.Entry> mutEntry : mutations.entrySet()) { + String columnFamily = mutEntry.getKey(); + for (Map.Entry janusgraphMutation : mutEntry.getValue().entrySet()) { + StaticBuffer key = janusgraphMutation.getKey(); + KCVMutation mut = janusgraphMutation.getValue(); + + org.apache.cassandra.db.Mutation rm = rowMutations.computeIfAbsent(key, + k -> new org.apache.cassandra.db.Mutation(keySpaceName, k.asByteBuffer())); + + if (mut.hasAdditions()) { + for (Entry e : mut.getAdditions()) { + Integer ttl = (Integer) e.getMetaData().get(EntryMetaData.TTL); + + if (null != ttl && ttl > 0) { + rm.add(columnFamily, CellNames.simpleDense(e.getColumnAs(StaticBuffer.BB_FACTORY)), + e.getValueAs(StaticBuffer.BB_FACTORY), commitTime.getAdditionTime(times), ttl); + } else { + rm.add(columnFamily, CellNames.simpleDense(e.getColumnAs(StaticBuffer.BB_FACTORY)), + e.getValueAs(StaticBuffer.BB_FACTORY), commitTime.getAdditionTime(times)); + } + } + } + + if (mut.hasDeletions()) { + for (StaticBuffer col : mut.getDeletions()) { + rm.delete(columnFamily, CellNames.simpleDense(col.as(StaticBuffer.BB_FACTORY)), + commitTime.getDeletionTime(times)); + } + } + + } + } + + mutate(new ArrayList<>(rowMutations.values()), getTx(txh).getWriteConsistencyLevel().getDB()); + + sleepAfterWrite(txh, commitTime); + } + + private void mutate(List commands, + org.apache.cassandra.db.ConsistencyLevel consistencyLevel) throws BackendException { + try { + schedule(DatabaseDescriptor.getRpcTimeout()); + try { + if (atomicBatch) { + StorageProxy.mutateAtomically(commands, consistencyLevel); + } else { + StorageProxy.mutate(commands, consistencyLevel); + } + } catch (RequestExecutionException e) { + throw new TemporaryBackendException(e); + } finally { + release(); + } + } catch (TimeoutException ex) { + log.debug("Cassandra TimeoutException", ex); + throw new TemporaryBackendException(ex); + } + } + + private void schedule(long timeoutMS) throws TimeoutException { + requestScheduler.queue(Thread.currentThread(), "default", DatabaseDescriptor.getRpcTimeout()); + } + + /** + * Release count for the used up resources + */ + private void release() { + requestScheduler.release(); + } + + @Override + public void clearStorage() throws BackendException { + openStores.clear(); + try { + KSMetaData ksMetaData = Schema.instance.getKSMetaData(keySpaceName); + + // Not a big deal if Keyspace doesn't not exist (dropped manually by user or tests). + // This is called on per test setup basis to make sure that previous test cleaned + // everything up, so first invocation would always fail as Keyspace doesn't yet exist. + if (ksMetaData == null) + return; + + if (this.storageConfig.get(GraphDatabaseConfiguration.DROP_ON_CLEAR)) { + MigrationManager.announceKeyspaceDrop(keySpaceName); + } else { + for (final String cfName : ksMetaData.cfMetaData().keySet()) + StorageService.instance.truncate(keySpaceName, cfName); + } + } catch (Exception e) { + throw new PermanentBackendException(e); + } + } + + @Override + public boolean exists() throws BackendException { + return Schema.instance.getKeyspaceInstance(keySpaceName) != null; + } + + private void ensureKeyspaceExists(String keyspaceName) throws BackendException { + if (null != Schema.instance.getKeyspaceInstance(keyspaceName)) + return; + + // Keyspace not found; create it + String strategyName = storageConfig.get(REPLICATION_STRATEGY); + + KSMetaData ksm; + try { + ksm = KSMetaData.newKeyspace(keyspaceName, strategyName, strategyOptions, true); + } catch (ConfigurationException e) { + throw new PermanentBackendException("Failed to instantiate keyspace metadata for " + keyspaceName, e); + } + try { + MigrationManager.announceNewKeyspace(ksm); + log.info("Created keyspace {}", keyspaceName); + } catch (ConfigurationException e) { + throw new PermanentBackendException("Failed to create keyspace " + keyspaceName, e); + } + } + + private void ensureColumnFamilyExists(String keyspaceName, String columnFamilyName) throws BackendException { + if (null != Schema.instance.getCFMetaData(keyspaceName, columnFamilyName)) + return; + + // Column Family not found; create it + final CFMetaData cfm = new CFMetaData(keyspaceName, columnFamilyName, ColumnFamilyType.Standard, + CellNames.fromAbstractType(BytesType.instance, true)); + try { + if (storageConfig.has(COMPACTION_STRATEGY)) { + cfm.compactionStrategyClass(CFMetaData.createCompactionStrategy(storageConfig.get(COMPACTION_STRATEGY))); + } + if (!compactionOptions.isEmpty()) { + cfm.compactionStrategyOptions(compactionOptions); + } + } catch (ConfigurationException e) { + throw new PermanentBackendException("Failed to create column family metadata for " + keyspaceName + ":" + + columnFamilyName, e); + } + + // Hard-coded caching settings + if (columnFamilyName.startsWith(Backend.EDGESTORE_NAME)) { + cfm.caching(CachingOptions.KEYS_ONLY); + } else if (columnFamilyName.startsWith(Backend.INDEXSTORE_NAME)) { + cfm.caching(CachingOptions.ROWS_ONLY); + } + + // Configure sstable compression + final CompressionParameters cp; + if (compressionEnabled) { + try { + cp = new CompressionParameters(compressionClass, compressionChunkSizeKB * 1024, + Collections.emptyMap()); + // CompressionParameters doesn't override toString(), so be explicit + log.debug("Creating CF {}: setting {}={} and {}={} on {}", columnFamilyName, + CompressionParameters.SSTABLE_COMPRESSION, compressionClass, CompressionParameters.CHUNK_LENGTH_KB, + compressionChunkSizeKB, cp); + } catch (ConfigurationException ce) { + throw new PermanentBackendException(ce); + } + } else { + cp = new CompressionParameters(null); + log.debug("Creating CF {}: setting {} to null to disable compression", + columnFamilyName, CompressionParameters.SSTABLE_COMPRESSION); + } + cfm.compressionParameters(cp); + + try { + cfm.addDefaultIndexNames(); + } catch (ConfigurationException e) { + throw new PermanentBackendException("Failed to create column family metadata for " + keyspaceName + ":" + + columnFamilyName, e); + } + try { + MigrationManager.announceNewColumnFamily(cfm); + log.info("Created CF {} in KS {}", columnFamilyName, keyspaceName); + } catch (ConfigurationException e) { + throw new PermanentBackendException("Failed to create column family " + keyspaceName + ":" + + columnFamilyName, e); + } + + /* + * I'm chasing a nondeterministic exception that appears only rarely on my + * machine when executing the embedded cassandra tests. If these dummy + * reads ever actually fail and dump a log message, it could help debug + * the root cause. + * + * java.lang.RuntimeException: java.lang.IllegalArgumentException: Unknown table/cf pair (InternalCassandraEmbeddedKeyColumnValueTest.testStore1) + * at org.apache.cassandra.service.StorageProxy$DroppableRunnable.run(StorageProxy.java:1582) + * at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) + * at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) + * at java.lang.Thread.run(Thread.java:744) + * Caused by: java.lang.IllegalArgumentException: Unknown table/cf pair (InternalCassandraEmbeddedKeyColumnValueTest.testStore1) + * at org.apache.cassandra.db.Table.getColumnFamilyStore(Table.java:166) + * at org.apache.cassandra.db.Table.getRow(Table.java:354) + * at org.apache.cassandra.db.SliceFromReadCommand.getRow(SliceFromReadCommand.java:70) + * at org.apache.cassandra.service.StorageProxy$LocalReadRunnable.runMayThrow(StorageProxy.java:1052) + * at org.apache.cassandra.service.StorageProxy$DroppableRunnable.run(StorageProxy.java:1578) + * ... 3 more + */ + retryDummyRead(keyspaceName, columnFamilyName); + } + + @Override + public Map getCompressionOptions(String cf) throws BackendException { + + CFMetaData cfm = Schema.instance.getCFMetaData(keySpaceName, cf); + + if (cfm == null) + return null; + + return ImmutableMap.copyOf(cfm.compressionParameters().asThriftOptions()); + } + + private void retryDummyRead(String ks, String cf) throws PermanentBackendException { + + final long limit = System.currentTimeMillis() + (60L * 1000L); + + while (System.currentTimeMillis() < limit) { + try { + // This is a singleton set. We need to define a comparator because SimpleDenseCellName is not + // comparable, but the comparator that we pass into the TreeSet constructor does not have to be useful + final SortedSet names = new TreeSet<>((o1, o2) -> 0); + names.add(CellNames.simpleDense(ByteBufferUtil.zeroByteBuffer(1))); + NamesQueryFilter nqf = new NamesQueryFilter(names); + final SliceByNamesReadCommand cmd = new SliceByNamesReadCommand(ks, + ByteBufferUtil.zeroByteBuffer(1), cf, 1L, nqf); + StorageProxy.read(ImmutableList.of(cmd), ConsistencyLevel.QUORUM); + log.info("Read on CF {} in KS {} succeeded", cf, ks); + return; + } catch (Throwable t) { + log.warn("Failed to read CF {} in KS {} following creation", cf, ks, t); + } + + try { + Thread.sleep(1000L); + } catch (InterruptedException e) { + throw new PermanentBackendException(e); + } + } + + throw new PermanentBackendException("Timed out while attempting to read CF " + cf + " in KS " + ks + + " following creation"); + } +} diff --git a/src/main/java/org/janusgraph/diskstorage/cassandra/thrift/CassandraThriftKeyColumnValueStore.java b/src/main/java/org/janusgraph/diskstorage/cassandra/thrift/CassandraThriftKeyColumnValueStore.java new file mode 100644 index 0000000..ae8f7cf --- /dev/null +++ b/src/main/java/org/janusgraph/diskstorage/cassandra/thrift/CassandraThriftKeyColumnValueStore.java @@ -0,0 +1,537 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.thrift; + +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; + +import org.janusgraph.diskstorage.*; +import org.janusgraph.diskstorage.cassandra.thrift.thriftpool.CTConnection; +import org.janusgraph.diskstorage.cassandra.thrift.thriftpool.CTConnectionPool; +import org.janusgraph.diskstorage.keycolumnvalue.*; +import org.janusgraph.diskstorage.util.*; +import org.apache.cassandra.dht.*; +import org.apache.cassandra.thrift.*; +import org.apache.cassandra.thrift.ConsistencyLevel; +import org.apache.commons.lang.ArrayUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; +import java.nio.ByteBuffer; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static org.janusgraph.diskstorage.cassandra.CassandraTransaction.getTx; +import static org.janusgraph.diskstorage.cassandra.utils.CassandraHelper.convert; +import static org.janusgraph.diskstorage.cassandra.utils.CassandraHelper.makeEntryIterator; +import static org.janusgraph.diskstorage.cassandra.utils.CassandraHelper.makeEntryList; + +/** + * A JanusGraph {@code KeyColumnValueStore} backed by Cassandra. + * This uses the Cassandra Thrift API. + * + * @author Dan LaRocque <dalaro@hopcount.org> + * @see CassandraThriftStoreManager + */ +public class CassandraThriftKeyColumnValueStore implements KeyColumnValueStore { + + private static final Logger logger = + LoggerFactory.getLogger(CassandraThriftKeyColumnValueStore.class); + + private static final Pattern BROKEN_BYTE_TOKEN_PATTERN = Pattern.compile("^Token\\(bytes\\[(.+)\\]\\)$"); + + // Cassandra access + private final CassandraThriftStoreManager storeManager; + private final String keyspace; + private final String columnFamily; + private final CTConnectionPool pool; + private final ThriftGetter entryGetter; + + public CassandraThriftKeyColumnValueStore(String keyspace, String columnFamily, CassandraThriftStoreManager storeManager, + CTConnectionPool pool) { + this.storeManager = storeManager; + this.keyspace = keyspace; + this.columnFamily = columnFamily; + this.pool = pool; + this.entryGetter = new ThriftGetter(storeManager.getMetaDataSchema(columnFamily)); + } + + /** + * Call Cassandra's Thrift get_slice() method. + *

+ * When columnEnd equals columnStart and either startInclusive + * or endInclusive is false (or both are false), then this + * method returns an empty list without making any Thrift calls. + *

+ * If columnEnd = columnStart + 1, and both startInclusive and + * startExclusive are false, then the arguments effectively form + * an empty interval. In this case, as in the one previous, + * an empty list is returned. However, it may not necessarily + * be handled efficiently; a Thrift call might still be made + * before returning the empty list. + * + * @throws org.janusgraph.diskstorage.BackendException + * when columnEnd < columnStart + */ + @Override + public EntryList getSlice(KeySliceQuery query, StoreTransaction txh) throws BackendException { + Map result = getNamesSlice(query.getKey(), query, txh); + return Iterables.getOnlyElement(result.values(), EntryList.EMPTY_LIST); + } + + @Override + public Map getSlice(List keys, SliceQuery query, StoreTransaction txh) throws BackendException { + return getNamesSlice(keys, query, txh); + } + + public Map getNamesSlice(StaticBuffer key, + SliceQuery query, StoreTransaction txh) throws BackendException { + return getNamesSlice(ImmutableList.of(key),query,txh); + } + + public Map getNamesSlice(List keys, + SliceQuery query, + StoreTransaction txh) throws BackendException { + ColumnParent parent = new ColumnParent(columnFamily); + /* + * Cassandra cannot handle columnStart = columnEnd. + * Cassandra's Thrift getSlice() throws InvalidRequestException + * if columnStart = columnEnd. + */ + if (query.getSliceStart().compareTo(query.getSliceEnd()) >= 0) { + // Check for invalid arguments where columnEnd < columnStart + if (query.getSliceEnd().compareTo(query.getSliceStart())<0) { + throw new PermanentBackendException("columnStart=" + query.getSliceStart() + + " is greater than columnEnd=" + query.getSliceEnd() + ". " + + "columnStart must be less than or equal to columnEnd"); + } + if (0 != query.getSliceStart().length() && 0 != query.getSliceEnd().length()) { + logger.debug("Return empty list due to columnEnd==columnStart and neither empty"); + return KCVSUtil.emptyResults(keys); + } + } + + assert query.getSliceStart().compareTo(query.getSliceEnd()) < 0; + ConsistencyLevel consistency = getTx(txh).getReadConsistencyLevel().getThrift(); + SlicePredicate predicate = new SlicePredicate(); + SliceRange range = new SliceRange(); + range.setCount(query.getLimit() + (query.hasLimit()?1:0)); //Add one for potentially removed last column + range.setStart(query.getSliceStart().asByteBuffer()); + range.setFinish(query.getSliceEnd().asByteBuffer()); + predicate.setSlice_range(range); + + CTConnection conn = null; + try { + conn = pool.borrowObject(keyspace); + Cassandra.Client client = conn.getClient(); + final Map> rows = client.multiget_slice(convert(keys), + parent, + predicate, + consistency); + + /* + * The final size of the "result" List may be at most rows.size(). + * However, "result" could also be up to two elements smaller than + * rows.size(), depending on startInclusive and endInclusive + */ + return rows.entrySet().stream().collect(Collectors.toMap(e -> StaticArrayBuffer.of(e.getKey()), + e -> makeEntryList(e.getValue(), entryGetter, query.getSliceEnd(), query.getLimit()))); + } catch (Exception e) { + throw convertException(e); + } finally { + pool.returnObjectUnsafe(keyspace, conn); + } + } + + private static class ThriftGetter implements StaticArrayEntry.GetColVal { + + private final EntryMetaData[] schema; + + private ThriftGetter(EntryMetaData[] schema) { + this.schema = schema; + } + + @Override + public ByteBuffer getColumn(ColumnOrSuperColumn element) { + return element.getColumn().bufferForName(); + } + + @Override + public ByteBuffer getValue(ColumnOrSuperColumn element) { + return element.getColumn().bufferForValue(); + } + + @Override + public EntryMetaData[] getMetaSchema(ColumnOrSuperColumn element) { + return schema; + } + + @Override + public Object getMetaData(ColumnOrSuperColumn element, EntryMetaData meta) { + switch(meta) { + case TIMESTAMP: + return element.getColumn().getTimestamp(); + case TTL: + return element.getColumn().getTtl(); + default: + throw new UnsupportedOperationException("Unsupported meta data: " + meta); + } + } + } + + @Override + public void close() { + // Do nothing + } + + @Override + public void acquireLock(StaticBuffer key, StaticBuffer column, StaticBuffer expectedValue, + StoreTransaction txh) throws BackendException { + throw new UnsupportedOperationException(); + } + + @Override + public KeyIterator getKeys(@Nullable SliceQuery sliceQuery, StoreTransaction txh) throws BackendException { + final IPartitioner partitioner = storeManager.getCassandraPartitioner(); + + if (!(partitioner instanceof RandomPartitioner) && !(partitioner instanceof Murmur3Partitioner)) + throw new PermanentBackendException("This operation is only allowed when random partitioner (md5 or murmur3) is used."); + + try { + return new AllTokensIterator(partitioner, sliceQuery, storeManager.getPageSize()); + } catch (Exception e) { + throw convertException(e); + } + } + + @Override + public KeyIterator getKeys(KeyRangeQuery keyRangeQuery, StoreTransaction txh) throws BackendException { + final IPartitioner partitioner = storeManager.getCassandraPartitioner(); + + // see rant about the reason of this limitation in Astyanax implementation of this method. + if (!(partitioner instanceof ByteOrderedPartitioner)) + throw new PermanentBackendException("This operation is only allowed when byte-ordered partitioner is used."); + + try { + return new KeyRangeIterator(partitioner, keyRangeQuery, storeManager.getPageSize(), + keyRangeQuery.getKeyStart().asByteBuffer(), + keyRangeQuery.getKeyEnd().asByteBuffer()); + } catch (Exception e) { + throw convertException(e); + } + } + + @Override + public String getName() { + return columnFamily; + } + + @Override + public void mutate(StaticBuffer key, List additions, List deletions, StoreTransaction txh) throws BackendException { + Map mutations = ImmutableMap.of(key, new KCVMutation(additions, deletions)); + mutateMany(mutations, txh); + } + + public void mutateMany(Map mutations, StoreTransaction txh) throws BackendException { + storeManager.mutateMany(ImmutableMap.of(columnFamily, mutations), txh); + } + + static BackendException convertException(Throwable e) { + if(e instanceof TimedOutException || e instanceof UnavailableException) { + return new TemporaryBackendException(e); + } + return new PermanentBackendException(e); + } + + @Override + public String toString() { + return "CassandraThriftKeyColumnValueStore[ks=" + + keyspace + ", cf=" + columnFamily + "]"; + } + + + private List getKeySlice(ByteBuffer startKey, + ByteBuffer endKey, + SliceQuery columnSlice, + int count) throws BackendException { + return getRangeSlices(new org.apache.cassandra.thrift.KeyRange().setStart_key(startKey).setEnd_key(endKey).setCount(count), columnSlice); + } + + private List getTokenSlice(T startToken, T endToken, + SliceQuery sliceQuery, int count) throws BackendException { + + String st = sanitizeBrokenByteToken(startToken); + String et = sanitizeBrokenByteToken(endToken); + + org.apache.cassandra.thrift.KeyRange kr = new org.apache.cassandra.thrift.KeyRange().setStart_token(st).setEnd_token(et).setCount(count); + + return getRangeSlices(kr, sliceQuery); + } + + private String sanitizeBrokenByteToken(Token tok) { + /* + * Background: https://issues.apache.org/jira/browse/CASSANDRA-5566 + * + * This check is useful for compatibility with Cassandra server versions + * 1.2.4 and earlier. + */ + String st = tok.toString(); + if (!(tok instanceof ByteOrderedPartitioner.BytesToken)) + return st; + + // Do a cheap 1-character startsWith before unleashing the regex + if (st.startsWith("T")) { + Matcher m = BROKEN_BYTE_TOKEN_PATTERN.matcher(st); + if (!m.matches()) { + logger.warn("Unknown token string format: \"{}\"", st); + } else { + String old = st; + st = m.group(1); + logger.debug("Rewrote token string: \"{}\" -> \"{}\"", old, st); + } + } + return st; + } + + private List getRangeSlices(org.apache.cassandra.thrift.KeyRange keyRange, @Nullable SliceQuery sliceQuery) throws BackendException { + SliceRange sliceRange = new SliceRange(); + + if (sliceQuery == null) { + sliceRange.setStart(ArrayUtils.EMPTY_BYTE_ARRAY) + .setFinish(ArrayUtils.EMPTY_BYTE_ARRAY) + .setCount(5); + } else { + sliceRange.setStart(sliceQuery.getSliceStart().asByteBuffer()) + .setFinish(sliceQuery.getSliceEnd().asByteBuffer()) + .setCount((sliceQuery.hasLimit()) ? sliceQuery.getLimit() : Integer.MAX_VALUE); + } + + + CTConnection connection = null; + try { + connection = pool.borrowObject(keyspace); + + List slices = + connection.getClient().get_range_slices(new ColumnParent(columnFamily), + new SlicePredicate() + .setSlice_range(sliceRange), + keyRange, + ConsistencyLevel.QUORUM); + + for (KeySlice s : slices) { + logger.debug("Key {}", ByteBufferUtil.toString(s.key, "-")); + } + + /* Note: we need to fetch columns for each row as well to remove "range ghosts" */ + List result = new ArrayList<>(slices.size()); + KeyIterationPredicate predicate = new KeyIterationPredicate(); + for (KeySlice ks : slices) + if (predicate.apply(ks)) + result.add(ks); + return result; + } catch (Exception e) { + throw convertException(e); + } finally { + if (connection != null) + pool.returnObjectUnsafe(keyspace, connection); + } + } + + private static class KeyIterationPredicate implements Predicate { + + @Override + public boolean apply(@Nullable KeySlice row) { + return (row != null) && row.getColumns().size() > 0; + } + } + + /** + * Slices rows and columns using tokens. Recall that the partitioner turns + * keys into tokens. For instance, under RandomPartitioner, tokens are the + * MD5 hashes of keys. + */ + public class AbstractBufferedRowIter implements KeyIterator { + + private final int pageSize; + private final SliceQuery columnSlice; + + private boolean isClosed; + private boolean seenEnd; + protected Iterator keySliceIterator; + private KeySlice mostRecentRow; + + private final IPartitioner partitioner; + private Token nextStartToken; + private final Token endToken; + private ByteBuffer nextStartKey; + + private final boolean omitEndToken; + + public AbstractBufferedRowIter(IPartitioner partitioner, + SliceQuery columnSlice, int pageSize, Token startToken, Token endToken, boolean omitEndToken) { + this.pageSize = pageSize; + this.partitioner = partitioner; + this.nextStartToken = startToken; + this.endToken = endToken; + this.columnSlice = columnSlice; + + this.seenEnd = false; + this.isClosed = false; + this.keySliceIterator = Collections.emptyIterator(); + this.mostRecentRow = null; + this.omitEndToken = omitEndToken; + } + + @Override + public boolean hasNext() { + ensureOpen(); + + if (!keySliceIterator.hasNext() && !seenEnd) { + try { + keySliceIterator = bufferAnew().iterator(); + } catch (BackendException e) { + throw new RuntimeException(e); + } + } + + return keySliceIterator.hasNext(); + } + + @Override + public StaticBuffer next() { + ensureOpen(); + + if (!hasNext()) + throw new NoSuchElementException(); + + mostRecentRow = keySliceIterator.next(); + + Preconditions.checkNotNull(mostRecentRow); + return StaticArrayBuffer.of(mostRecentRow.bufferForKey()); + } + + @Override + public void close() { + closeIterator(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public RecordIterator getEntries() { + ensureOpen(); + + return new RecordIterator() { + final Iterator columns = makeEntryIterator(mostRecentRow.getColumns(), + entryGetter, columnSlice.getSliceEnd(), columnSlice.getLimit()); + + @Override + public boolean hasNext() { + ensureOpen(); + return columns.hasNext(); + } + + @Override + public Entry next() { + ensureOpen(); + return columns.next(); + } + + @Override + public void close() { + closeIterator(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + private void ensureOpen() { + if (isClosed) + throw new IllegalStateException("Iterator has been closed."); + } + + private void closeIterator() { + if (!isClosed) { + isClosed = true; + } + } + + private List bufferAnew() throws BackendException { + + Preconditions.checkArgument(!seenEnd); + + return checkFreshSlices(getNextKeySlices()); + } + + protected List checkFreshSlices(List ks) { + + if (0 == ks.size()) { + seenEnd = true; + return Collections.emptyList(); + } + + nextStartKey = ks.get(ks.size() - 1).bufferForKey(); + nextStartToken = partitioner.getToken(nextStartKey); + + if (nextStartToken.equals(endToken)) { + seenEnd = true; + if (omitEndToken) + ks.remove(ks.size() - 1); + } + + return ks; + } + + protected final List getNextKeySlices() throws BackendException { + return getTokenSlice(nextStartToken, endToken, columnSlice, pageSize); + } + } + + private final class AllTokensIterator extends AbstractBufferedRowIter { + public AllTokensIterator(IPartitioner partitioner, SliceQuery columnSlice, int pageSize) { + super(partitioner, columnSlice, pageSize, partitioner.getMinimumToken(), partitioner.getMinimumToken(), false); + } + } + + private final class KeyRangeIterator extends AbstractBufferedRowIter { + public KeyRangeIterator(IPartitioner partitioner, SliceQuery columnSlice, + int pageSize, ByteBuffer startKey, ByteBuffer endKey) throws BackendException { + super(partitioner, columnSlice, pageSize, partitioner.getToken(startKey), partitioner.getToken(endKey), true); + + Preconditions.checkArgument(partitioner instanceof ByteOrderedPartitioner); + + // Get first slice with key range instead of token range. Token + // ranges are start-exclusive, key ranges are start-inclusive. Both + // are end-inclusive. If we don't make the call below, then we will + // erroneously miss startKey. + List ks = getKeySlice(startKey, endKey, columnSlice, pageSize); + + this.keySliceIterator = checkFreshSlices(ks).iterator(); + } + } +} diff --git a/src/main/java/org/janusgraph/diskstorage/cassandra/thrift/CassandraThriftStoreManager.java b/src/main/java/org/janusgraph/diskstorage/cassandra/thrift/CassandraThriftStoreManager.java new file mode 100644 index 0000000..0cd9a85 --- /dev/null +++ b/src/main/java/org/janusgraph/diskstorage/cassandra/thrift/CassandraThriftStoreManager.java @@ -0,0 +1,654 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.thrift; + +import static org.janusgraph.diskstorage.cassandra.CassandraTransaction.getTx; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import org.janusgraph.diskstorage.*; +import org.janusgraph.diskstorage.cassandra.utils.CassandraHelper; +import org.janusgraph.diskstorage.configuration.ConfigElement; +import org.janusgraph.diskstorage.configuration.ConfigNamespace; +import org.janusgraph.diskstorage.configuration.ConfigOption; +import org.janusgraph.diskstorage.configuration.Configuration; +import org.janusgraph.diskstorage.keycolumnvalue.KeyRange; +import org.janusgraph.graphdb.configuration.PreInitializeConfigOptions; +import org.janusgraph.util.system.NetworkUtil; + +import org.apache.cassandra.dht.ByteOrderedPartitioner; +import org.apache.cassandra.dht.IPartitioner; +import org.apache.cassandra.dht.Token; +import org.apache.cassandra.thrift.*; +import org.apache.cassandra.utils.FBUtilities; +import org.apache.commons.pool.impl.GenericKeyedObjectPool; +import org.apache.thrift.TException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreManager; +import org.janusgraph.diskstorage.cassandra.thrift.thriftpool.CTConnection; +import org.janusgraph.diskstorage.cassandra.thrift.thriftpool.CTConnectionFactory; +import org.janusgraph.diskstorage.cassandra.thrift.thriftpool.CTConnectionPool; +import org.janusgraph.diskstorage.keycolumnvalue.KCVMutation; +import org.janusgraph.diskstorage.keycolumnvalue.StoreTransaction; +import org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration; + +import static org.janusgraph.diskstorage.configuration.ConfigOption.disallowEmpty; + +/** + * This class creates @see CassandraThriftKeyColumnValueStore and + * handles Cassandra-backed allocation of vertex IDs for JanusGraph (when so + * configured). + * + * @author Dan LaRocque <dalaro@hopcount.org> + */ +@PreInitializeConfigOptions +public class CassandraThriftStoreManager extends AbstractCassandraStoreManager { + + public enum PoolExhaustedAction { + BLOCK(GenericKeyedObjectPool.WHEN_EXHAUSTED_BLOCK), + FAIL(GenericKeyedObjectPool.WHEN_EXHAUSTED_FAIL), + GROW(GenericKeyedObjectPool.WHEN_EXHAUSTED_GROW); + + private final byte b; + + PoolExhaustedAction(byte b) { + this.b = b; + } + + public byte getByte() { + return b; + } + } + + private static final Logger log = LoggerFactory.getLogger(CassandraThriftStoreManager.class); + + public static final ConfigNamespace THRIFT_NS = + new ConfigNamespace(AbstractCassandraStoreManager.CASSANDRA_NS, "thrift", + "Options for JanusGraph's own Thrift Cassandra backend"); + + public static final ConfigNamespace CPOOL_NS = + new ConfigNamespace(THRIFT_NS, "cpool", "Options for the Apache commons-pool connection manager"); + + public static final ConfigOption CPOOL_WHEN_EXHAUSTED = + new ConfigOption<>(CPOOL_NS, "when-exhausted", + "What to do when clients concurrently request more active connections than are allowed " + + "by the pool. The value must be one of BLOCK, FAIL, or GROW.", + ConfigOption.Type.MASKABLE, String.class, PoolExhaustedAction.BLOCK.toString(), + disallowEmpty(String.class)); + + public static final ConfigOption CPOOL_MAX_TOTAL = + new ConfigOption<>(CPOOL_NS, "max-total", + "Max number of allowed Thrift connections, idle or active (-1 to leave undefined)", + ConfigOption.Type.MASKABLE, -1); + + public static final ConfigOption CPOOL_MAX_ACTIVE = + new ConfigOption<>(CPOOL_NS, "max-active", + "Maximum number of concurrently in-use connections (-1 to leave undefined)", + ConfigOption.Type.MASKABLE, 16); + + public static final ConfigOption CPOOL_MAX_IDLE = + new ConfigOption<>(CPOOL_NS, "max-idle", + "Maximum number of concurrently idle connections (-1 to leave undefined)", + ConfigOption.Type.MASKABLE, 4); + + public static final ConfigOption CPOOL_MIN_IDLE = + new ConfigOption<>(CPOOL_NS, "min-idle", + "Minimum number of idle connections the pool attempts to maintain", + ConfigOption.Type.MASKABLE, 0); + + // Wart: allowing -1 like commons-pool's convention precludes using StandardDuration + public static final ConfigOption CPOOL_MAX_WAIT = + new ConfigOption<>(CPOOL_NS, "max-wait", + "Maximum number of milliseconds to block when " + ConfigElement.getPath(CPOOL_WHEN_EXHAUSTED) + + " is set to BLOCK. Has no effect when set to actions besides BLOCK. Set to -1 to wait indefinitely.", + ConfigOption.Type.MASKABLE, -1L); + + // Wart: allowing -1 like commons-pool's convention precludes using StandardDuration + public static final ConfigOption CPOOL_EVICTOR_PERIOD = + new ConfigOption<>(CPOOL_NS, "evictor-period", + "Approximate number of milliseconds between runs of the idle connection evictor. " + + "Set to -1 to never run the idle connection evictor.", + ConfigOption.Type.MASKABLE, 30L * 1000L); + + // Wart: allowing -1 like commons-pool's convention precludes using StandardDuration + public static final ConfigOption CPOOL_MIN_EVICTABLE_IDLE_TIME = + new ConfigOption<>(CPOOL_NS, "min-evictable-idle-time", + "Minimum number of milliseconds a connection must be idle before it is eligible for " + + "eviction. See also " + ConfigElement.getPath(CPOOL_EVICTOR_PERIOD) + ". Set to -1 to never evict " + + "idle connections.", ConfigOption.Type.MASKABLE, 60L * 1000L); + + public static final ConfigOption CPOOL_IDLE_TESTS = + new ConfigOption<>(CPOOL_NS, "idle-test", + "Whether the idle connection evictor validates idle connections and drops those that fail to validate", + ConfigOption.Type.MASKABLE, false); + + public static final ConfigOption CPOOL_IDLE_TESTS_PER_EVICTION_RUN = + new ConfigOption<>(CPOOL_NS, "idle-tests-per-eviction-run", + "When the value is negative, e.g. -n, roughly one nth of the idle connections are tested per run. " + + "When the value is positive, e.g. n, the min(idle-count, n) connections are tested per run.", + ConfigOption.Type.MASKABLE, 0); + + + private final Map openStores; + private final CTConnectionPool pool; + private final Deployment deployment; + + public CassandraThriftStoreManager(Configuration config) throws BackendException { + super(config); + + /* + * This is eventually passed to Thrift's TSocket constructor. The + * constructor parameter is of type int. + */ + int thriftTimeoutMS = (int)config.get(GraphDatabaseConfiguration.CONNECTION_TIMEOUT).toMillis(); + + CTConnectionFactory.Config factoryConfig = new CTConnectionFactory.Config(hostnames, port, username, password) + .setTimeoutMS(thriftTimeoutMS) + .setFrameSize(thriftFrameSizeBytes); + + if (config.get(SSL_ENABLED)) { + factoryConfig.setSSLTruststoreLocation(config.get(SSL_TRUSTSTORE_LOCATION)); + factoryConfig.setSSLTruststorePassword(config.get(SSL_TRUSTSTORE_PASSWORD)); + } + + final PoolExhaustedAction poolExhaustedAction = ConfigOption.getEnumValue( + config.get(CPOOL_WHEN_EXHAUSTED), PoolExhaustedAction.class); + + CTConnectionPool p = new CTConnectionPool(factoryConfig.build()); + p.setTestOnBorrow(true); + p.setTestOnReturn(true); + p.setTestWhileIdle(config.get(CPOOL_IDLE_TESTS)); + p.setNumTestsPerEvictionRun(config.get(CPOOL_IDLE_TESTS_PER_EVICTION_RUN)); + p.setWhenExhaustedAction(poolExhaustedAction.getByte()); + p.setMaxActive(config.get(CPOOL_MAX_ACTIVE)); + p.setMaxTotal(config.get(CPOOL_MAX_TOTAL)); // maxTotal limits active + idle + p.setMaxIdle(config.get(CPOOL_MAX_IDLE)); + p.setMinIdle(config.get(CPOOL_MIN_IDLE)); + p.setMaxWait(config.get(CPOOL_MAX_WAIT)); + p.setTimeBetweenEvictionRunsMillis(config.get(CPOOL_EVICTOR_PERIOD)); + p.setMinEvictableIdleTimeMillis(config.get(CPOOL_MIN_EVICTABLE_IDLE_TIME)); + this.pool = p; + + this.openStores = new HashMap<>(); + + // Only watch the ring and change endpoints with BOP + if (getCassandraPartitioner() instanceof ByteOrderedPartitioner) { + deployment = (hostnames.length == 1)// mark deployment as local only in case we have byte ordered partitioner and local connection + ? (NetworkUtil.isLocalConnection(hostnames[0])) ? Deployment.LOCAL : Deployment.REMOTE + : Deployment.REMOTE; + } else { + deployment = Deployment.REMOTE; + } + } + + @Override + public Deployment getDeployment() { + return deployment; + } + + @Override + public IPartitioner getCassandraPartitioner() throws BackendException { + CTConnection conn = null; + try { + conn = pool.borrowObject(SYSTEM_KS); + return FBUtilities.newPartitioner(conn.getClient().describe_partitioner()); + } catch (Exception e) { + throw new TemporaryBackendException(e); + } finally { + pool.returnObjectUnsafe(SYSTEM_KS, conn); + } + } + + @Override + public String toString() { + return "thriftCassandra" + super.toString(); + } + + @Override + public void close() throws BackendException { + openStores.clear(); + closePool(); + } + + @Override + public void mutateMany(Map> mutations, StoreTransaction txh) throws BackendException { + Preconditions.checkNotNull(mutations); + + final MaskedTimestamp commitTime = new MaskedTimestamp(txh); + + ConsistencyLevel consistency = getTx(txh).getWriteConsistencyLevel().getThrift(); + + // Generate Thrift-compatible batch_mutate() datastructure + // key -> cf -> cassmutation + int size = 0; + for (final Map mutation : mutations.values()) { + size += mutation.size(); + } + final Map>> batch = new HashMap<>(size); + + + for (final Map.Entry> keyMutation : mutations.entrySet()) { + final String columnFamily = keyMutation.getKey(); + for (final Map.Entry mutEntry : keyMutation.getValue().entrySet()) { + ByteBuffer keyBB = mutEntry.getKey().asByteBuffer(); + + // Get or create the single Cassandra Mutation object responsible for this key + // Most mutations only modify the edgeStore and indexStore + final Map> cfmutation + = batch.computeIfAbsent(keyBB, k -> new HashMap<>(3)); + + final KCVMutation mutation = mutEntry.getValue(); + final List thriftMutation = new ArrayList<>(mutations.size()); + + if (mutation.hasDeletions()) { + for (final StaticBuffer buf : mutation.getDeletions()) { + final Deletion d = new Deletion(); + final SlicePredicate sp = new SlicePredicate(); + sp.addToColumn_names(buf.as(StaticBuffer.BB_FACTORY)); + d.setPredicate(sp); + d.setTimestamp(commitTime.getDeletionTime(times)); + org.apache.cassandra.thrift.Mutation m = new org.apache.cassandra.thrift.Mutation(); + m.setDeletion(d); + thriftMutation.add(m); + } + } + + if (mutation.hasAdditions()) { + for (final Entry ent : mutation.getAdditions()) { + final ColumnOrSuperColumn columnOrSuperColumn = new ColumnOrSuperColumn(); + final Column column = new Column(ent.getColumnAs(StaticBuffer.BB_FACTORY)); + column.setValue(ent.getValueAs(StaticBuffer.BB_FACTORY)); + + column.setTimestamp(commitTime.getAdditionTime(times)); + + final Integer ttl = (Integer) ent.getMetaData().get(EntryMetaData.TTL); + if (null != ttl && ttl > 0) { + column.setTtl(ttl); + } + + columnOrSuperColumn.setColumn(column); + org.apache.cassandra.thrift.Mutation m = new org.apache.cassandra.thrift.Mutation(); + m.setColumn_or_supercolumn(columnOrSuperColumn); + thriftMutation.add(m); + } + } + + cfmutation.put(columnFamily, thriftMutation); + } + } + + CTConnection conn = null; + try { + conn = pool.borrowObject(keySpaceName); + Cassandra.Client client = conn.getClient(); + if (atomicBatch) { + client.atomic_batch_mutate(batch, consistency); + } else { + client.batch_mutate(batch, consistency); + } + } catch (Exception ex) { + throw CassandraThriftKeyColumnValueStore.convertException(ex); + } finally { + pool.returnObjectUnsafe(keySpaceName, conn); + } + + sleepAfterWrite(txh, commitTime); + } + + @Override // TODO: *BIG FAT WARNING* 'synchronized is always *bad*, change openStores to use ConcurrentLinkedHashMap + public synchronized CassandraThriftKeyColumnValueStore openDatabase(final String name, StoreMetaData.Container metaData) throws BackendException { + if (openStores.containsKey(name)) + return openStores.get(name); + + ensureColumnFamilyExists(keySpaceName, name); + + CassandraThriftKeyColumnValueStore store = new CassandraThriftKeyColumnValueStore(keySpaceName, name, this, pool); + openStores.put(name, store); + return store; + } + + @Override + public List getLocalKeyPartition() throws BackendException { + CTConnection conn = null; + IPartitioner partitioner = getCassandraPartitioner(); + + if (!(partitioner instanceof ByteOrderedPartitioner)) + throw new UnsupportedOperationException("getLocalKeyPartition() only supported by byte ordered partitioner."); + + Token.TokenFactory tokenFactory = partitioner.getTokenFactory(); + + try { + // Resist the temptation to describe SYSTEM_KS. It has no ring. + // Instead, we'll create our own keyspace (or check that it exists), then describe it. + ensureKeyspaceExists(keySpaceName); + + conn = pool.borrowObject(keySpaceName); + final List ranges = conn.getClient().describe_ring(keySpaceName); + final List keyRanges = new ArrayList<>(ranges.size()); + + for (TokenRange range : ranges) { + if (!NetworkUtil.hasLocalAddress(range.endpoints)) + continue; + + keyRanges.add(CassandraHelper.transformRange(tokenFactory.fromString(range.start_token), tokenFactory.fromString(range.end_token))); + } + + return keyRanges; + } catch (Exception e) { + throw CassandraThriftKeyColumnValueStore.convertException(e); + } finally { + pool.returnObjectUnsafe(keySpaceName, conn); + } + } + + /** + * Connect to Cassandra via Thrift on the specified host and port and attempt to truncate the named keyspace. + *

+ * This is a utility method intended mainly for testing. It is + * equivalent to issuing 'truncate <cf>' for each of the column families in keyspace using + * the cassandra-cli tool. + *

+ * Using truncate is better for a number of reasons, most significantly because it doesn't + * involve any schema modifications which can take time to propagate across the cluster such + * leaves nodes in the inconsistent state and could result in read/write failures. + * Any schema modifications are discouraged until there is no traffic to Keyspace or ColumnFamilies. + * + * @throws org.janusgraph.diskstorage.BackendException if any checked Thrift or UnknownHostException is thrown in the body of this method + */ + public void clearStorage() throws BackendException { + openStores.clear(); + final String lp = "ClearStorage: "; // "log prefix" + /* + * log4j is capable of automatically writing the name of a method that + * generated a log message, but the docs warn that "generating caller + * location information is extremely slow and should be avoided unless + * execution speed is not an issue." + */ + + CTConnection conn = null; + try { + conn = pool.borrowObject(SYSTEM_KS); + Cassandra.Client client = conn.getClient(); + + KsDef ksDef; + try { + client.set_keyspace(keySpaceName); + ksDef = client.describe_keyspace(keySpaceName); + } catch (NotFoundException e) { + log.debug(lp + "Keyspace {} does not exist, not attempting to truncate.", keySpaceName); + return; + } catch (InvalidRequestException e) { + log.debug(lp + "InvalidRequestException when attempting to describe keyspace {}, not attempting to truncate.", keySpaceName); + return; + } + + + if (null == ksDef) { + log.debug(lp + "Received null KsDef for keyspace {}; not truncating its CFs", keySpaceName); + return; + } + + if (this.storageConfig.get(GraphDatabaseConfiguration.DROP_ON_CLEAR)) { + client.system_drop_keyspace(keySpaceName); + pool.clear(); + } else { + final List columnFamilyDefinitions = ksDef.getCf_defs(); + + if (null == columnFamilyDefinitions) { + log.debug(lp + "Received empty CfDef list for keyspace {}; not truncating CFs", keySpaceName); + return; + } + + for (final CfDef cfDef : ksDef.getCf_defs()) { + client.truncate(cfDef.name); + log.info(lp + "Truncated CF {} in keyspace {}", cfDef.name, keySpaceName); + } + + /* + * Clearing the CTConnectionPool is unnecessary. This method + * removes no keyspaces. All open Cassandra connections will + * remain valid. + */ + } + } catch (Exception e) { + throw new TemporaryBackendException(e); + } finally { + if (conn != null && conn.getClient() != null) { + try { + conn.getClient().set_keyspace(SYSTEM_KS); + } catch (TException e) { + log.warn("Failed to reset keyspace", e); + } + } + pool.returnObjectUnsafe(SYSTEM_KS, conn); + } + } + + @Override + public boolean exists() throws BackendException { + CTConnection connection = null; + + try { + connection = pool.borrowObject(SYSTEM_KS); + final Cassandra.Client client = connection.getClient(); + + try { + // Side effect: throws Exception if keyspaceName doesn't exist + client.set_keyspace(keySpaceName); // Don't remove + client.set_keyspace(SYSTEM_KS); + return true; + } catch (InvalidRequestException e) { + return false; + } + } catch (Exception e) { + throw new TemporaryBackendException(e); + } finally { + pool.returnObjectUnsafe(SYSTEM_KS, connection); + } + } + + private KsDef ensureKeyspaceExists(String keyspaceName) throws BackendException { + CTConnection connection = null; + + try { + connection = pool.borrowObject(SYSTEM_KS); + Cassandra.Client client = connection.getClient(); + + try { + // Side effect: throws Exception if keyspaceName doesn't exist + client.set_keyspace(keyspaceName); // Don't remove + client.set_keyspace(SYSTEM_KS); + log.debug("Found existing keyspace {}", keyspaceName); + } catch (InvalidRequestException e) { + // Keyspace didn't exist; create it + log.debug("Creating keyspace {}...", keyspaceName); + + final KsDef ksdef = new KsDef().setName(keyspaceName) + .setCf_defs(new LinkedList<>()) // cannot be null but can be empty + .setStrategy_class(storageConfig.get(REPLICATION_STRATEGY)) + .setStrategy_options(strategyOptions); + + client.set_keyspace(SYSTEM_KS); + try { + client.system_add_keyspace(ksdef); + retrySetKeyspace(keyspaceName, client); + log.debug("Created keyspace {}", keyspaceName); + } catch (InvalidRequestException ire) { + log.error("system_add_keyspace failed for keyspace=" + keyspaceName, ire); + throw ire; + } + + } + + return client.describe_keyspace(keyspaceName); + } catch (Exception e) { + throw new TemporaryBackendException(e); + } finally { + pool.returnObjectUnsafe(SYSTEM_KS, connection); + } + } + + private void retrySetKeyspace(String ksName, Cassandra.Client client) throws BackendException { + final long end = System.currentTimeMillis() + (60L * 1000L); + + while (System.currentTimeMillis() <= end) { + try { + client.set_keyspace(ksName); + return; + } catch (Exception e) { + log.warn("Exception when changing to keyspace {} after creating it", ksName, e); + try { + Thread.sleep(1000L); + } catch (InterruptedException ie) { + throw new PermanentBackendException("Unexpected interrupt (shutting down?)", ie); + } + } + } + + throw new PermanentBackendException("Could change to keyspace " + ksName + " after creating it"); + } + + private void ensureColumnFamilyExists(String ksName, String cfName) throws BackendException { + CTConnection conn = null; + try { + KsDef keyspaceDef = ensureKeyspaceExists(ksName); + + conn = pool.borrowObject(ksName); + Cassandra.Client client = conn.getClient(); + + log.debug("Looking up metadata on keyspace {}...", ksName); + + boolean foundColumnFamily = false; + for (CfDef cfDef : keyspaceDef.getCf_defs()) { + String curCfName = cfDef.getName(); + if (curCfName.equals(cfName)) + foundColumnFamily = true; + } + + if (!foundColumnFamily) { + createColumnFamily(client, ksName, cfName, "org.apache.cassandra.db.marshal.BytesType"); + } else { + log.debug("Keyspace {} and ColumnFamily {} were found.", ksName, cfName); + } + } catch (SchemaDisagreementException e) { + throw new TemporaryBackendException(e); + } catch (Exception e) { + throw new PermanentBackendException(e); + } finally { + pool.returnObjectUnsafe(ksName, conn); + } + } + + private void createColumnFamily(Cassandra.Client client, + String ksName, + String cfName, + String comparator) throws BackendException { + + CfDef createColumnFamily = new CfDef(); + createColumnFamily.setName(cfName); + createColumnFamily.setKeyspace(ksName); + createColumnFamily.setComparator_type(comparator); + if (storageConfig.has(COMPACTION_STRATEGY)) { + createColumnFamily.setCompaction_strategy(storageConfig.get(COMPACTION_STRATEGY)); + } + if (!compactionOptions.isEmpty()) { + createColumnFamily.setCompaction_strategy_options(compactionOptions); + } + final ImmutableMap.Builder compressionOptions = new ImmutableMap.Builder<>(); + + if (compressionEnabled) { + compressionOptions.put("sstable_compression", compressionClass) + .put("chunk_length_kb", Integer.toString(compressionChunkSizeKB)); + } + + createColumnFamily.setCompression_options(compressionOptions.build()); + + // Hard-coded caching settings + if (cfName.startsWith(Backend.EDGESTORE_NAME)) { + createColumnFamily.setCaching("keys_only"); + } else if (cfName.startsWith(Backend.INDEXSTORE_NAME)) { + createColumnFamily.setCaching("rows_only"); + } + + log.debug("Adding column family {} to keyspace {}...", cfName, ksName); + try { + client.system_add_column_family(createColumnFamily); + } catch (SchemaDisagreementException e) { + throw new TemporaryBackendException("Error in setting up column family", e); + } catch (Exception e) { + throw new PermanentBackendException(e); + } + + log.debug("Added column family {} to keyspace {}.", cfName, ksName); + } + + @Override + public Map getCompressionOptions(String cf) throws BackendException { + CTConnection conn = null; + Map result = null; + + try { + conn = pool.borrowObject(keySpaceName); + Cassandra.Client client = conn.getClient(); + + KsDef ksDef = client.describe_keyspace(keySpaceName); + + for (CfDef cfDef : ksDef.getCf_defs()) { + if (null != cfDef && cfDef.getName().equals(cf)) { + result = cfDef.getCompression_options(); + break; + } + } + + return result; + } catch (InvalidRequestException e) { + log.debug("Keyspace {} does not exist", keySpaceName); + return null; + } catch (Exception e) { + throw new TemporaryBackendException(e); + } finally { + pool.returnObjectUnsafe(keySpaceName, conn); + } + } + + private void closePool() { + /* + * pool.close() does not affect borrowed connections. + * + * Connections currently borrowed by some thread which are + * talking to the old host will eventually be destroyed by + * CTConnectionFactory#validateObject() returning false when + * those connections are returned to the pool. + */ + try { + pool.close(); + log.info("Closed Thrift connection pooler."); + } catch (Exception e) { + log.warn("Failed to close connection pooler. " + + "We might be leaking Cassandra connections.", e); + // There's still hope: CTConnectionFactory#validateObject() + // will be called on borrow() and might tear down the + // connections that close() failed to tear down + } + } +} \ No newline at end of file diff --git a/src/main/java/org/janusgraph/diskstorage/cassandra/thrift/thriftpool/CTConnection.java b/src/main/java/org/janusgraph/diskstorage/cassandra/thrift/thriftpool/CTConnection.java new file mode 100644 index 0000000..5c03cec --- /dev/null +++ b/src/main/java/org/janusgraph/diskstorage/cassandra/thrift/thriftpool/CTConnection.java @@ -0,0 +1,69 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.thrift.thriftpool; + +import org.apache.cassandra.thrift.Cassandra; +import org.apache.cassandra.thrift.Cassandra.Client; +import org.apache.thrift.transport.TTransport; + +import java.io.Closeable; + +/** + * Wraps a {@code Cassandra.Client} instance, its underlying {@code TTransport} + * instance, and the {@link org.janusgraph.diskstorage.cassandra.thrift.thriftpool.CTConnectionFactory.Config} instance used to setup + * the connection. + * + * @see CTConnectionFactory + * @see CTConnectionPool + * @author Dan LaRocque <dalaro@hopcount.org> + */ +public class CTConnection implements Closeable { + + private final TTransport transport; + private final Cassandra.Client client; + private final CTConnectionFactory.Config cfg; + + public CTConnection(TTransport transport, Client client, CTConnectionFactory.Config cfg) { + this.transport = transport; + this.client = client; + this.cfg = cfg; + } + + public TTransport getTransport() { + return transport; + } + + public Cassandra.Client getClient() { + return client; + } + + public CTConnectionFactory.Config getConfig() { + return cfg; + } + + public boolean isOpen() { + return transport.isOpen(); + } + @Override + public void close() { + if (transport != null && transport.isOpen()) + transport.close(); + } + + @Override + public String toString() { + return "CTConnection [transport=" + transport + ", client=" + client + ", cfg=" + cfg + "]"; + } +} diff --git a/src/main/java/org/janusgraph/diskstorage/cassandra/thrift/thriftpool/CTConnectionFactory.java b/src/main/java/org/janusgraph/diskstorage/cassandra/thrift/thriftpool/CTConnectionFactory.java new file mode 100644 index 0000000..735e1c2 --- /dev/null +++ b/src/main/java/org/janusgraph/diskstorage/cassandra/thrift/thriftpool/CTConnectionFactory.java @@ -0,0 +1,239 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.thrift.thriftpool; + +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.cassandra.auth.PasswordAuthenticator; +import org.apache.cassandra.thrift.AuthenticationRequest; +import org.apache.cassandra.thrift.Cassandra; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.pool.KeyedPoolableObjectFactory; +import org.apache.thrift.TException; +import org.apache.thrift.protocol.TBinaryProtocol; +import org.apache.thrift.transport.TFramedTransport; +import org.apache.thrift.transport.TSSLTransportFactory; +import org.apache.thrift.transport.TSocket; +import org.apache.thrift.transport.TTransport; +import org.apache.thrift.transport.TTransportException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A factory compatible with Apache commons-pool for Cassandra Thrift API + * connections. + * + * @author Dan LaRocque <dalaro@hopcount.org> + */ +public class CTConnectionFactory implements KeyedPoolableObjectFactory { + + private static final Logger log = LoggerFactory.getLogger(CTConnectionFactory.class); + private static final long SCHEMA_WAIT_MAX = 5000L; + private static final long SCHEMA_WAIT_INCREMENT = 25L; + + private final AtomicReference cfgRef; + + private CTConnectionFactory(Config config) { + this.cfgRef = new AtomicReference<>(config); + } + + @Override + public void activateObject(String key, CTConnection c) throws Exception { + // Do nothing, as in passivateObject + } + + @Override + public void destroyObject(String key, CTConnection c) throws Exception { + TTransport t = c.getTransport(); + + if (t.isOpen()) { + t.close(); + log.trace("Closed transport {}", t); + } else { + log.trace("Not closing transport {} (already closed)", t); + } + } + + @Override + public CTConnection makeObject(String key) throws Exception { + CTConnection conn = makeRawConnection(); + Cassandra.Client client = conn.getClient(); + client.set_keyspace(key); + + return conn; + } + + /** + * Create a Cassandra-Thrift connection, but do not attempt to + * set a keyspace on the connection. + * + * @return A CTConnection ready to talk to a Cassandra cluster + * @throws TTransportException on any Thrift transport failure + */ + public CTConnection makeRawConnection() throws TTransportException { + final Config cfg = cfgRef.get(); + + String hostname = cfg.getRandomHost(); + + log.debug("Creating TSocket({}, {}, {}, {}, {})", hostname, cfg.port, cfg.username, cfg.password, cfg.timeoutMS); + + TSocket socket; + if (null != cfg.sslTruststoreLocation && !cfg.sslTruststoreLocation.isEmpty()) { + TSSLTransportFactory.TSSLTransportParameters params = new TSSLTransportFactory.TSSLTransportParameters() {{ + setTrustStore(cfg.sslTruststoreLocation, cfg.sslTruststorePassword); + }}; + socket = TSSLTransportFactory.getClientSocket(hostname, cfg.port, cfg.timeoutMS, params); + } else { + socket = new TSocket(hostname, cfg.port, cfg.timeoutMS); + } + + TTransport transport = new TFramedTransport(socket, cfg.frameSize); + log.trace("Created transport {}", transport); + TBinaryProtocol protocol = new TBinaryProtocol(transport); + Cassandra.Client client = new Cassandra.Client(protocol); + if (!transport.isOpen()) { + transport.open(); + } + + if (cfg.username != null) { + Map credentials = new HashMap() {{ + put(PasswordAuthenticator.USERNAME_KEY, cfg.username); + put(PasswordAuthenticator.PASSWORD_KEY, cfg.password); + }}; + + try { + client.login(new AuthenticationRequest(credentials)); + } catch (Exception e) { // TTransportException will propagate authentication/authorization failure + throw new TTransportException(e); + } + } + return new CTConnection(transport, client, cfg); + } + + @Override + public void passivateObject(String key, CTConnection o) throws Exception { + // Do nothing, as in activateObject + } + + @Override + public boolean validateObject(String key, CTConnection c) { + Config curCfg = cfgRef.get(); + + boolean isSameConfig = c.getConfig().equals(curCfg); + if (log.isDebugEnabled()) { + if (isSameConfig) { + log.trace("Validated {} by configuration {}", c, curCfg); + } else { + log.trace("Rejected {}; current config is {}; rejected connection config is {}", + c, curCfg, c.getConfig()); + } + } + + boolean isOpen = c.isOpen(); + try { + c.getClient().describe_version(); + } catch (TException e) { + isOpen = false; + } + + return isSameConfig && isOpen; + } + + public static class Config { + + private final String[] hostnames; + private final int port; + private final String username; + private final String password; + private final Random random; + + private int timeoutMS; + private int frameSize; + + private String sslTruststoreLocation; + private String sslTruststorePassword; + + private boolean isBuilt; + + public Config(String[] hostnames, int port, String username, String password) { + this.hostnames = hostnames; + this.port = port; + this.username = username; + this.password = password; + this.random = new Random(); + } + + // TODO: we don't really need getters/setters here as all of the fields are final and immutable + + public String getHostname() { + return hostnames[0]; + } + + public int getPort() { + return port; + } + + public String getRandomHost() { + return hostnames.length == 1 ? hostnames[0] : hostnames[random.nextInt(hostnames.length)]; + } + + public Config setTimeoutMS(int timeoutMS) { + checkIfAlreadyBuilt(); + this.timeoutMS = timeoutMS; + return this; + } + + public Config setFrameSize(int frameSize) { + checkIfAlreadyBuilt(); + this.frameSize = frameSize; + return this; + } + + public Config setSSLTruststoreLocation(String location) { + checkIfAlreadyBuilt(); + this.sslTruststoreLocation = location; + return this; + } + + public Config setSSLTruststorePassword(String password) { + checkIfAlreadyBuilt(); + this.sslTruststorePassword = password; + return this; + } + + public CTConnectionFactory build() { + isBuilt = true; + return new CTConnectionFactory(this); + } + + + public void checkIfAlreadyBuilt() { + if (isBuilt) + throw new IllegalStateException("Can't accept modifications when used with built factory."); + } + + @Override + public String toString() { + return "Config[hostnames=" + StringUtils.join(hostnames, ',') + ", port=" + port + + ", timeoutMS=" + timeoutMS + ", frameSize=" + frameSize + + "]"; + } + } + +} + diff --git a/src/main/java/org/janusgraph/diskstorage/cassandra/thrift/thriftpool/CTConnectionPool.java b/src/main/java/org/janusgraph/diskstorage/cassandra/thrift/thriftpool/CTConnectionPool.java new file mode 100644 index 0000000..50e128f --- /dev/null +++ b/src/main/java/org/janusgraph/diskstorage/cassandra/thrift/thriftpool/CTConnectionPool.java @@ -0,0 +1,72 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.thrift.thriftpool; + +import org.apache.commons.pool.KeyedPoolableObjectFactory; +import org.apache.commons.pool.impl.GenericKeyedObjectPool; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class extends Apache Commons Pool's GenericKeyedObjectPool, adding + * two methods that support Java 5 generic type safety. However, a + * programmer can still cause RuntimeExceptions related to type errors + * by mixing calls to these additional methods with calls to the legacy + * "Object"-typed methods. + *

+ * Unfortunately, GenericKeyedObjectPool is not actually generic in the + * type-system sense. All of its methods are typed to Object, forcing the + * client programmer to sprinkle code with casts. This class centralizes + * that casting to a single method. + *

+ * As a corollary, this class is slightly less flexible than + * GenericKeyedObjectPool, as this class can only store keys and pooled + * objects each of a single type, whereas GenericKeyedObjectPool could + * theoretically contain heterogeneous types of each. However, I do not + * need the flexibility of heterogeneous types for pooling Thrift + * connections, the original work that precipitated writing this class. + * + * <K> Key type + * <V> Pooled object type + * @author Dan LaRocque <dalaro@hopcount.org> + */ +public class CTConnectionPool extends GenericKeyedObjectPool { + + private static final Logger log = + LoggerFactory.getLogger(CTConnectionPool.class); + + public CTConnectionPool(KeyedPoolableObjectFactory factory) { + super(factory); + } + + /** + * If {@code conn} is non-null and is still open, then call + * {@link org.apache.commons.pool.impl.GenericKeyedObjectPool#returnObject}, + * catching and logging and Exception that method might generate. + * This method does not emit any exceptions. + * + * @param keyspace The key of the pooled object being returned + * @param conn The pooled object being returned, or null to do nothing + */ + public void returnObjectUnsafe(String keyspace, CTConnection conn) { + if (conn != null && conn.isOpen()) try { + returnObject(keyspace, conn); + } catch (Exception e) { + log.warn("Failed to return Cassandra connection to pool", e); + log.warn("Failure context: keyspace={}, pool={}, connection={}", keyspace, this, conn); + } + } +} + diff --git a/src/main/java/org/janusgraph/diskstorage/cassandra/utils/CassandraDaemonWrapper.java b/src/main/java/org/janusgraph/diskstorage/cassandra/utils/CassandraDaemonWrapper.java new file mode 100644 index 0000000..3464a9b --- /dev/null +++ b/src/main/java/org/janusgraph/diskstorage/cassandra/utils/CassandraDaemonWrapper.java @@ -0,0 +1,92 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.utils; + +import org.apache.cassandra.service.CassandraDaemon; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class starts a Thrift CassandraDaemon inside the current JVM. This class + * supports testing and shouldn't be used in production. + * + * This class starts Cassandra on the first invocation of + * {@link CassandraDaemonWrapper#start(String)} in the life of the JVM. + * Invocations after the first have no effect except that they may log a + * warning. + * + * When the thread that first called {@code #start(String)} dies, a daemon + * thread returns from {@link Thread#join()} and kills all embedded Cassandra + * threads in the JVM. + * + * This class once supported consecutive, idempotent calls to start(String) so + * long as the argument was the same in each invocation. It also once used + * reference counting to kill Cassandra's non-daemon threads once stop() was called as + * many times as start(). Some of Cassandra's background threads and statics + * can't be easily reset to allow a restart inside the same JVM, so this was + * intended as a one-use thing. However, this interacts poorly with the new + * KCVConfiguration system in janusgraph-core. When KCVConfiguration is in use, core + * starts and stops each backend at least twice in the course of opening a + * single database instance. So the old reference counting and killing approach is out. + * + * @author Dan LaRocque <dalaro@hopcount.org> + */ +public class CassandraDaemonWrapper { + + private static final Logger log = + LoggerFactory.getLogger(CassandraDaemonWrapper.class); + + private static String activeConfig; + + private static boolean started; + + public static synchronized void start(String config) { + + if (started) { + if (null != config && !config.equals(activeConfig)) { + log.warn("Can't start in-process Cassandra instance " + + "with yaml path {} because an instance was " + + "previously started with yaml path {}", + config, activeConfig); + } + + return; + } + + started = true; + + log.debug("Current working directory: {}", System.getProperty("user.dir")); + + System.setProperty("cassandra.config", config); + // Prevent Cassandra from closing stdout/stderr streams + System.setProperty("cassandra-foreground", "yes"); + // Prevent Cassandra from overwriting Log4J configuration + System.setProperty("log4j.defaultInitOverride", "false"); + + log.info("Starting cassandra with {}", config); + + /* + * This main method doesn't block for any substantial length of time. It + * creates and starts threads and returns in relatively short order. + */ + CassandraDaemon.main(new String[0]); + + activeConfig = config; + } + + public static synchronized boolean isStarted() { + return started; + } +} diff --git a/src/main/java/org/janusgraph/diskstorage/cassandra/utils/CassandraHelper.java b/src/main/java/org/janusgraph/diskstorage/cassandra/utils/CassandraHelper.java new file mode 100644 index 0000000..dca6db4 --- /dev/null +++ b/src/main/java/org/janusgraph/diskstorage/cassandra/utils/CassandraHelper.java @@ -0,0 +1,148 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.utils; + +import java.nio.ByteBuffer; +import java.util.*; +import java.util.stream.Collectors; + +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterators; +import org.janusgraph.diskstorage.EntryList; +import org.janusgraph.diskstorage.StaticBuffer; +import org.janusgraph.diskstorage.Entry; +import org.janusgraph.diskstorage.keycolumnvalue.KeyRange; +import org.janusgraph.diskstorage.util.BufferUtil; +import org.janusgraph.diskstorage.util.StaticArrayBuffer; +import org.janusgraph.diskstorage.util.StaticArrayEntry; +import org.janusgraph.diskstorage.util.StaticArrayEntryList; +import org.apache.cassandra.dht.ByteOrderedPartitioner.BytesToken; +import org.apache.cassandra.dht.Range; +import org.apache.cassandra.dht.Token; + +import javax.annotation.Nullable; + +public class CassandraHelper { + + public static List convert(List keys) { + return keys.stream() + .map(StaticBuffer::asByteBuffer) + .collect(Collectors.toCollection(() -> new ArrayList<>(keys.size()))); + } + + /** + * Constructs an {@link EntryList} from the Iterable of entries while excluding the end slice + * (since the method contract states that the end slice is exclusive, yet Cassandra treats it as + * inclusive) and respecting the limit. + * + * @param entries + * @param getter + * @param lastColumn TODO: make this StaticBuffer so we can avoid the conversion and provide equals method + * @param limit + * @param + * @return + */ + public static EntryList makeEntryList(final Iterable entries, + final StaticArrayEntry.GetColVal getter, + final StaticBuffer lastColumn, final int limit) { + return StaticArrayEntryList.ofByteBuffer(() -> Iterators.filter( + entries.iterator(), new FilterResultColumns<>(lastColumn, limit, getter)), getter); + } + + private static class FilterResultColumns implements Predicate { + + private int count = 0; + + private final int limit; + private final StaticBuffer lastColumn; + private final StaticArrayEntry.GetColVal getter; + + private FilterResultColumns(StaticBuffer lastColumn, int limit, + StaticArrayEntry.GetColVal getter) { + this.limit = limit; + this.lastColumn = lastColumn; + this.getter = getter; + } + + @Override + public boolean apply(@Nullable E e) { + assert e!=null; + if (count>=limit || BufferUtil.equals(lastColumn, getter.getColumn(e))) return false; + count++; + return true; + } + + } + + public static Iterator makeEntryIterator(final Iterable entries, + final StaticArrayEntry.GetColVal getter, + final StaticBuffer lastColumn, final int limit) { + return Iterators.transform(Iterators.filter(entries.iterator(), new FilterResultColumns<>(lastColumn, limit, getter)), new Function() { + @Nullable + @Override + public Entry apply(@Nullable E e) { + return StaticArrayEntry.ofByteBuffer(e,getter); + } + }); + } + + + public static KeyRange transformRange(Range range) { + return transformRange(range.left, range.right); + } + + public static KeyRange transformRange(Token leftKeyExclusive, Token rightKeyInclusive) { + if (!(leftKeyExclusive instanceof BytesToken)) + throw new UnsupportedOperationException(); + + // if left part is BytesToken, right part should be too, otherwise there is no sense in the ring + assert rightKeyInclusive instanceof BytesToken; + + // l is exclusive, r is inclusive + BytesToken l = (BytesToken) leftKeyExclusive; + BytesToken r = (BytesToken) rightKeyInclusive; + + byte[] leftTokenValue = (byte[]) l.getTokenValue(); + byte[] rightTokenValue = (byte[]) r.getTokenValue(); + + Preconditions.checkArgument(leftTokenValue.length == rightTokenValue.length, + "Tokens have unequal length"); + int tokenLength = leftTokenValue.length; + + byte[][] tokens = new byte[][]{leftTokenValue, rightTokenValue}; + byte[][] plusOne = new byte[2][tokenLength]; + + for (int j = 0; j < 2; j++) { + boolean carry = true; + for (int i = tokenLength - 1; i >= 0; i--) { + byte b = tokens[j][i]; + if (carry) { + b++; + carry = b == 0; + } + plusOne[j][i] = b; + } + } + + StaticBuffer lb = StaticArrayBuffer.of(plusOne[0]); + StaticBuffer rb = StaticArrayBuffer.of(plusOne[1]); + Preconditions.checkArgument(lb.length() == tokenLength, lb.length()); + Preconditions.checkArgument(rb.length() == tokenLength, rb.length()); + + return new KeyRange(lb, rb); + } +} diff --git a/src/main/java/org/janusgraph/hadoop/CassandraHadoopStoreManager.java b/src/main/java/org/janusgraph/hadoop/CassandraHadoopStoreManager.java new file mode 100644 index 0000000..3cdd1de --- /dev/null +++ b/src/main/java/org/janusgraph/hadoop/CassandraHadoopStoreManager.java @@ -0,0 +1,35 @@ +// Copyright 2020 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.hadoop; + +import org.apache.cassandra.dht.IPartitioner; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.mapreduce.InputFormat; +import org.janusgraph.hadoop.formats.cassandra.CassandraBinaryInputFormat; + +public class CassandraHadoopStoreManager implements HadoopStoreManager { + + private IPartitioner partitioner; + + public CassandraHadoopStoreManager(IPartitioner partitioner) { + this.partitioner = partitioner; + } + + @Override + public Class getInputFormat(Configuration hadoopConf) { + hadoopConf.set("cassandra.input.partitioner.class", partitioner.getClass().getName()); + return CassandraBinaryInputFormat.class; + } +} diff --git a/src/main/java/org/janusgraph/hadoop/CassandraMapReduceIndexJobsUtils.java b/src/main/java/org/janusgraph/hadoop/CassandraMapReduceIndexJobsUtils.java new file mode 100644 index 0000000..e91da1b --- /dev/null +++ b/src/main/java/org/janusgraph/hadoop/CassandraMapReduceIndexJobsUtils.java @@ -0,0 +1,95 @@ +// Copyright 2020 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.hadoop; + +import org.apache.hadoop.conf.Configuration; +import org.janusgraph.diskstorage.configuration.ModifiableConfiguration; +import org.janusgraph.diskstorage.keycolumnvalue.scan.ScanMetrics; +import org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration; +import org.janusgraph.graphdb.olap.job.IndexRemoveJob; +import org.janusgraph.graphdb.olap.job.IndexRepairJob; +import org.janusgraph.hadoop.scan.CassandraHadoopScanRunner; +import org.janusgraph.util.system.IOUtils; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Properties; + +public class CassandraMapReduceIndexJobsUtils { + + public static ScanMetrics repair(String janusgraphPropertiesPath, String indexName, String relationType, String partitionerName) + throws InterruptedException, IOException, ClassNotFoundException { + Properties p = new Properties(); + FileInputStream fis = null; + try { + fis = new FileInputStream(janusgraphPropertiesPath); + p.load(fis); + return repair(p, indexName, relationType, partitionerName); + } finally { + IOUtils.closeQuietly(fis); + } + } + + public static ScanMetrics repair(Properties janusgraphProperties, String indexName, String relationType, + String partitionerName) + throws InterruptedException, IOException, ClassNotFoundException { + return repair(janusgraphProperties, indexName, relationType, partitionerName, new Configuration()); + } + + public static ScanMetrics repair(Properties janusgraphProperties, String indexName, String relationType, + String partitionerName, Configuration hadoopBaseConf) + throws InterruptedException, IOException, ClassNotFoundException { + IndexRepairJob job = new IndexRepairJob(); + CassandraHadoopScanRunner cr = new CassandraHadoopScanRunner(job); + return executeScanRunner(janusgraphProperties, indexName, relationType, partitionerName, hadoopBaseConf, cr); + } + + public static ScanMetrics remove(String janusgraphPropertiesPath, String indexName, String relationType, String partitionerName) + throws InterruptedException, IOException, ClassNotFoundException { + Properties p = new Properties(); + FileInputStream fis = null; + try { + fis = new FileInputStream(janusgraphPropertiesPath); + p.load(fis); + return remove(p, indexName, relationType, partitionerName); + } finally { + IOUtils.closeQuietly(fis); + } + } + + public static ScanMetrics remove(Properties janusgraphProperties, String indexName, String relationType, + String partitionerName) + throws InterruptedException, IOException, ClassNotFoundException { + return remove(janusgraphProperties, indexName, relationType, partitionerName, new Configuration()); + } + + public static ScanMetrics remove(Properties janusgraphProperties, String indexName, String relationType, + String partitionerName, Configuration hadoopBaseConf) + throws InterruptedException, IOException, ClassNotFoundException { + IndexRemoveJob job = new IndexRemoveJob(); + CassandraHadoopScanRunner cr = new CassandraHadoopScanRunner(job); + return executeScanRunner(janusgraphProperties, indexName, relationType, partitionerName, hadoopBaseConf, cr); + } + + private static ScanMetrics executeScanRunner(Properties janusgraphProperties, String indexName, String relationType, String partitionerName, Configuration hadoopBaseConf, CassandraHadoopScanRunner cr) throws InterruptedException, IOException, ClassNotFoundException { + ModifiableConfiguration mc = MapReduceIndexJobs.getIndexJobConf(indexName, relationType); + MapReduceIndexJobs.copyPropertiesToInputAndOutputConf(hadoopBaseConf, janusgraphProperties); + cr.partitionerOverride(partitionerName); + cr.scanJobConf(mc); + cr.scanJobConfRoot(GraphDatabaseConfiguration.class.getName() + "#JOB_NS"); + cr.baseHadoopConf(hadoopBaseConf); + return cr.run(); + } +} diff --git a/src/main/java/org/janusgraph/hadoop/formats/cassandra/CassandraBinaryInputFormat.java b/src/main/java/org/janusgraph/hadoop/formats/cassandra/CassandraBinaryInputFormat.java new file mode 100644 index 0000000..022ce8d --- /dev/null +++ b/src/main/java/org/janusgraph/hadoop/formats/cassandra/CassandraBinaryInputFormat.java @@ -0,0 +1,106 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.hadoop.formats.cassandra; + +import org.janusgraph.diskstorage.Entry; +import org.janusgraph.diskstorage.StaticBuffer; +import org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreManager; +import org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration; +import org.janusgraph.hadoop.config.JanusGraphHadoopConfiguration; +import org.janusgraph.hadoop.formats.util.AbstractBinaryInputFormat; +import org.apache.cassandra.hadoop.ColumnFamilyInputFormat; +import org.apache.cassandra.hadoop.ColumnFamilyRecordReader; +import org.apache.cassandra.hadoop.ConfigHelper; +import org.apache.cassandra.thrift.SlicePredicate; +import org.apache.cassandra.thrift.SliceRange; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.mapreduce.InputSplit; +import org.apache.hadoop.mapreduce.JobContext; +import org.apache.hadoop.mapreduce.RecordReader; +import org.apache.hadoop.mapreduce.TaskAttemptContext; +import org.janusgraph.hadoop.formats.util.input.current.JanusGraphHadoopSetupImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.List; + +/** + * Wraps a ColumnFamilyInputFormat and converts CFIF's binary types to JanusGraph's binary types. + */ +public class CassandraBinaryInputFormat extends AbstractBinaryInputFormat { + + private static final Logger log = LoggerFactory.getLogger(CassandraBinaryInputFormat.class); + + // Copied these private constants from Cassandra's ConfigHelper circa 2.0.9 + private static final String INPUT_WIDEROWS_CONFIG = "cassandra.input.widerows"; + private static final String RANGE_BATCH_SIZE_CONFIG = "cassandra.range.batch.size"; + + private final ColumnFamilyInputFormat columnFamilyInputFormat = new ColumnFamilyInputFormat(); + RecordReader> janusgraphRecordReader; + + public RecordReader> getRecordReader() { + return janusgraphRecordReader; + } + + @Override + public List getSplits(final JobContext jobContext) throws IOException, InterruptedException { + return this.columnFamilyInputFormat.getSplits(jobContext); + } + + @Override + public RecordReader> createRecordReader(final InputSplit inputSplit, final TaskAttemptContext taskAttemptContext) + throws IOException, InterruptedException { + janusgraphRecordReader = new CassandraBinaryRecordReader( + (ColumnFamilyRecordReader) columnFamilyInputFormat.createRecordReader(inputSplit, taskAttemptContext)); + return janusgraphRecordReader; + } + + @Override + public void setConf(final Configuration config) { + super.setConf(config); + + // Copy some JanusGraph configuration keys to the Hadoop Configuration keys used by Cassandra's ColumnFamilyInputFormat + ConfigHelper.setInputInitialAddress(config, janusgraphConf.get(GraphDatabaseConfiguration.STORAGE_HOSTS)[0]); + if (janusgraphConf.has(GraphDatabaseConfiguration.STORAGE_PORT)) + ConfigHelper.setInputRpcPort(config, String.valueOf(janusgraphConf.get(GraphDatabaseConfiguration.STORAGE_PORT))); + if (janusgraphConf.has(GraphDatabaseConfiguration.AUTH_USERNAME)) + ConfigHelper.setInputKeyspaceUserName(config, janusgraphConf.get(GraphDatabaseConfiguration.AUTH_USERNAME)); + if (janusgraphConf.has(GraphDatabaseConfiguration.AUTH_PASSWORD)) + ConfigHelper.setInputKeyspacePassword(config, janusgraphConf.get(GraphDatabaseConfiguration.AUTH_PASSWORD)); + + // Copy keyspace, force the CF setting to edgestore, honor widerows when set + final boolean wideRows = config.getBoolean(INPUT_WIDEROWS_CONFIG, false); + // Use the setInputColumnFamily overload that includes a widerows argument; using the overload without this argument forces it false + ConfigHelper.setInputColumnFamily(config, janusgraphConf.get(AbstractCassandraStoreManager.CASSANDRA_KEYSPACE), + mrConf.get(JanusGraphHadoopConfiguration.COLUMN_FAMILY_NAME), wideRows); + log.debug("Set keyspace: {}", janusgraphConf.get(AbstractCassandraStoreManager.CASSANDRA_KEYSPACE)); + + // Set the column slice bounds via Faunus' vertex query filter + final SlicePredicate predicate = new SlicePredicate(); + final int rangeBatchSize = config.getInt(RANGE_BATCH_SIZE_CONFIG, Integer.MAX_VALUE); + predicate.setSlice_range(getSliceRange(rangeBatchSize)); // TODO stop slicing the whole row + ConfigHelper.setInputSlicePredicate(config, predicate); + } + + private SliceRange getSliceRange(final int limit) { + final SliceRange sliceRange = new SliceRange(); + sliceRange.setStart(JanusGraphHadoopSetupImpl.DEFAULT_SLICE_QUERY.getSliceStart().asByteBuffer()); + sliceRange.setFinish(JanusGraphHadoopSetupImpl.DEFAULT_SLICE_QUERY.getSliceEnd().asByteBuffer()); + sliceRange.setCount(Math.min(limit, JanusGraphHadoopSetupImpl.DEFAULT_SLICE_QUERY.getLimit())); + return sliceRange; + } +} + diff --git a/src/main/java/org/janusgraph/hadoop/formats/cassandra/CassandraBinaryRecordReader.java b/src/main/java/org/janusgraph/hadoop/formats/cassandra/CassandraBinaryRecordReader.java new file mode 100644 index 0000000..3548f38 --- /dev/null +++ b/src/main/java/org/janusgraph/hadoop/formats/cassandra/CassandraBinaryRecordReader.java @@ -0,0 +1,133 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.hadoop.formats.cassandra; + +import org.janusgraph.diskstorage.Entry; +import org.janusgraph.diskstorage.StaticBuffer; +import org.janusgraph.diskstorage.util.StaticArrayBuffer; +import org.janusgraph.diskstorage.util.StaticArrayEntry; +import org.apache.cassandra.hadoop.ColumnFamilyRecordReader; +import org.apache.hadoop.mapreduce.InputSplit; +import org.apache.hadoop.mapreduce.RecordReader; +import org.apache.hadoop.mapreduce.TaskAttemptContext; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; + +/** + * Wraps a ColumnFamilyRecordReader and converts CFRR's binary types to JanusGraph binary types. + */ +public class CassandraBinaryRecordReader extends RecordReader> { + + private final ColumnFamilyRecordReader reader; + + private KV currentKV; + private KV incompleteKV; + + public CassandraBinaryRecordReader(final ColumnFamilyRecordReader reader) { + this.reader = reader; + } + + @Override + public void initialize(final InputSplit inputSplit, final TaskAttemptContext taskAttemptContext) throws IOException, InterruptedException { + reader.initialize(inputSplit, taskAttemptContext); + } + + @Override + public boolean nextKeyValue() throws IOException, InterruptedException { + return null != (currentKV = completeNextKV()); + } + + private KV completeNextKV() throws IOException { + KV completedKV = null; + boolean hasNext; + do { + hasNext = reader.nextKeyValue(); + + if (!hasNext) { + completedKV = incompleteKV; + incompleteKV = null; + } else { + StaticArrayBuffer key = StaticArrayBuffer.of(reader.getCurrentKey()); + SortedMap valueSortedMap = reader.getCurrentValue(); + List entries = new ArrayList<>(valueSortedMap.size()); + for (Map.Entry ent : valueSortedMap.entrySet()) { + ByteBuffer col = ent.getKey(); + ByteBuffer val = ent.getValue().value; + entries.add(StaticArrayEntry.of(StaticArrayBuffer.of(col), StaticArrayBuffer.of(val))); + } + + if (null == incompleteKV) { + // Initialization; this should happen just once in an instance's lifetime + incompleteKV = new KV(key); + } else if (!incompleteKV.key.equals(key)) { + // The underlying Cassandra reader has just changed to a key we haven't seen yet + // This implies that there will be no more entries for the prior key + completedKV = incompleteKV; + incompleteKV = new KV(key); + } + + incompleteKV.addEntries(entries); + } + /* Loop ends when either + * A) the cassandra reader ran out of data + * or + * B) the cassandra reader switched keys, thereby completing a KV */ + } while (hasNext && null == completedKV); + + return completedKV; + } + + @Override + public StaticBuffer getCurrentKey() throws IOException, InterruptedException { + return currentKV.key; + } + + @Override + public Iterable getCurrentValue() throws IOException, InterruptedException { + return currentKV.entries; + } + + @Override + public void close() throws IOException { + reader.close(); + } + + @Override + public float getProgress() { + return reader.getProgress(); + } + + private static class KV { + private final StaticArrayBuffer key; + private ArrayList entries; + + public KV(StaticArrayBuffer key) { + this.key = key; + } + + public void addEntries(Collection toAdd) { + if (null == entries) + entries = new ArrayList<>(toAdd.size()); + + entries.addAll(toAdd); + } + } +} diff --git a/src/main/java/org/janusgraph/hadoop/formats/cassandra/CassandraInputFormat.java b/src/main/java/org/janusgraph/hadoop/formats/cassandra/CassandraInputFormat.java new file mode 100644 index 0000000..4b1ef1a --- /dev/null +++ b/src/main/java/org/janusgraph/hadoop/formats/cassandra/CassandraInputFormat.java @@ -0,0 +1,23 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.hadoop.formats.cassandra; + +import org.janusgraph.hadoop.formats.util.HadoopInputFormat; + +public class CassandraInputFormat extends HadoopInputFormat { + public CassandraInputFormat() { + super(new CassandraBinaryInputFormat()); + } +} diff --git a/src/main/java/org/janusgraph/hadoop/scan/CassandraHadoopScanRunner.java b/src/main/java/org/janusgraph/hadoop/scan/CassandraHadoopScanRunner.java new file mode 100644 index 0000000..bf75ae2 --- /dev/null +++ b/src/main/java/org/janusgraph/hadoop/scan/CassandraHadoopScanRunner.java @@ -0,0 +1,89 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.hadoop.scan; + +import com.google.common.base.Preconditions; +import org.janusgraph.diskstorage.configuration.*; +import org.janusgraph.diskstorage.keycolumnvalue.scan.ScanJob; +import org.janusgraph.diskstorage.keycolumnvalue.scan.ScanMetrics; +import org.janusgraph.graphdb.olap.VertexScanJob; +import org.janusgraph.hadoop.config.JanusGraphHadoopConfiguration; +import org.janusgraph.hadoop.formats.cassandra.CassandraBinaryInputFormat; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +public class CassandraHadoopScanRunner extends AbstractHadoopScanRunner { + + private static final Logger log = + LoggerFactory.getLogger(CassandraHadoopScanRunner.class); + + private static final String CASSANDRA_PARTITIONER_KEY = "cassandra.input.partitioner.class"; + + private String partitionerOverride; + + public CassandraHadoopScanRunner(ScanJob scanJob) { + super(scanJob); + } + + public CassandraHadoopScanRunner(VertexScanJob vertexScanJob) { + super(vertexScanJob); + } + + protected CassandraHadoopScanRunner self() { + return this; + } + + public CassandraHadoopScanRunner partitionerOverride(String partitionerOverride) { + this.partitionerOverride = partitionerOverride; + return this; + } + + public ScanMetrics run() throws InterruptedException, IOException, ClassNotFoundException { + + org.apache.hadoop.conf.Configuration hadoopConf = null != baseHadoopConf ? + baseHadoopConf : new org.apache.hadoop.conf.Configuration(); + + if (null != janusgraphConf) { + for (String k : janusgraphConf.getKeys("")) { + String prefix = ConfigElement.getPath(JanusGraphHadoopConfiguration.GRAPH_CONFIG_KEYS, true) + "."; + hadoopConf.set(prefix + k, janusgraphConf.get(k, Object.class).toString()); + log.debug("Set: {}={}", prefix + k, + janusgraphConf.get(k, Object.class).toString()); + } + } + + if (null != partitionerOverride) { + hadoopConf.set(CASSANDRA_PARTITIONER_KEY, partitionerOverride); + } + + if (null == hadoopConf.get(CASSANDRA_PARTITIONER_KEY)) { + throw new IllegalArgumentException(CASSANDRA_PARTITIONER_KEY + + " must be provided in either the base Hadoop Configuration object or by the partitionerOverride method"); + } else { + log.debug("Partitioner: {}={}", + CASSANDRA_PARTITIONER_KEY, hadoopConf.get(CASSANDRA_PARTITIONER_KEY)); + } + + Preconditions.checkNotNull(hadoopConf); + + if (null != scanJob) { + return HadoopScanRunner.runScanJob(scanJob, scanJobConf, scanJobConfRoot, hadoopConf, CassandraBinaryInputFormat.class); + } else { + return HadoopScanRunner.runVertexScanJob(vertexScanJob, scanJobConf, scanJobConfRoot, hadoopConf, CassandraBinaryInputFormat.class); + } + } +} diff --git a/src/test/java/org/janusgraph/CassandraStorageSetup.java b/src/test/java/org/janusgraph/CassandraStorageSetup.java new file mode 100644 index 0000000..af1285f --- /dev/null +++ b/src/test/java/org/janusgraph/CassandraStorageSetup.java @@ -0,0 +1,140 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph; + +import static org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreManager.CASSANDRA_KEYSPACE; + +import java.io.File; +import java.io.IOException; +import java.time.Duration; + +import org.janusgraph.diskstorage.cassandra.utils.CassandraDaemonWrapper; +import org.janusgraph.diskstorage.configuration.ModifiableConfiguration; +import org.janusgraph.diskstorage.configuration.WriteConfiguration; + +import org.apache.commons.io.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; + +import static org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration.*; + +public class CassandraStorageSetup { + + public static final String CONFDIR_SYSPROP = "test.cassandra.confdir"; + public static final String DATADIR_SYSPROP = "test.cassandra.datadir"; + + private static volatile Paths paths; + + private static final Logger log = LoggerFactory.getLogger(CassandraStorageSetup.class); + + private static synchronized Paths getPaths() { + if (null == paths) { + String yamlPath = "file://" + loadAbsoluteDirectoryPath("conf", CONFDIR_SYSPROP, true) + File.separator + "cassandra.yaml"; + String dataPath = loadAbsoluteDirectoryPath("data", DATADIR_SYSPROP, false); + paths = new Paths(yamlPath, dataPath); + } + return paths; + } + + public static ModifiableConfiguration getEmbeddedConfiguration(String ks) { + ModifiableConfiguration config = buildGraphConfiguration(); + if (null != ks) { + config.set(CASSANDRA_KEYSPACE, cleanKeyspaceName(ks)); + log.debug("Set keyspace name: {}", config.get(CASSANDRA_KEYSPACE)); + } + config.set(PAGE_SIZE,500); + config.set(CONNECTION_TIMEOUT, Duration.ofSeconds(60L)); + config.set(STORAGE_BACKEND, "embeddedcassandra"); + config.set(DROP_ON_CLEAR, false); + config.set(STORAGE_CONF_FILE, getPaths().yamlPath); + return config; + } + + + public static WriteConfiguration getEmbeddedCassandraPartitionGraphConfiguration(String ks) { + return getEmbeddedConfiguration(ks).getConfiguration(); + } + + /** + * Load cassandra.yaml and data paths from the environment or from default + * values if nothing is set in the environment, then delete all existing + * data, and finally start Cassandra. + *

+ * This method is idempotent. Calls after the first have no effect aside + * from logging statements. + */ + public static void startCleanEmbedded() { + startCleanEmbedded(getPaths()); + } + + /* + * Cassandra only accepts keyspace names 48 characters long or shorter made + * up of alphanumeric characters and underscores. + */ + public static String cleanKeyspaceName(String raw) { + Preconditions.checkNotNull(raw); + Preconditions.checkArgument(0 < raw.length()); + + if (48 < raw.length() || raw.matches("^.*[^a-zA-Z0-9_].*$")) { + return "strhash" + Math.abs(raw.hashCode()); + } else { + return raw; + } + } + + private static void startCleanEmbedded(Paths p) { + if (!CassandraDaemonWrapper.isStarted()) { + try { + FileUtils.deleteDirectory(new File(p.dataPath)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + CassandraDaemonWrapper.start(p.yamlPath); + } + + private static String loadAbsoluteDirectoryPath(String name, String prop, boolean mustExistAndBeAbsolute) { + String s = System.getProperty(prop); + + if (null == s) { + s = Joiner.on(File.separator).join(System.getProperty("user.dir"), "target", "cassandra", name, "localhost-bop"); + log.info("Set default Cassandra {} directory path {}", name, s); + } else { + log.info("Loaded Cassandra {} directory path {} from system property {}", name, s, prop); + } + + if (mustExistAndBeAbsolute) { + File dir = new File(s); + Preconditions.checkArgument(dir.isDirectory(), "Path %s must be a directory", s); + Preconditions.checkArgument(dir.isAbsolute(), "Path %s must be absolute", s); + } + + return s; + } + + private static class Paths { + private final String yamlPath; + private final String dataPath; + + public Paths(String yamlPath, String dataPath) { + this.yamlPath = yamlPath; + this.dataPath = dataPath; + } + } +} diff --git a/src/test/java/org/janusgraph/CassandraTestCategory.java b/src/test/java/org/janusgraph/CassandraTestCategory.java new file mode 100644 index 0000000..e8d594b --- /dev/null +++ b/src/test/java/org/janusgraph/CassandraTestCategory.java @@ -0,0 +1,20 @@ +// Copyright 2019 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph; + +public class CassandraTestCategory { + public static final String CASSANDRA_SSL_TESTS = "CASSANDRA_SSL_TESTS"; + public static final String STANDALONE_TESTS = "STANDALONE_TESTS"; +} diff --git a/src/test/java/org/janusgraph/JanusGraphCassandraThriftContainer.java b/src/test/java/org/janusgraph/JanusGraphCassandraThriftContainer.java new file mode 100644 index 0000000..b3779b2 --- /dev/null +++ b/src/test/java/org/janusgraph/JanusGraphCassandraThriftContainer.java @@ -0,0 +1,172 @@ +// Copyright 2020 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph; + +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import org.janusgraph.diskstorage.configuration.ModifiableConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.BindMode; +import org.testcontainers.containers.CassandraContainer; + +import java.io.File; +import java.time.Duration; + +import static org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreManager.CASSANDRA_KEYSPACE; +import static org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreManager.SSL_ENABLED; +import static org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreManager.SSL_TRUSTSTORE_LOCATION; +import static org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreManager.SSL_TRUSTSTORE_PASSWORD; +import static org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration.*; + +public class JanusGraphCassandraThriftContainer extends CassandraContainer { + private static final Logger LOGGER = LoggerFactory.getLogger(JanusGraphCassandraThriftContainer.class); + + private static final String DEFAULT_VERSION = "2.2.14"; + private static final String DEFAULT_IMAGE = "cassandra"; + private static final String DEFAULT_PARTITIONER = "murmur"; + private static final boolean DEFAULT_USE_SSL = false; + private static final boolean DEFAULT_USE_DEFAULT_CONFIG_FROM_IMAGE = false; + public static final Integer THRIFT_PORT = 9160; + + + private static String getVersion() { + String property = System.getProperty("cassandra.docker.version"); + if (property != null && !property.isEmpty()) + return property; + return DEFAULT_VERSION; + } + + private static String getCassandraImage() { + String property = System.getProperty("cassandra.docker.image"); + if (property != null && !property.isEmpty()) + return property; + return DEFAULT_IMAGE; + } + + private static String getPartitioner() { + String property = System.getProperty("cassandra.docker.partitioner"); + if (property != null && !property.isEmpty()) + return property; + return DEFAULT_PARTITIONER; + } + + private static boolean useDynamicConfig() { + String property = System.getProperty("cassandra.docker.useDefaultConfigFromImage"); + if (property != null && !property.isEmpty()) + return !Boolean.parseBoolean(property); + return !DEFAULT_USE_DEFAULT_CONFIG_FROM_IMAGE; + } + + private static boolean useSSL() { + String property = System.getProperty("cassandra.docker.useSSL"); + if (property != null && !property.isEmpty()) + return Boolean.parseBoolean(property); + return DEFAULT_USE_SSL; + } + + /** + * This function is used as a condition to executed tests if compacted storage is supported. + */ + public static boolean isCompactStorageSupported() { + return !getVersion().startsWith("3.") && getCassandraImage().equals(DEFAULT_IMAGE); + } + + private String getConfigPrefix() { + if (getVersion().startsWith("3.")) { + return "cassandra3"; + } + return "cassandra2"; + } + + public JanusGraphCassandraThriftContainer() { + this(false); + } + + public JanusGraphCassandraThriftContainer(boolean fixedExposedPorts) { + super(getCassandraImage() + ":" + getVersion()); + if (fixedExposedPorts) { + addFixedExposedPort(CQL_PORT, CQL_PORT); + addFixedExposedPort(THRIFT_PORT, THRIFT_PORT); + } + withEnv("MAX_HEAP_SIZE", "2G"); + withEnv("HEAP_NEWSIZE", "1G"); + if (useDynamicConfig()) { + withCommand("-Dcassandra.config=/opt/cassandra.yaml -Dcassandra.skip_wait_for_gossip_to_settle=0 -Dcassandra.load_ring_state=false"); + switch (getPartitioner()){ + case "byteordered": + withClasspathResourceMapping(getConfigPrefix() + "-byteordered.yaml", "/opt/cassandra.yaml", BindMode.READ_WRITE); + break; + case "murmur": + if (useSSL()) { + withClasspathResourceMapping("cert/test.crt", "/etc/ssl/test.crt", BindMode.READ_WRITE); + withClasspathResourceMapping("cert/test.keystore", "/etc/ssl/test.keystore", BindMode.READ_WRITE); + withClasspathResourceMapping("cqlshrc", "/root/.cassandra/cqlshrc", BindMode.READ_WRITE); + withClasspathResourceMapping(getConfigPrefix() + "-murmur-ssl.yaml", "/opt/cassandra.yaml", BindMode.READ_WRITE); + } else { + withClasspathResourceMapping(getConfigPrefix() + "-murmur.yaml", "/opt/cassandra.yaml", BindMode.READ_WRITE); + } + break; + default: + throw new IllegalArgumentException("Unrecognized partitioner: " + getPartitioner()); + } + } + } + + private static String cleanKeyspaceName(String raw) { + Preconditions.checkNotNull(raw); + Preconditions.checkArgument(0 < raw.length()); + + if (48 < raw.length() || raw.matches("^.*[^a-zA-Z0-9_].*$")) { + return "strhash" + Math.abs(raw.hashCode()); + } else { + return raw; + } + } + + public ModifiableConfiguration getThriftConfiguration(final String keyspace) { + return getConfiguration(keyspace, "cassandrathrift"); + } + + public ModifiableConfiguration getAstyanaxConfiguration(final String keyspace) { + return getConfiguration(keyspace, "astyanax"); + } + + private ModifiableConfiguration getConfiguration(final String keyspace, final String storage_backend) { + final ModifiableConfiguration config = buildGraphConfiguration(); + if (null != keyspace) { + config.set(CASSANDRA_KEYSPACE, cleanKeyspaceName(keyspace)); + LOGGER.debug("Set keyspace name: {}", config.get(CASSANDRA_KEYSPACE)); + } + config.set(PAGE_SIZE, 500); + config.set(CONNECTION_TIMEOUT, Duration.ofSeconds(60L)); + config.set(STORAGE_BACKEND, storage_backend); + config.set(STORAGE_PORT, getMappedPort(THRIFT_PORT)); + config.set(STORAGE_HOSTS, new String[]{getContainerIpAddress()}); + config.set(DROP_ON_CLEAR, false); + if (useSSL() && useDynamicConfig()) { + config.set(SSL_ENABLED, true); + config.set(SSL_TRUSTSTORE_LOCATION, + Joiner.on(File.separator).join("target", "test-classes", "cert", "test.truststore")); + config.set(SSL_TRUSTSTORE_PASSWORD, "cassandra"); + } + return config; + } + + public int getMappedThirftPort() { + return getMappedPort(THRIFT_PORT); + } + +} \ No newline at end of file diff --git a/src/test/java/org/janusgraph/blueprints/thrift/ThriftGraphComputerProvider.java b/src/test/java/org/janusgraph/blueprints/thrift/ThriftGraphComputerProvider.java new file mode 100644 index 0000000..044773e --- /dev/null +++ b/src/test/java/org/janusgraph/blueprints/thrift/ThriftGraphComputerProvider.java @@ -0,0 +1,38 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.blueprints.thrift; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.blueprints.AbstractJanusGraphComputerProvider; +import org.janusgraph.diskstorage.configuration.ModifiableConfiguration; +import org.janusgraph.graphdb.olap.computer.FulgoraGraphComputer; +import org.apache.tinkerpop.gremlin.GraphProvider; + +/** + * @author Matthias Broecheler (me@matthiasb.com) + */ +@GraphProvider.Descriptor(computer = FulgoraGraphComputer.class) +public class ThriftGraphComputerProvider extends AbstractJanusGraphComputerProvider { + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Override + public ModifiableConfiguration getJanusGraphConfiguration(String graphName, Class test, String testMethodName) { + thriftContainer.start(); + ModifiableConfiguration config = super.getJanusGraphConfiguration(graphName, test, testMethodName); + config.setAll(thriftContainer.getThriftConfiguration(graphName).getAll()); + return config; + } + +} diff --git a/src/test/java/org/janusgraph/blueprints/thrift/ThriftGraphProvider.java b/src/test/java/org/janusgraph/blueprints/thrift/ThriftGraphProvider.java new file mode 100644 index 0000000..30fd3ee --- /dev/null +++ b/src/test/java/org/janusgraph/blueprints/thrift/ThriftGraphProvider.java @@ -0,0 +1,33 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.blueprints.thrift; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.blueprints.AbstractJanusGraphProvider; +import org.janusgraph.diskstorage.configuration.ModifiableConfiguration; + +/** + * @author Matthias Broecheler (me@matthiasb.com) + */ +public class ThriftGraphProvider extends AbstractJanusGraphProvider { + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Override + public ModifiableConfiguration getJanusGraphConfiguration(String graphName, Class test, String testMethodName) { + thriftContainer.start(); + return thriftContainer.getThriftConfiguration(graphName); + } + +} diff --git a/src/test/java/org/janusgraph/blueprints/thrift/ThriftMultiQueryGraphProvider.java b/src/test/java/org/janusgraph/blueprints/thrift/ThriftMultiQueryGraphProvider.java new file mode 100644 index 0000000..403c9d8 --- /dev/null +++ b/src/test/java/org/janusgraph/blueprints/thrift/ThriftMultiQueryGraphProvider.java @@ -0,0 +1,35 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.blueprints.thrift; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.blueprints.AbstractJanusGraphProvider; +import org.janusgraph.diskstorage.configuration.ModifiableConfiguration; +import org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration; + +/** + * @author Ted Wilmes (twilmes@gmail.com) + */ +public class ThriftMultiQueryGraphProvider extends AbstractJanusGraphProvider { + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Override + public ModifiableConfiguration getJanusGraphConfiguration(String graphName, Class test, String testMethodName) { + thriftContainer.start(); + return thriftContainer.getThriftConfiguration(graphName) + .set(GraphDatabaseConfiguration.USE_MULTIQUERY, true); + } + +} diff --git a/src/test/java/org/janusgraph/blueprints/thrift/process/ThriftComputerTest.java b/src/test/java/org/janusgraph/blueprints/thrift/process/ThriftComputerTest.java new file mode 100644 index 0000000..2c8f79a --- /dev/null +++ b/src/test/java/org/janusgraph/blueprints/thrift/process/ThriftComputerTest.java @@ -0,0 +1,36 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.blueprints.thrift.process; + +import org.janusgraph.blueprints.thrift.ThriftGraphComputerProvider; +import org.janusgraph.core.JanusGraph; +import org.apache.tinkerpop.gremlin.GraphProviderClass; +import org.apache.tinkerpop.gremlin.process.ProcessComputerSuite; +import org.junit.runner.RunWith; + +/** + * @author Matthias Broecheler (me@matthiasb.com) + */ +@RunWith(ProcessComputerSuite.class) +@GraphProviderClass(provider = ThriftGraphComputerProvider.class, graph = JanusGraph.class) +public class ThriftComputerTest { + +// TP3 ignores @BeforeClass -- the following method is never executed +// @BeforeAll +// public static void beforeSuite() { +// CassandraStorageSetup.startCleanEmbedded(); +// } + +} diff --git a/src/test/java/org/janusgraph/blueprints/thrift/process/ThriftMultiQueryProcessTest.java b/src/test/java/org/janusgraph/blueprints/thrift/process/ThriftMultiQueryProcessTest.java new file mode 100644 index 0000000..66c582a --- /dev/null +++ b/src/test/java/org/janusgraph/blueprints/thrift/process/ThriftMultiQueryProcessTest.java @@ -0,0 +1,29 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.blueprints.thrift.process; + +import org.apache.tinkerpop.gremlin.GraphProviderClass; +import org.apache.tinkerpop.gremlin.process.ProcessStandardSuite; +import org.janusgraph.blueprints.thrift.ThriftMultiQueryGraphProvider; +import org.janusgraph.core.JanusGraph; +import org.junit.runner.RunWith; + +/** + * @author Ted Wilmes (twilmese@gmail.com) + */ +@RunWith(ProcessStandardSuite.class) +@GraphProviderClass(provider = ThriftMultiQueryGraphProvider.class, graph = JanusGraph.class) +public class ThriftMultiQueryProcessTest { +} diff --git a/src/test/java/org/janusgraph/blueprints/thrift/process/ThriftProcessTest.java b/src/test/java/org/janusgraph/blueprints/thrift/process/ThriftProcessTest.java new file mode 100644 index 0000000..3f8fb29 --- /dev/null +++ b/src/test/java/org/janusgraph/blueprints/thrift/process/ThriftProcessTest.java @@ -0,0 +1,36 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.blueprints.thrift.process; + +import org.janusgraph.blueprints.thrift.ThriftGraphProvider; +import org.janusgraph.core.JanusGraph; +import org.apache.tinkerpop.gremlin.GraphProviderClass; +import org.apache.tinkerpop.gremlin.process.ProcessStandardSuite; +import org.junit.runner.RunWith; + +/** + * @author Matthias Broecheler (me@matthiasb.com) + */ +@RunWith(ProcessStandardSuite.class) +@GraphProviderClass(provider = ThriftGraphProvider.class, graph = JanusGraph.class) +public class ThriftProcessTest { + +// TP3 ignores @BeforeClass -- the following method is never executed +// @BeforeAll +// public static void beforeSuite() { +// CassandraStorageSetup.startCleanEmbedded(); +// } + +} diff --git a/src/test/java/org/janusgraph/blueprints/thrift/structure/ThriftStructureTest.java b/src/test/java/org/janusgraph/blueprints/thrift/structure/ThriftStructureTest.java new file mode 100644 index 0000000..7f43a5d --- /dev/null +++ b/src/test/java/org/janusgraph/blueprints/thrift/structure/ThriftStructureTest.java @@ -0,0 +1,36 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.blueprints.thrift.structure; + +import org.janusgraph.blueprints.thrift.ThriftGraphProvider; +import org.janusgraph.core.JanusGraph; +import org.apache.tinkerpop.gremlin.GraphProviderClass; +import org.apache.tinkerpop.gremlin.structure.StructureStandardSuite; +import org.junit.runner.RunWith; + +/** + * @author Matthias Broecheler (me@matthiasb.com) + */ +@RunWith(StructureStandardSuite.class) +@GraphProviderClass(provider = ThriftGraphProvider.class, graph = JanusGraph.class) +public class ThriftStructureTest { + +// TP3 ignores @BeforeClass -- the following method is never executed +// @BeforeAll +// public static void beforeSuite() { +// CassandraStorageSetup.startCleanEmbedded(); +// } + +} diff --git a/src/test/java/org/janusgraph/diskstorage/cassandra/AbstractCassandraStoreTest.java b/src/test/java/org/janusgraph/diskstorage/cassandra/AbstractCassandraStoreTest.java new file mode 100644 index 0000000..fd2474c --- /dev/null +++ b/src/test/java/org/janusgraph/diskstorage/cassandra/AbstractCassandraStoreTest.java @@ -0,0 +1,148 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra; + +import com.google.common.collect.ImmutableMap; +import org.janusgraph.diskstorage.BackendException; +import org.janusgraph.diskstorage.KeyColumnValueStoreTest; +import org.janusgraph.diskstorage.configuration.Configuration; +import org.janusgraph.diskstorage.configuration.ModifiableConfiguration; +import org.janusgraph.diskstorage.keycolumnvalue.StoreFeatures; +import org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration; +import org.janusgraph.testutil.FeatureFlag; +import org.janusgraph.testutil.JanusGraphFeature; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public abstract class AbstractCassandraStoreTest extends KeyColumnValueStoreTest { + private static final String TEST_CF_NAME = "testcf"; + private static final String DEFAULT_COMPRESSOR_PACKAGE = "org.apache.cassandra.io.compress"; + + public abstract ModifiableConfiguration getBaseStorageConfiguration(); + public abstract ModifiableConfiguration getBaseStorageConfiguration(String keyspace); + + public abstract AbstractCassandraStoreManager openStorageManager(Configuration c) throws BackendException; + + @Test + @FeatureFlag(feature = JanusGraphFeature.UnorderedScan) + public void testUnorderedConfiguration() { + StoreFeatures features = manager.getFeatures(); + assertFalse(features.isKeyOrdered()); + assertFalse(features.hasLocalKeyPartition()); + } + + @Test + @FeatureFlag(feature = JanusGraphFeature.OrderedScan) + public void testOrderedConfiguration() { + StoreFeatures features = manager.getFeatures(); + assertTrue(features.isKeyOrdered()); + } + + @Test + public void testDefaultCFCompressor() throws BackendException { + + final String cf = TEST_CF_NAME + "_snappy"; + + AbstractCassandraStoreManager mgr = openStorageManager(); + + mgr.openDatabase(cf); + + Map defaultCfCompressionOps = + new ImmutableMap.Builder() + .put("sstable_compression", DEFAULT_COMPRESSOR_PACKAGE + "." + AbstractCassandraStoreManager.CF_COMPRESSION_TYPE.getDefaultValue()) + .put("chunk_length_kb", "64") + .build(); + + assertEquals(defaultCfCompressionOps, mgr.getCompressionOptions(cf)); + } + + @Test + public void testCustomCFCompressor() throws BackendException { + + final String compressor = "DeflateCompressor"; + final int ckb = 128; + final String cf = TEST_CF_NAME + "_gzip"; + + ModifiableConfiguration config = getBaseStorageConfiguration(); + config.set(AbstractCassandraStoreManager.CF_COMPRESSION_TYPE,compressor); + config.set(AbstractCassandraStoreManager.CF_COMPRESSION_BLOCK_SIZE,ckb); + + AbstractCassandraStoreManager mgr = openStorageManager(config); + + // N.B.: clearStorage() truncates CFs but does not delete them + mgr.openDatabase(cf); + + final Map expected = ImmutableMap + . builder() + .put("sstable_compression", + DEFAULT_COMPRESSOR_PACKAGE + "." + compressor) + .put("chunk_length_kb", String.valueOf(ckb)).build(); + + assertEquals(expected, mgr.getCompressionOptions(cf)); + } + + @Test + public void testDisableCFCompressor() throws BackendException { + + final String cf = TEST_CF_NAME + "_nocompress"; + + ModifiableConfiguration config = getBaseStorageConfiguration(); + config.set(AbstractCassandraStoreManager.CF_COMPRESSION,false); + AbstractCassandraStoreManager mgr = openStorageManager(config); + + // N.B.: clearStorage() truncates CFs but does not delete them + mgr.openDatabase(cf); + + assertEquals(Collections.emptyMap(), mgr.getCompressionOptions(cf)); + } + + @Test + public void testTTLSupported() { + StoreFeatures features = manager.getFeatures(); + assertTrue(features.hasCellTTL()); + } + + @Test + public void keyspaceShouldBeEquivalentToProvidedOne() throws BackendException { + final ModifiableConfiguration config = getBaseStorageConfiguration("randomNewKeyspace"); + final AbstractCassandraStoreManager mgr = openStorageManager(config); + assertEquals("randomNewKeyspace", mgr.keySpaceName); + } + + @Test + public void keyspaceShouldBeEquivalentToGraphName() throws BackendException { + final ModifiableConfiguration config = getBaseStorageConfiguration(null); + config.set(GraphDatabaseConfiguration.GRAPH_NAME, "randomNewGraphName"); + final AbstractCassandraStoreManager mgr = openStorageManager(config); + assertEquals("randomNewGraphName", mgr.keySpaceName); + } + + @Override + public AbstractCassandraStoreManager openStorageManager() throws BackendException { + return openStorageManager(getBaseStorageConfiguration()); + } + + @Override + public AbstractCassandraStoreManager openStorageManagerForClearStorageTest() throws Exception { + return openStorageManager(getBaseStorageConfiguration().set(GraphDatabaseConfiguration.DROP_ON_CLEAR, true)); + } + +} diff --git a/src/test/java/org/janusgraph/diskstorage/cassandra/CassandraTransactionTest.java b/src/test/java/org/janusgraph/diskstorage/cassandra/CassandraTransactionTest.java new file mode 100644 index 0000000..12e4c1f --- /dev/null +++ b/src/test/java/org/janusgraph/diskstorage/cassandra/CassandraTransactionTest.java @@ -0,0 +1,93 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra; + +import com.google.common.base.Preconditions; +import org.janusgraph.diskstorage.BaseTransactionConfig; +import org.janusgraph.diskstorage.configuration.ModifiableConfiguration; +import org.janusgraph.diskstorage.util.StandardBaseTransactionConfig; +import org.janusgraph.diskstorage.util.time.TimestampProviders; +import org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration; +import org.junit.jupiter.api.Test; + +import static org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreManager.CASSANDRA_READ_CONSISTENCY; +import static org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreManager.CASSANDRA_WRITE_CONSISTENCY; +import static org.junit.jupiter.api.Assertions.*; + +public class CassandraTransactionTest { + + /* testRead/WriteConsistencyLevel have unnecessary code duplication + * that could be avoided by creating a common helper method that takes + * a ConfigOption parameter and a function that converts a + * CassandraTransaction to a consistency level by calling either + * ct.getReadConsistencyLevel() or .getWriteConsistencyLevel(), + * but it doesn't seem worth the complexity. + */ + + @Test + public void testWriteConsistencyLevel() { + int levelsChecked = 0; + + // Test whether CassandraTransaction honors the write consistency level option + for (CLevel writeLevel : CLevel.values()) { + StandardBaseTransactionConfig.Builder b = new StandardBaseTransactionConfig.Builder(); + ModifiableConfiguration mc = GraphDatabaseConfiguration.buildGraphConfiguration(); + mc.set(CASSANDRA_WRITE_CONSISTENCY, writeLevel.name()); + b.customOptions(mc); + b.timestampProvider(TimestampProviders.MICRO); + CassandraTransaction ct = new CassandraTransaction(b.build()); + assertEquals(writeLevel, ct.getWriteConsistencyLevel()); + levelsChecked++; + } + + // Sanity check: if CLevel.values was empty, something is wrong with the test + Preconditions.checkState(0 < levelsChecked); + } + + @Test + public void testReadConsistencyLevel() { + int levelsChecked = 0; + + // Test whether CassandraTransaction honors the write consistency level option + for (CLevel writeLevel : CLevel.values()) { + StandardBaseTransactionConfig.Builder b = new StandardBaseTransactionConfig.Builder(); + ModifiableConfiguration mc = GraphDatabaseConfiguration.buildGraphConfiguration(); + mc.set(CASSANDRA_READ_CONSISTENCY, writeLevel.name()); + b.timestampProvider(TimestampProviders.MICRO); + b.customOptions(mc); + CassandraTransaction ct = new CassandraTransaction(b.build()); + assertEquals(writeLevel, ct.getReadConsistencyLevel()); + levelsChecked++; + } + + // Sanity check: if CLevel.values was empty, something is wrong with the test + Preconditions.checkState(0 < levelsChecked); + } + + @Test + public void testTimestampProvider() { + BaseTransactionConfig baseTransactionConfig = StandardBaseTransactionConfig.of(TimestampProviders.NANO); + CassandraTransaction ct = new CassandraTransaction(baseTransactionConfig); + assertEquals(TimestampProviders.NANO, ct.getConfiguration().getTimestampProvider()); + + baseTransactionConfig = StandardBaseTransactionConfig.of(TimestampProviders.MICRO); + ct = new CassandraTransaction(baseTransactionConfig); + assertEquals(TimestampProviders.MICRO, ct.getConfiguration().getTimestampProvider()); + + baseTransactionConfig = StandardBaseTransactionConfig.of(TimestampProviders.MILLI); + ct = new CassandraTransaction(baseTransactionConfig); + assertEquals(TimestampProviders.MILLI, ct.getConfiguration().getTimestampProvider()); + } +} diff --git a/src/test/java/org/janusgraph/diskstorage/cassandra/UUIDTest.java b/src/test/java/org/janusgraph/diskstorage/cassandra/UUIDTest.java new file mode 100644 index 0000000..cad066d --- /dev/null +++ b/src/test/java/org/janusgraph/diskstorage/cassandra/UUIDTest.java @@ -0,0 +1,47 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra; + +import org.apache.cassandra.db.marshal.TimeUUIDType; +import org.janusgraph.CassandraTestCategory; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.nio.ByteBuffer; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@Tag(CassandraTestCategory.STANDALONE_TESTS) +public class UUIDTest { + public static final String z = "00000000-0000-1000-0000-000000000000"; + public static final String v = "9451e273-7753-11e0-92df-e700f669bcfc"; + + @Test + public void timeUUIDComparison() { + TimeUUIDType ti = TimeUUIDType.instance; + + UUID zu = UUID.fromString(z); + UUID vu = UUID.fromString(v); + + ByteBuffer zb = ti.decompose(zu); + ByteBuffer vb = ti.decompose(vu); + + assertEquals(-1, ti.compare(zb, vb)); + assertEquals(1, zu.compareTo(vu)); + assertEquals(1, ti.compare(vb, zb)); + assertEquals(-1, vu.compareTo(zu)); + } +} diff --git a/src/test/java/org/janusgraph/diskstorage/cassandra/astyanax/AstyanaxColumnPaginationTest.java b/src/test/java/org/janusgraph/diskstorage/cassandra/astyanax/AstyanaxColumnPaginationTest.java new file mode 100644 index 0000000..665cfd6 --- /dev/null +++ b/src/test/java/org/janusgraph/diskstorage/cassandra/astyanax/AstyanaxColumnPaginationTest.java @@ -0,0 +1,158 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.astyanax; + +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.core.JanusGraphFactory; +import org.janusgraph.diskstorage.BackendException; +import org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreManager; +import org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreTest; +import org.janusgraph.diskstorage.configuration.Configuration; +import org.janusgraph.diskstorage.configuration.ModifiableConfiguration; +import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@Testcontainers +public class AstyanaxColumnPaginationTest extends AbstractCassandraStoreTest { + @Container + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + private static final int DEFAULT_READ_PAGE_SIZE = 4096; + + @Override + public ModifiableConfiguration getBaseStorageConfiguration() { + return getBaseStorageConfiguration(getClass().getSimpleName()); + } + + @Override + public ModifiableConfiguration getBaseStorageConfiguration(String keyspace) { + return thriftContainer.getAstyanaxConfiguration(keyspace); + } + + @Override + public AbstractCassandraStoreManager openStorageManager(Configuration c) throws BackendException { + return new AstyanaxStoreManager(c); + } + + @Test + public void ensureReadPageSizePropertySetCorrectly() { + assertEquals(((AstyanaxStoreManager) manager).readPageSize, DEFAULT_READ_PAGE_SIZE); + } + + @Test + public void retrieveLessThanBoundaryColumnPaginationProperties() { + final Graph graph = JanusGraphFactory.open(getBaseStorageConfiguration()); + final Vertex v = graph.addVertex(); + for(int i = 0; i < DEFAULT_READ_PAGE_SIZE - 1; i++) { + v.property(String.valueOf(i), i); + } + graph.tx().commit(); + + assertEquals(DEFAULT_READ_PAGE_SIZE - 1, graph.traversal().V(v).valueMap().next().keySet().size()); + } + + @Test + public void retrieveBoundaryColumnPaginationProperties() { + final Graph graph = JanusGraphFactory.open(getBaseStorageConfiguration()); + final Vertex v = graph.addVertex(); + for(int i = 0; i < DEFAULT_READ_PAGE_SIZE; i++) { + v.property(String.valueOf(i), i); + } + graph.tx().commit(); + + assertEquals(DEFAULT_READ_PAGE_SIZE, graph.traversal().V(v).valueMap().next().keySet().size()); + } + + @Test + public void retrieveBeyondBoundaryColumnPaginationProperties() { + final Graph graph = JanusGraphFactory.open(getBaseStorageConfiguration()); + final Vertex v = graph.addVertex(); + for(int i = 0; i < DEFAULT_READ_PAGE_SIZE + 1; i++) { + v.property(String.valueOf(i), i); + } + graph.tx().commit(); + + assertEquals(DEFAULT_READ_PAGE_SIZE + 1, graph.traversal().V(v).valueMap().next().keySet().size()); + } + + @Test + public void retrieveWayBeyondBoundaryColumnPaginationProperties() { + final Graph graph = JanusGraphFactory.open(getBaseStorageConfiguration()); + final Vertex v = graph.addVertex(); + for (int i = 0; i < DEFAULT_READ_PAGE_SIZE * 5; i++) { + v.property(String.valueOf(i), i); + } + graph.tx().commit(); + + assertEquals(DEFAULT_READ_PAGE_SIZE * 5, graph.traversal().V(v).valueMap().next().keySet().size()); + } + + @Test + public void retrieveLessThanBoundaryColumnPaginationEdges() { + final Graph graph = JanusGraphFactory.open(getBaseStorageConfiguration()); + final Vertex v = graph.addVertex(); + final Vertex v2 = graph.addVertex(); + for(int i = 0; i < DEFAULT_READ_PAGE_SIZE - 1; i++) { + v.addEdge("edgeLabel", v2); + } + graph.tx().commit(); + + assertEquals(DEFAULT_READ_PAGE_SIZE - 1, graph.traversal().V(v).outE().toList().size()); + } + + @Test + public void retrieveBoundaryColumnPaginationEdges() { + final Graph graph = JanusGraphFactory.open(getBaseStorageConfiguration()); + final Vertex v = graph.addVertex(); + final Vertex v2 = graph.addVertex(); + for(int i = 0; i < DEFAULT_READ_PAGE_SIZE; i++) { + v.addEdge("edgeLabel", v2); + } + graph.tx().commit(); + + assertEquals(DEFAULT_READ_PAGE_SIZE, graph.traversal().V(v).outE().toList().size()); + } + + @Test + public void retrieveBeyondBoundaryColumnPaginationEdges() { + final Graph graph = JanusGraphFactory.open(getBaseStorageConfiguration()); + final Vertex v = graph.addVertex(); + final Vertex v2 = graph.addVertex(); + for(int i = 0; i < DEFAULT_READ_PAGE_SIZE + 1; i++) { + v.addEdge("edgeLabel", v2); + } + graph.tx().commit(); + + assertEquals(DEFAULT_READ_PAGE_SIZE + 1, graph.traversal().V(v).outE().toList().size()); + } + + @Test + public void retrieveWayBeyondBoundaryColumnPaginationEdges() { + final Graph graph = JanusGraphFactory.open(getBaseStorageConfiguration()); + final Vertex v = graph.addVertex(); + final Vertex v2 = graph.addVertex(); + for (int i = 0; i < DEFAULT_READ_PAGE_SIZE * 5; i++) { + v.addEdge("edgeLabel", v2); + } + graph.tx().commit(); + + assertEquals(DEFAULT_READ_PAGE_SIZE * 5, graph.traversal().V(v).outE().toList().size()); + } +} diff --git a/src/test/java/org/janusgraph/diskstorage/cassandra/astyanax/AstyanaxIDAuthorityTest.java b/src/test/java/org/janusgraph/diskstorage/cassandra/astyanax/AstyanaxIDAuthorityTest.java new file mode 100644 index 0000000..6c4cc29 --- /dev/null +++ b/src/test/java/org/janusgraph/diskstorage/cassandra/astyanax/AstyanaxIDAuthorityTest.java @@ -0,0 +1,33 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.astyanax; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.diskstorage.BackendException; +import org.janusgraph.diskstorage.IDAuthorityTest; +import org.janusgraph.diskstorage.keycolumnvalue.KeyColumnValueStoreManager; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +public class AstyanaxIDAuthorityTest extends IDAuthorityTest { + @Container + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Override + public KeyColumnValueStoreManager openStorageManager() throws BackendException { + return new AstyanaxStoreManager(thriftContainer.getAstyanaxConfiguration(getClass().getSimpleName())); + } +} diff --git a/src/test/java/org/janusgraph/diskstorage/cassandra/astyanax/AstyanaxLockStoreTest.java b/src/test/java/org/janusgraph/diskstorage/cassandra/astyanax/AstyanaxLockStoreTest.java new file mode 100644 index 0000000..cab0442 --- /dev/null +++ b/src/test/java/org/janusgraph/diskstorage/cassandra/astyanax/AstyanaxLockStoreTest.java @@ -0,0 +1,34 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.astyanax; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.diskstorage.BackendException; +import org.janusgraph.diskstorage.LockKeyColumnValueStoreTest; +import org.janusgraph.diskstorage.configuration.Configuration; +import org.janusgraph.diskstorage.keycolumnvalue.KeyColumnValueStoreManager; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +public class AstyanaxLockStoreTest extends LockKeyColumnValueStoreTest { + @Container + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Override + public KeyColumnValueStoreManager openStorageManager(int idx, Configuration configuration) throws BackendException { + return new AstyanaxStoreManager(thriftContainer.getAstyanaxConfiguration(getClass().getSimpleName())); + } +} diff --git a/src/test/java/org/janusgraph/diskstorage/cassandra/astyanax/AstyanaxLogTest.java b/src/test/java/org/janusgraph/diskstorage/cassandra/astyanax/AstyanaxLogTest.java new file mode 100644 index 0000000..4c8b7e4 --- /dev/null +++ b/src/test/java/org/janusgraph/diskstorage/cassandra/astyanax/AstyanaxLogTest.java @@ -0,0 +1,36 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.astyanax; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.TestCategory; +import org.janusgraph.diskstorage.BackendException; +import org.janusgraph.diskstorage.keycolumnvalue.KeyColumnValueStoreManager; +import org.janusgraph.diskstorage.log.KCVSLogTest; +import org.junit.jupiter.api.Tag; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Tag(TestCategory.SERIAL_TESTS) +@Testcontainers +public class AstyanaxLogTest extends KCVSLogTest { + @Container + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Override + public KeyColumnValueStoreManager openStorageManager() throws BackendException { + return new AstyanaxStoreManager(thriftContainer.getAstyanaxConfiguration(getClass().getSimpleName())); + } +} diff --git a/src/test/java/org/janusgraph/diskstorage/cassandra/astyanax/AstyanaxMultiWriteStoreTest.java b/src/test/java/org/janusgraph/diskstorage/cassandra/astyanax/AstyanaxMultiWriteStoreTest.java new file mode 100644 index 0000000..c09a803 --- /dev/null +++ b/src/test/java/org/janusgraph/diskstorage/cassandra/astyanax/AstyanaxMultiWriteStoreTest.java @@ -0,0 +1,33 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.astyanax; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.diskstorage.BackendException; +import org.janusgraph.diskstorage.MultiWriteKeyColumnValueStoreTest; +import org.janusgraph.diskstorage.keycolumnvalue.KeyColumnValueStoreManager; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +public class AstyanaxMultiWriteStoreTest extends MultiWriteKeyColumnValueStoreTest { + @Container + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Override + public KeyColumnValueStoreManager openStorageManager() throws BackendException { + return new AstyanaxStoreManager(thriftContainer.getAstyanaxConfiguration(getClass().getSimpleName())); + } +} diff --git a/src/test/java/org/janusgraph/diskstorage/cassandra/astyanax/AstyanaxStoreTest.java b/src/test/java/org/janusgraph/diskstorage/cassandra/astyanax/AstyanaxStoreTest.java new file mode 100644 index 0000000..c2dda65 --- /dev/null +++ b/src/test/java/org/janusgraph/diskstorage/cassandra/astyanax/AstyanaxStoreTest.java @@ -0,0 +1,45 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.astyanax; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.diskstorage.BackendException; +import org.janusgraph.diskstorage.configuration.Configuration; +import org.janusgraph.diskstorage.configuration.ModifiableConfiguration; +import org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreTest; +import org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreManager; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +public class AstyanaxStoreTest extends AbstractCassandraStoreTest { + @Container + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Override + public ModifiableConfiguration getBaseStorageConfiguration(String keyspace) { + return thriftContainer.getAstyanaxConfiguration(keyspace); + } + + @Override + public ModifiableConfiguration getBaseStorageConfiguration() { + return getBaseStorageConfiguration(getClass().getSimpleName()); + } + + @Override + public AbstractCassandraStoreManager openStorageManager(Configuration c) throws BackendException { + return new AstyanaxStoreManager(c); + } +} diff --git a/src/test/java/org/janusgraph/diskstorage/cassandra/embedded/EmbeddedIDAuthorityTest.java b/src/test/java/org/janusgraph/diskstorage/cassandra/embedded/EmbeddedIDAuthorityTest.java new file mode 100644 index 0000000..bf94680 --- /dev/null +++ b/src/test/java/org/janusgraph/diskstorage/cassandra/embedded/EmbeddedIDAuthorityTest.java @@ -0,0 +1,28 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.embedded; + +import org.janusgraph.CassandraStorageSetup; +import org.janusgraph.diskstorage.BackendException; +import org.janusgraph.diskstorage.IDAuthorityTest; +import org.janusgraph.diskstorage.keycolumnvalue.KeyColumnValueStoreManager; + +public class EmbeddedIDAuthorityTest extends IDAuthorityTest { + + @Override + public KeyColumnValueStoreManager openStorageManager() throws BackendException { + return new CassandraEmbeddedStoreManager(CassandraStorageSetup.getEmbeddedConfiguration(getClass().getSimpleName())); + } +} diff --git a/src/test/java/org/janusgraph/diskstorage/cassandra/embedded/EmbeddedLockStoreTest.java b/src/test/java/org/janusgraph/diskstorage/cassandra/embedded/EmbeddedLockStoreTest.java new file mode 100644 index 0000000..b8f5482 --- /dev/null +++ b/src/test/java/org/janusgraph/diskstorage/cassandra/embedded/EmbeddedLockStoreTest.java @@ -0,0 +1,29 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.embedded; + +import org.janusgraph.CassandraStorageSetup; +import org.janusgraph.diskstorage.BackendException; +import org.janusgraph.diskstorage.LockKeyColumnValueStoreTest; +import org.janusgraph.diskstorage.configuration.Configuration; +import org.janusgraph.diskstorage.keycolumnvalue.KeyColumnValueStoreManager; + +public class EmbeddedLockStoreTest extends LockKeyColumnValueStoreTest { + + @Override + public KeyColumnValueStoreManager openStorageManager(int idx, Configuration configuration) throws BackendException { + return new CassandraEmbeddedStoreManager(CassandraStorageSetup.getEmbeddedConfiguration(getClass().getSimpleName())); + } +} diff --git a/src/test/java/org/janusgraph/diskstorage/cassandra/embedded/EmbeddedLogTest.java b/src/test/java/org/janusgraph/diskstorage/cassandra/embedded/EmbeddedLogTest.java new file mode 100644 index 0000000..18b2a1e --- /dev/null +++ b/src/test/java/org/janusgraph/diskstorage/cassandra/embedded/EmbeddedLogTest.java @@ -0,0 +1,38 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.embedded; + +import org.janusgraph.TestCategory; +import org.janusgraph.diskstorage.BackendException; +import org.junit.jupiter.api.BeforeAll; + +import org.janusgraph.CassandraStorageSetup; +import org.janusgraph.diskstorage.keycolumnvalue.KeyColumnValueStoreManager; +import org.janusgraph.diskstorage.log.KCVSLogTest; +import org.junit.jupiter.api.Tag; + +@Tag(TestCategory.SERIAL_TESTS) +public class EmbeddedLogTest extends KCVSLogTest { + + @BeforeAll + public static void startCassandra() { + CassandraStorageSetup.startCleanEmbedded(); + } + + @Override + public KeyColumnValueStoreManager openStorageManager() throws BackendException { + return new CassandraEmbeddedStoreManager(CassandraStorageSetup.getEmbeddedConfiguration(getClass().getSimpleName())); + } +} diff --git a/src/test/java/org/janusgraph/diskstorage/cassandra/embedded/EmbeddedMultiWriteStoreTest.java b/src/test/java/org/janusgraph/diskstorage/cassandra/embedded/EmbeddedMultiWriteStoreTest.java new file mode 100644 index 0000000..b640088 --- /dev/null +++ b/src/test/java/org/janusgraph/diskstorage/cassandra/embedded/EmbeddedMultiWriteStoreTest.java @@ -0,0 +1,29 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.embedded; + +import org.janusgraph.diskstorage.BackendException; + +import org.janusgraph.CassandraStorageSetup; +import org.janusgraph.diskstorage.MultiWriteKeyColumnValueStoreTest; +import org.janusgraph.diskstorage.keycolumnvalue.KeyColumnValueStoreManager; + +public class EmbeddedMultiWriteStoreTest extends MultiWriteKeyColumnValueStoreTest { + + @Override + public KeyColumnValueStoreManager openStorageManager() throws BackendException { + return new CassandraEmbeddedStoreManager(CassandraStorageSetup.getEmbeddedConfiguration(getClass().getSimpleName())); + } +} diff --git a/src/test/java/org/janusgraph/diskstorage/cassandra/embedded/EmbeddedStoreTest.java b/src/test/java/org/janusgraph/diskstorage/cassandra/embedded/EmbeddedStoreTest.java new file mode 100644 index 0000000..ab270ba --- /dev/null +++ b/src/test/java/org/janusgraph/diskstorage/cassandra/embedded/EmbeddedStoreTest.java @@ -0,0 +1,60 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.embedded; + +import org.janusgraph.diskstorage.BackendException; +import org.janusgraph.diskstorage.configuration.Configuration; +import org.janusgraph.diskstorage.configuration.ModifiableConfiguration; +import org.janusgraph.testutil.FeatureFlag; +import org.janusgraph.testutil.JanusGraphFeature; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import org.janusgraph.CassandraStorageSetup; +import org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreTest; +import org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreManager; +import org.janusgraph.diskstorage.keycolumnvalue.StoreFeatures; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class EmbeddedStoreTest extends AbstractCassandraStoreTest { + + @BeforeAll + public static void startCassandra() { + CassandraStorageSetup.startCleanEmbedded(); + } + + @Override + public ModifiableConfiguration getBaseStorageConfiguration() { + return getBaseStorageConfiguration(getClass().getSimpleName()); + } + + public ModifiableConfiguration getBaseStorageConfiguration(String keyspace) { + return CassandraStorageSetup.getEmbeddedConfiguration(keyspace); + } + + @Override + public AbstractCassandraStoreManager openStorageManager(Configuration c) throws BackendException { + return new CassandraEmbeddedStoreManager(c); + } + + @Test + @FeatureFlag(feature = JanusGraphFeature.OrderedScan) + public void testConfiguration() { + StoreFeatures features = manager.getFeatures(); + assertTrue(features.isKeyOrdered()); + assertTrue(features.hasLocalKeyPartition()); + } +} diff --git a/src/test/java/org/janusgraph/diskstorage/cassandra/thrift/CassandraThriftStoreManagerTest.java b/src/test/java/org/janusgraph/diskstorage/cassandra/thrift/CassandraThriftStoreManagerTest.java new file mode 100644 index 0000000..65b09f9 --- /dev/null +++ b/src/test/java/org/janusgraph/diskstorage/cassandra/thrift/CassandraThriftStoreManagerTest.java @@ -0,0 +1,57 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.thrift; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.diskstorage.configuration.ModifiableConfiguration; +import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@Testcontainers +public class CassandraThriftStoreManagerTest { + @Container + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Test + public void configOptionFrameSizeMbShouldErrorOnLowValue() { + assertThrows(IllegalArgumentException.class, () -> { + ModifiableConfiguration config = thriftContainer.getThriftConfiguration("janusgraph"); + config.set(CassandraThriftStoreManager.THRIFT_FRAME_SIZE_MB, 0); + config.get(CassandraThriftStoreManager.THRIFT_FRAME_SIZE_MB); + }); + } + + @Test + public void configOptionFrameSizeMbShouldBeHappy() { + ModifiableConfiguration config = thriftContainer.getThriftConfiguration("janusgraph"); + config.set(CassandraThriftStoreManager.THRIFT_FRAME_SIZE_MB, 1); + Integer result = config.get(CassandraThriftStoreManager.THRIFT_FRAME_SIZE_MB); + assertEquals(1, result.intValue()); + } + + @Test + public void configOptionFrameSizeMbShouldErrorOnHighValue() { + assertThrows(IllegalArgumentException.class, () -> { + ModifiableConfiguration config = thriftContainer.getThriftConfiguration("janusgraph"); + config.set(CassandraThriftStoreManager.THRIFT_FRAME_SIZE_MB, 2048); + config.get(CassandraThriftStoreManager.THRIFT_FRAME_SIZE_MB); + }); + } + +} diff --git a/src/test/java/org/janusgraph/diskstorage/cassandra/thrift/ThriftDistributedStoreManagerTest.java b/src/test/java/org/janusgraph/diskstorage/cassandra/thrift/ThriftDistributedStoreManagerTest.java new file mode 100644 index 0000000..0ea07e2 --- /dev/null +++ b/src/test/java/org/janusgraph/diskstorage/cassandra/thrift/ThriftDistributedStoreManagerTest.java @@ -0,0 +1,55 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.thrift; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.diskstorage.BackendException; +import org.janusgraph.diskstorage.DistributedStoreManagerTest; +import org.janusgraph.diskstorage.common.DistributedStoreManager.Deployment; +import org.janusgraph.testutil.FeatureFlag; +import org.janusgraph.testutil.JanusGraphFeature; +import org.junit.jupiter.api.*; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@Testcontainers +public class ThriftDistributedStoreManagerTest extends DistributedStoreManagerTest { + @Container + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + + @BeforeEach + public void setUp() throws BackendException { + manager = new CassandraThriftStoreManager( + thriftContainer.getThriftConfiguration(this.getClass().getSimpleName())); + store = manager.openDatabase("distributedcf"); + } + + @AfterEach + public void tearDown() throws BackendException { + if (null != manager) + manager.close(); + } + + @Override + @Test + @FeatureFlag(feature = JanusGraphFeature.OrderedScan) + public void testGetDeployment() { + final Deployment deployment = Deployment.LOCAL; + assertEquals(deployment, manager.getDeployment()); + } +} diff --git a/src/test/java/org/janusgraph/diskstorage/cassandra/thrift/ThriftIDAuthorityTest.java b/src/test/java/org/janusgraph/diskstorage/cassandra/thrift/ThriftIDAuthorityTest.java new file mode 100644 index 0000000..583e171 --- /dev/null +++ b/src/test/java/org/janusgraph/diskstorage/cassandra/thrift/ThriftIDAuthorityTest.java @@ -0,0 +1,33 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.thrift; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.diskstorage.BackendException; +import org.janusgraph.diskstorage.IDAuthorityTest; +import org.janusgraph.diskstorage.keycolumnvalue.KeyColumnValueStoreManager; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +public class ThriftIDAuthorityTest extends IDAuthorityTest { + @Container + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Override + public KeyColumnValueStoreManager openStorageManager() throws BackendException { + return new CassandraThriftStoreManager(thriftContainer.getThriftConfiguration(this.getClass().getSimpleName())); + } +} diff --git a/src/test/java/org/janusgraph/diskstorage/cassandra/thrift/ThriftLockStoreTest.java b/src/test/java/org/janusgraph/diskstorage/cassandra/thrift/ThriftLockStoreTest.java new file mode 100644 index 0000000..ec12f14 --- /dev/null +++ b/src/test/java/org/janusgraph/diskstorage/cassandra/thrift/ThriftLockStoreTest.java @@ -0,0 +1,35 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.thrift; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.diskstorage.BackendException; +import org.janusgraph.diskstorage.configuration.Configuration; + +import org.janusgraph.diskstorage.LockKeyColumnValueStoreTest; +import org.janusgraph.diskstorage.keycolumnvalue.KeyColumnValueStoreManager; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +public class ThriftLockStoreTest extends LockKeyColumnValueStoreTest { + @Container + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Override + public KeyColumnValueStoreManager openStorageManager(int idx, Configuration configuration) throws BackendException { + return new CassandraThriftStoreManager(thriftContainer.getThriftConfiguration(this.getClass().getSimpleName())); + } +} diff --git a/src/test/java/org/janusgraph/diskstorage/cassandra/thrift/ThriftLogTest.java b/src/test/java/org/janusgraph/diskstorage/cassandra/thrift/ThriftLogTest.java new file mode 100644 index 0000000..4da2c64 --- /dev/null +++ b/src/test/java/org/janusgraph/diskstorage/cassandra/thrift/ThriftLogTest.java @@ -0,0 +1,37 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.thrift; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.TestCategory; +import org.janusgraph.diskstorage.BackendException; +import org.janusgraph.diskstorage.keycolumnvalue.KeyColumnValueStoreManager; +import org.janusgraph.diskstorage.log.KCVSLogTest; +import org.junit.jupiter.api.Tag; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Tag(TestCategory.SERIAL_TESTS) +@Testcontainers +public class ThriftLogTest extends KCVSLogTest { + @Container + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Override + public KeyColumnValueStoreManager openStorageManager() throws BackendException { + return new CassandraThriftStoreManager(thriftContainer.getThriftConfiguration(this.getClass().getSimpleName())); + } + +} diff --git a/src/test/java/org/janusgraph/diskstorage/cassandra/thrift/ThriftMultiWriteStoreTest.java b/src/test/java/org/janusgraph/diskstorage/cassandra/thrift/ThriftMultiWriteStoreTest.java new file mode 100644 index 0000000..5c05a19 --- /dev/null +++ b/src/test/java/org/janusgraph/diskstorage/cassandra/thrift/ThriftMultiWriteStoreTest.java @@ -0,0 +1,33 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.thrift; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.diskstorage.BackendException; +import org.janusgraph.diskstorage.MultiWriteKeyColumnValueStoreTest; +import org.janusgraph.diskstorage.keycolumnvalue.KeyColumnValueStoreManager; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +public class ThriftMultiWriteStoreTest extends MultiWriteKeyColumnValueStoreTest { + @Container + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Override + public KeyColumnValueStoreManager openStorageManager() throws BackendException { + return new CassandraThriftStoreManager(thriftContainer.getThriftConfiguration(this.getClass().getSimpleName())); + } +} diff --git a/src/test/java/org/janusgraph/diskstorage/cassandra/thrift/ThriftStoreTest.java b/src/test/java/org/janusgraph/diskstorage/cassandra/thrift/ThriftStoreTest.java new file mode 100644 index 0000000..b534825 --- /dev/null +++ b/src/test/java/org/janusgraph/diskstorage/cassandra/thrift/ThriftStoreTest.java @@ -0,0 +1,45 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.thrift; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.diskstorage.BackendException; +import org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreManager; +import org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreTest; +import org.janusgraph.diskstorage.configuration.Configuration; +import org.janusgraph.diskstorage.configuration.ModifiableConfiguration; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +public class ThriftStoreTest extends AbstractCassandraStoreTest { + @Container + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Override + public ModifiableConfiguration getBaseStorageConfiguration() { + return getBaseStorageConfiguration(getClass().getSimpleName()); + } + + @Override + public ModifiableConfiguration getBaseStorageConfiguration(String keyspace) { + return thriftContainer.getThriftConfiguration(keyspace); + } + + @Override + public AbstractCassandraStoreManager openStorageManager(Configuration c) throws BackendException { + return new CassandraThriftStoreManager(c); + } +} diff --git a/src/test/java/org/janusgraph/diskstorage/cassandra/utils/CassandraHelperTest.java b/src/test/java/org/janusgraph/diskstorage/cassandra/utils/CassandraHelperTest.java new file mode 100644 index 0000000..797d57c --- /dev/null +++ b/src/test/java/org/janusgraph/diskstorage/cassandra/utils/CassandraHelperTest.java @@ -0,0 +1,42 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.diskstorage.cassandra.utils; + +import org.apache.cassandra.dht.ByteOrderedPartitioner.BytesToken; +import org.janusgraph.diskstorage.keycolumnvalue.KeyRange; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +public class CassandraHelperTest { + + private static final BytesToken ZERO = new BytesToken(new byte[] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}); + + @Test + public void testTransformRange() { + BytesToken token2 = new BytesToken(new byte[] {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,-1}); + KeyRange keyRange = CassandraHelper.transformRange(ZERO, token2); + assertArrayEquals(new byte[] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, keyRange.getStart().asByteBuffer().array()); + assertArrayEquals(new byte[] {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,1,0}, keyRange.getEnd().asByteBuffer().array()); + } + + @Test + public void testTransformRangeWithRollingCarry() { + BytesToken token2 = new BytesToken(new byte[] {0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}); + KeyRange keyRange = CassandraHelper.transformRange(ZERO, token2); + assertArrayEquals(new byte[] {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, keyRange.getEnd().asByteBuffer().array()); + } + +} diff --git a/src/test/java/org/janusgraph/graphdb/CassandraGraphTest.java b/src/test/java/org/janusgraph/graphdb/CassandraGraphTest.java new file mode 100644 index 0000000..232c890 --- /dev/null +++ b/src/test/java/org/janusgraph/graphdb/CassandraGraphTest.java @@ -0,0 +1,145 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.graphdb; + +import static org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreManager.CASSANDRA_KEYSPACE; +import static org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreManager.CASSANDRA_READ_CONSISTENCY; +import static org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreManager.CASSANDRA_WRITE_CONSISTENCY; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.janusgraph.core.JanusGraphFactory; +import org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreManager; +import org.janusgraph.diskstorage.configuration.ConfigElement; +import org.janusgraph.diskstorage.configuration.WriteConfiguration; +import org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration; +import org.janusgraph.graphdb.configuration.JanusGraphConstants; +import org.janusgraph.graphdb.database.StandardJanusGraph; +import org.janusgraph.graphdb.transaction.StandardJanusGraphTx; +import org.junit.jupiter.api.Test; + +/** + * @author Joshua Shinavier (http://fortytwo.net) + */ +public abstract class CassandraGraphTest extends JanusGraphTest { + + @Test + public void testHasTTL() { + assertTrue(features.hasCellTTL()); + } + + @Test + public void testStorageVersionSet() { + close(); + WriteConfiguration wc = getConfiguration(); + assertNull(wc.get(ConfigElement.getPath(GraphDatabaseConfiguration.INITIAL_STORAGE_VERSION), + GraphDatabaseConfiguration.INITIAL_STORAGE_VERSION.getDatatype())); + wc.set(ConfigElement.getPath(GraphDatabaseConfiguration.INITIAL_STORAGE_VERSION), JanusGraphConstants.STORAGE_VERSION); + graph = (StandardJanusGraph) JanusGraphFactory.open(wc); + mgmt = graph.openManagement(); + assertEquals(JanusGraphConstants.STORAGE_VERSION, (mgmt.get("graph.storage-version"))); + mgmt.rollback(); + } + + @Test + public void testGraphConfigUsedByThreadBoundTx() { + close(); + WriteConfiguration wc = getConfiguration(); + wc.set(ConfigElement.getPath(CASSANDRA_READ_CONSISTENCY), "ALL"); + wc.set(ConfigElement.getPath(CASSANDRA_WRITE_CONSISTENCY), "LOCAL_QUORUM"); + + graph = (StandardJanusGraph) JanusGraphFactory.open(wc); + + StandardJanusGraphTx tx = (StandardJanusGraphTx)graph.getCurrentThreadTx(); + assertEquals("ALL", + tx.getTxHandle().getBaseTransactionConfig().getCustomOptions() + .get(AbstractCassandraStoreManager.CASSANDRA_READ_CONSISTENCY)); + assertEquals("LOCAL_QUORUM", + tx.getTxHandle().getBaseTransactionConfig().getCustomOptions() + .get(AbstractCassandraStoreManager.CASSANDRA_WRITE_CONSISTENCY)); + } + + @Test + public void testGraphConfigUsedByTx() { + close(); + WriteConfiguration wc = getConfiguration(); + wc.set(ConfigElement.getPath(CASSANDRA_READ_CONSISTENCY), "TWO"); + wc.set(ConfigElement.getPath(CASSANDRA_WRITE_CONSISTENCY), "THREE"); + + graph = (StandardJanusGraph) JanusGraphFactory.open(wc); + + StandardJanusGraphTx tx = (StandardJanusGraphTx)graph.newTransaction(); + assertEquals("TWO", + tx.getTxHandle().getBaseTransactionConfig().getCustomOptions() + .get(AbstractCassandraStoreManager.CASSANDRA_READ_CONSISTENCY)); + assertEquals("THREE", + tx.getTxHandle().getBaseTransactionConfig().getCustomOptions() + .get(AbstractCassandraStoreManager.CASSANDRA_WRITE_CONSISTENCY)); + tx.rollback(); + } + + @Test + public void testCustomConfigUsedByTx() { + close(); + WriteConfiguration wc = getConfiguration(); + wc.set(ConfigElement.getPath(CASSANDRA_READ_CONSISTENCY), "ALL"); + wc.set(ConfigElement.getPath(CASSANDRA_WRITE_CONSISTENCY), "ALL"); + + graph = (StandardJanusGraph) JanusGraphFactory.open(wc); + + StandardJanusGraphTx tx = (StandardJanusGraphTx)graph.buildTransaction() + .customOption(ConfigElement.getPath(CASSANDRA_READ_CONSISTENCY), "ONE") + .customOption(ConfigElement.getPath(CASSANDRA_WRITE_CONSISTENCY), "TWO").start(); + + assertEquals("ONE", + tx.getTxHandle().getBaseTransactionConfig().getCustomOptions() + .get(AbstractCassandraStoreManager.CASSANDRA_READ_CONSISTENCY)); + assertEquals("TWO", + tx.getTxHandle().getBaseTransactionConfig().getCustomOptions() + .get(AbstractCassandraStoreManager.CASSANDRA_WRITE_CONSISTENCY)); + tx.rollback(); + } + + @Test + public void testTitanGraphBackwardCompatibility() { + close(); + WriteConfiguration wc = getConfiguration(); + wc.set(ConfigElement.getPath(CASSANDRA_KEYSPACE), "titan"); + wc.set(ConfigElement.getPath(GraphDatabaseConfiguration.TITAN_COMPATIBLE_VERSIONS), "x.x.x"); + + assertNull(wc.get(ConfigElement.getPath(GraphDatabaseConfiguration.INITIAL_JANUSGRAPH_VERSION), + GraphDatabaseConfiguration.INITIAL_JANUSGRAPH_VERSION.getDatatype())); + + assertFalse(JanusGraphConstants.TITAN_COMPATIBLE_VERSIONS.contains( + wc.get(ConfigElement.getPath(GraphDatabaseConfiguration.TITAN_COMPATIBLE_VERSIONS), + GraphDatabaseConfiguration.TITAN_COMPATIBLE_VERSIONS.getDatatype()) + )); + + wc.set(ConfigElement.getPath(GraphDatabaseConfiguration.TITAN_COMPATIBLE_VERSIONS), "1.0.0"); + assertTrue(JanusGraphConstants.TITAN_COMPATIBLE_VERSIONS.contains( + wc.get(ConfigElement.getPath(GraphDatabaseConfiguration.TITAN_COMPATIBLE_VERSIONS), + GraphDatabaseConfiguration.TITAN_COMPATIBLE_VERSIONS.getDatatype()) + )); + + wc.set(ConfigElement.getPath(GraphDatabaseConfiguration.IDS_STORE_NAME), JanusGraphConstants.TITAN_ID_STORE_NAME); + assertEquals( + JanusGraphConstants.TITAN_ID_STORE_NAME, + wc.get(ConfigElement.getPath(GraphDatabaseConfiguration.IDS_STORE_NAME), + GraphDatabaseConfiguration.IDS_STORE_NAME.getDatatype())); + graph = (StandardJanusGraph) JanusGraphFactory.open(wc); + } +} diff --git a/src/test/java/org/janusgraph/graphdb/astyanax/AstyanaxGraphConcurrentTest.java b/src/test/java/org/janusgraph/graphdb/astyanax/AstyanaxGraphConcurrentTest.java new file mode 100644 index 0000000..4160ef5 --- /dev/null +++ b/src/test/java/org/janusgraph/graphdb/astyanax/AstyanaxGraphConcurrentTest.java @@ -0,0 +1,35 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.graphdb.astyanax; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.TestCategory; +import org.janusgraph.diskstorage.configuration.WriteConfiguration; +import org.janusgraph.graphdb.JanusGraphConcurrentTest; +import org.junit.jupiter.api.Tag; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Tag(TestCategory.PERFORMANCE_TESTS) +@Testcontainers +public class AstyanaxGraphConcurrentTest extends JanusGraphConcurrentTest { + @Container + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Override + public WriteConfiguration getConfiguration() { + return thriftContainer.getAstyanaxConfiguration(getClass().getSimpleName()).getConfiguration(); + } +} diff --git a/src/test/java/org/janusgraph/graphdb/astyanax/AstyanaxGraphPerformanceMemoryTest.java b/src/test/java/org/janusgraph/graphdb/astyanax/AstyanaxGraphPerformanceMemoryTest.java new file mode 100644 index 0000000..f4dde4e --- /dev/null +++ b/src/test/java/org/janusgraph/graphdb/astyanax/AstyanaxGraphPerformanceMemoryTest.java @@ -0,0 +1,32 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.graphdb.astyanax; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.diskstorage.configuration.WriteConfiguration; +import org.janusgraph.graphdb.JanusGraphPerformanceMemoryTest; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +public class AstyanaxGraphPerformanceMemoryTest extends JanusGraphPerformanceMemoryTest { + @Container + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Override + public WriteConfiguration getConfiguration() { + return thriftContainer.getAstyanaxConfiguration(getClass().getSimpleName()).getConfiguration(); + } +} diff --git a/src/test/java/org/janusgraph/graphdb/astyanax/AstyanaxGraphTest.java b/src/test/java/org/janusgraph/graphdb/astyanax/AstyanaxGraphTest.java new file mode 100644 index 0000000..5fa3475 --- /dev/null +++ b/src/test/java/org/janusgraph/graphdb/astyanax/AstyanaxGraphTest.java @@ -0,0 +1,32 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.graphdb.astyanax; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.diskstorage.configuration.WriteConfiguration; +import org.janusgraph.graphdb.CassandraGraphTest; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +public class AstyanaxGraphTest extends CassandraGraphTest { + @Container + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Override + public WriteConfiguration getConfiguration() { + return thriftContainer.getAstyanaxConfiguration(getClass().getSimpleName()).getConfiguration(); + } +} diff --git a/src/test/java/org/janusgraph/graphdb/astyanax/AstyanaxPartitionGraphTest.java b/src/test/java/org/janusgraph/graphdb/astyanax/AstyanaxPartitionGraphTest.java new file mode 100644 index 0000000..3ecb946 --- /dev/null +++ b/src/test/java/org/janusgraph/graphdb/astyanax/AstyanaxPartitionGraphTest.java @@ -0,0 +1,32 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.graphdb.astyanax; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.diskstorage.configuration.WriteConfiguration; +import org.janusgraph.graphdb.JanusGraphPartitionGraphTest; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +public class AstyanaxPartitionGraphTest extends JanusGraphPartitionGraphTest { + @Container + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Override + public WriteConfiguration getBaseConfiguration() { + return thriftContainer.getAstyanaxConfiguration(getClass().getSimpleName()).getConfiguration(); + } +} diff --git a/src/test/java/org/janusgraph/graphdb/embedded/EmbeddedEventualGraphTest.java b/src/test/java/org/janusgraph/graphdb/embedded/EmbeddedEventualGraphTest.java new file mode 100644 index 0000000..7a56827 --- /dev/null +++ b/src/test/java/org/janusgraph/graphdb/embedded/EmbeddedEventualGraphTest.java @@ -0,0 +1,34 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.graphdb.embedded; + +import org.janusgraph.CassandraStorageSetup; +import org.janusgraph.diskstorage.configuration.WriteConfiguration; +import org.janusgraph.graphdb.JanusGraphEventualGraphTest; +import org.junit.jupiter.api.BeforeAll; + +public class EmbeddedEventualGraphTest extends JanusGraphEventualGraphTest { + + @BeforeAll + public static void startEmbeddedCassandra() { + CassandraStorageSetup.startCleanEmbedded(); + } + + @Override + public WriteConfiguration getConfiguration() { + return CassandraStorageSetup.getEmbeddedCassandraPartitionGraphConfiguration(getClass().getSimpleName()); + } + +} diff --git a/src/test/java/org/janusgraph/graphdb/embedded/EmbeddedGraphConcurrentTest.java b/src/test/java/org/janusgraph/graphdb/embedded/EmbeddedGraphConcurrentTest.java new file mode 100644 index 0000000..fd6319f --- /dev/null +++ b/src/test/java/org/janusgraph/graphdb/embedded/EmbeddedGraphConcurrentTest.java @@ -0,0 +1,41 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.graphdb.embedded; + +import org.janusgraph.CassandraStorageSetup; +import org.janusgraph.TestCategory; +import org.janusgraph.diskstorage.configuration.WriteConfiguration; +import org.janusgraph.graphdb.JanusGraphConcurrentTest; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; + +/** + * @author Matthias Broecheler (me@matthiasb.com) + */ + +@Tag(TestCategory.PERFORMANCE_TESTS) +public class EmbeddedGraphConcurrentTest extends JanusGraphConcurrentTest { + + @BeforeAll + public static void startCassandra() { + CassandraStorageSetup.startCleanEmbedded(); + } + + @Override + public WriteConfiguration getConfiguration() { + return CassandraStorageSetup.getEmbeddedCassandraPartitionGraphConfiguration(getClass().getSimpleName()); + } + +} diff --git a/src/test/java/org/janusgraph/graphdb/embedded/EmbeddedGraphMemoryPerformanceTest.java b/src/test/java/org/janusgraph/graphdb/embedded/EmbeddedGraphMemoryPerformanceTest.java new file mode 100644 index 0000000..9b65a4a --- /dev/null +++ b/src/test/java/org/janusgraph/graphdb/embedded/EmbeddedGraphMemoryPerformanceTest.java @@ -0,0 +1,38 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.graphdb.embedded; + +import org.janusgraph.CassandraStorageSetup; +import org.janusgraph.diskstorage.configuration.WriteConfiguration; +import org.janusgraph.graphdb.JanusGraphPerformanceMemoryTest; +import org.junit.jupiter.api.BeforeAll; + +/** + * @author Matthias Broecheler (me@matthiasb.com) + */ + +public class EmbeddedGraphMemoryPerformanceTest extends JanusGraphPerformanceMemoryTest { + + @BeforeAll + public static void startCassandra() { + CassandraStorageSetup.startCleanEmbedded(); + } + + @Override + public WriteConfiguration getConfiguration() { + return CassandraStorageSetup.getEmbeddedCassandraPartitionGraphConfiguration(getClass().getSimpleName()); + } + +} diff --git a/src/test/java/org/janusgraph/graphdb/embedded/EmbeddedGraphTest.java b/src/test/java/org/janusgraph/graphdb/embedded/EmbeddedGraphTest.java new file mode 100644 index 0000000..b64ebfa --- /dev/null +++ b/src/test/java/org/janusgraph/graphdb/embedded/EmbeddedGraphTest.java @@ -0,0 +1,32 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.graphdb.embedded; + +import org.janusgraph.CassandraStorageSetup; +import org.janusgraph.diskstorage.configuration.WriteConfiguration; +import org.janusgraph.graphdb.CassandraGraphTest; +import org.junit.jupiter.api.BeforeAll; + +public class EmbeddedGraphTest extends CassandraGraphTest { + @BeforeAll + public static void startEmbeddedCassandra() { + CassandraStorageSetup.startCleanEmbedded(); + } + + @Override + public WriteConfiguration getConfiguration() { + return CassandraStorageSetup.getEmbeddedCassandraPartitionGraphConfiguration(getClass().getSimpleName()); + } +} diff --git a/src/test/java/org/janusgraph/graphdb/embedded/EmbeddedPartitionGraphTest.java b/src/test/java/org/janusgraph/graphdb/embedded/EmbeddedPartitionGraphTest.java new file mode 100644 index 0000000..cd724d9 --- /dev/null +++ b/src/test/java/org/janusgraph/graphdb/embedded/EmbeddedPartitionGraphTest.java @@ -0,0 +1,34 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.graphdb.embedded; + +import org.janusgraph.CassandraStorageSetup; +import org.janusgraph.diskstorage.configuration.WriteConfiguration; +import org.janusgraph.graphdb.JanusGraphPartitionGraphTest; +import org.junit.jupiter.api.BeforeAll; + +public class EmbeddedPartitionGraphTest extends JanusGraphPartitionGraphTest { + + @BeforeAll + public static void startEmbeddedCassandra() { + CassandraStorageSetup.startCleanEmbedded(); + } + + @Override + public WriteConfiguration getBaseConfiguration() { + return CassandraStorageSetup.getEmbeddedCassandraPartitionGraphConfiguration(getClass().getSimpleName()); + } + +} diff --git a/src/test/java/org/janusgraph/graphdb/thrift/ThriftConnectionTest.java b/src/test/java/org/janusgraph/graphdb/thrift/ThriftConnectionTest.java new file mode 100644 index 0000000..cba9d84 --- /dev/null +++ b/src/test/java/org/janusgraph/graphdb/thrift/ThriftConnectionTest.java @@ -0,0 +1,81 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.graphdb.thrift; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.google.common.collect.ImmutableMap; +import java.net.InetAddress; +import org.apache.cassandra.config.Config; +import org.apache.cassandra.config.KSMetaData; +import org.apache.cassandra.config.Schema; +import org.apache.cassandra.thrift.ThriftServer; +import org.apache.thrift.transport.TTransport; +import org.apache.thrift.transport.TTransportException; +import org.janusgraph.diskstorage.cassandra.thrift.thriftpool.CTConnection; +import org.janusgraph.diskstorage.cassandra.thrift.thriftpool.CTConnectionFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class ThriftConnectionTest { + + private static final int FRAME_SIZE = 15 * 1024 * 1024; + private static final int TIMEOUT_MS = 5 * 1000; + private static final int BACKLOG_PORT = 9098; + private static final int LISTEN_PORT = 9099; + + private final Logger log = LoggerFactory.getLogger(ThriftConnectionTest.class); + private CTConnectionFactory.Config factoryConfig; + + @BeforeEach + public void setUp() { + try { + Config.setClientMode(true); + Schema.instance.load(KSMetaData.newKeyspace("janusgraph", "SimpleStrategy", ImmutableMap.of("replication_factor", "1"), true)); + + log.debug("Starting ThriftServer for connection drop on port [{}]", LISTEN_PORT); + final InetAddress localhost = InetAddress.getLoopbackAddress(); + final ThriftServer server = new ThriftServer(localhost, LISTEN_PORT, BACKLOG_PORT); + server.start(); + log.debug("Started ThriftServer for connection drop on port [{}]", LISTEN_PORT); + + final String[] hosts = new String[] { localhost.getHostAddress() }; + factoryConfig = new CTConnectionFactory.Config(hosts, LISTEN_PORT, null, null) + .setTimeoutMS(TIMEOUT_MS) + .setFrameSize(FRAME_SIZE); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Test + public void testConnectionDropped() throws Exception { + CTConnectionFactory connectionFactory = spy(factoryConfig.build()); + CTConnection mockConnection = spy(connectionFactory.makeObject("janusgraph")); + + when(mockConnection.getConfig()).thenReturn(factoryConfig); + when(mockConnection.isOpen()).thenReturn(true); + TTransport mockClient = spy(mockConnection.getTransport()); + + assertTrue(connectionFactory.validateObject(null, mockConnection)); + when(mockClient.readAll(new byte[0], 0,0)).thenThrow(new TTransportException("Broken Pipe")); + assertTrue(mockClient.isOpen()); + } +} diff --git a/src/test/java/org/janusgraph/graphdb/thrift/ThriftEventualGraphTest.java b/src/test/java/org/janusgraph/graphdb/thrift/ThriftEventualGraphTest.java new file mode 100644 index 0000000..bd8e312 --- /dev/null +++ b/src/test/java/org/janusgraph/graphdb/thrift/ThriftEventualGraphTest.java @@ -0,0 +1,32 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.graphdb.thrift; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.diskstorage.configuration.WriteConfiguration; +import org.janusgraph.graphdb.JanusGraphEventualGraphTest; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +public class ThriftEventualGraphTest extends JanusGraphEventualGraphTest { + @Container + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Override + public WriteConfiguration getConfiguration() { + return thriftContainer.getThriftConfiguration(getClass().getSimpleName()).getConfiguration(); + } +} diff --git a/src/test/java/org/janusgraph/graphdb/thrift/ThriftGraphCacheTest.java b/src/test/java/org/janusgraph/graphdb/thrift/ThriftGraphCacheTest.java new file mode 100644 index 0000000..ea5f670 --- /dev/null +++ b/src/test/java/org/janusgraph/graphdb/thrift/ThriftGraphCacheTest.java @@ -0,0 +1,33 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.graphdb.thrift; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.StorageSetup; +import org.janusgraph.diskstorage.configuration.WriteConfiguration; +import org.janusgraph.graphdb.JanusGraphTest; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +public class ThriftGraphCacheTest extends JanusGraphTest { + @Container + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Override + public WriteConfiguration getConfiguration() { + return StorageSetup.addPermanentCache(thriftContainer.getThriftConfiguration(getClass().getSimpleName())); + } +} diff --git a/src/test/java/org/janusgraph/graphdb/thrift/ThriftGraphConcurrentTest.java b/src/test/java/org/janusgraph/graphdb/thrift/ThriftGraphConcurrentTest.java new file mode 100644 index 0000000..8b53314 --- /dev/null +++ b/src/test/java/org/janusgraph/graphdb/thrift/ThriftGraphConcurrentTest.java @@ -0,0 +1,35 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.graphdb.thrift; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.TestCategory; +import org.janusgraph.diskstorage.configuration.WriteConfiguration; +import org.janusgraph.graphdb.JanusGraphConcurrentTest; +import org.junit.jupiter.api.Tag; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Tag(TestCategory.PERFORMANCE_TESTS) +@Testcontainers +public class ThriftGraphConcurrentTest extends JanusGraphConcurrentTest { + @Container + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Override + public WriteConfiguration getConfiguration() { + return thriftContainer.getThriftConfiguration(getClass().getSimpleName()).getConfiguration(); + } +} diff --git a/src/test/java/org/janusgraph/graphdb/thrift/ThriftGraphIterativeTest.java b/src/test/java/org/janusgraph/graphdb/thrift/ThriftGraphIterativeTest.java new file mode 100644 index 0000000..696bf75 --- /dev/null +++ b/src/test/java/org/janusgraph/graphdb/thrift/ThriftGraphIterativeTest.java @@ -0,0 +1,42 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.graphdb.thrift; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.diskstorage.BackendException; +import org.janusgraph.diskstorage.cassandra.thrift.CassandraThriftStoreManager; +import org.janusgraph.diskstorage.configuration.BasicConfiguration; +import org.janusgraph.diskstorage.configuration.WriteConfiguration; +import org.janusgraph.diskstorage.keycolumnvalue.KeyColumnValueStoreManager; +import org.janusgraph.graphdb.JanusGraphIterativeBenchmark; +import org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +public class ThriftGraphIterativeTest extends JanusGraphIterativeBenchmark { + @Container + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Override + public WriteConfiguration getConfiguration() { + return thriftContainer.getThriftConfiguration(getClass().getSimpleName()).getConfiguration(); + } + + @Override + public KeyColumnValueStoreManager openStorageManager() throws BackendException { + return new CassandraThriftStoreManager(new BasicConfiguration(GraphDatabaseConfiguration.ROOT_NS,getConfiguration(), BasicConfiguration.Restriction.NONE)); + } +} diff --git a/src/test/java/org/janusgraph/graphdb/thrift/ThriftGraphPerformanceMemoryTest.java b/src/test/java/org/janusgraph/graphdb/thrift/ThriftGraphPerformanceMemoryTest.java new file mode 100644 index 0000000..8ca5eac --- /dev/null +++ b/src/test/java/org/janusgraph/graphdb/thrift/ThriftGraphPerformanceMemoryTest.java @@ -0,0 +1,32 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.graphdb.thrift; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.diskstorage.configuration.WriteConfiguration; +import org.janusgraph.graphdb.JanusGraphPerformanceMemoryTest; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +public class ThriftGraphPerformanceMemoryTest extends JanusGraphPerformanceMemoryTest { + @Container + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Override + public WriteConfiguration getConfiguration() { + return thriftContainer.getThriftConfiguration(getClass().getSimpleName()).getConfiguration(); + } +} diff --git a/src/test/java/org/janusgraph/graphdb/thrift/ThriftGraphTest.java b/src/test/java/org/janusgraph/graphdb/thrift/ThriftGraphTest.java new file mode 100644 index 0000000..9b6f0b8 --- /dev/null +++ b/src/test/java/org/janusgraph/graphdb/thrift/ThriftGraphTest.java @@ -0,0 +1,32 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.graphdb.thrift; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.diskstorage.configuration.WriteConfiguration; +import org.janusgraph.graphdb.CassandraGraphTest; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +public class ThriftGraphTest extends CassandraGraphTest { + @Container + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Override + public WriteConfiguration getConfiguration() { + return thriftContainer.getThriftConfiguration(getClass().getSimpleName()).getConfiguration(); + } +} diff --git a/src/test/java/org/janusgraph/graphdb/thrift/ThriftOLAPTest.java b/src/test/java/org/janusgraph/graphdb/thrift/ThriftOLAPTest.java new file mode 100644 index 0000000..0a4f810 --- /dev/null +++ b/src/test/java/org/janusgraph/graphdb/thrift/ThriftOLAPTest.java @@ -0,0 +1,32 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.graphdb.thrift; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.diskstorage.configuration.WriteConfiguration; +import org.janusgraph.olap.OLAPTest; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +public class ThriftOLAPTest extends OLAPTest { + @Container + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Override + public WriteConfiguration getConfiguration() { + return thriftContainer.getThriftConfiguration(getClass().getSimpleName()).getConfiguration(); + } +} diff --git a/src/test/java/org/janusgraph/graphdb/thrift/ThriftOperationCountingTest.java b/src/test/java/org/janusgraph/graphdb/thrift/ThriftOperationCountingTest.java new file mode 100644 index 0000000..497f155 --- /dev/null +++ b/src/test/java/org/janusgraph/graphdb/thrift/ThriftOperationCountingTest.java @@ -0,0 +1,32 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.graphdb.thrift; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.diskstorage.configuration.WriteConfiguration; +import org.janusgraph.graphdb.JanusGraphOperationCountingTest; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +public class ThriftOperationCountingTest extends JanusGraphOperationCountingTest { + @Container + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Override + public WriteConfiguration getBaseConfiguration() { + return thriftContainer.getThriftConfiguration(getClass().getSimpleName()).getConfiguration(); + } +} diff --git a/src/test/java/org/janusgraph/graphdb/thrift/ThriftPartitionGraphTest.java b/src/test/java/org/janusgraph/graphdb/thrift/ThriftPartitionGraphTest.java new file mode 100644 index 0000000..32788a2 --- /dev/null +++ b/src/test/java/org/janusgraph/graphdb/thrift/ThriftPartitionGraphTest.java @@ -0,0 +1,33 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.graphdb.thrift; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.diskstorage.configuration.WriteConfiguration; +import org.janusgraph.graphdb.JanusGraphPartitionGraphTest; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +public class ThriftPartitionGraphTest extends JanusGraphPartitionGraphTest { + + @Container + public static final JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(); + + @Override + public WriteConfiguration getBaseConfiguration() { + return thriftContainer.getThriftConfiguration(getClass().getSimpleName()).getConfiguration(); + } +} diff --git a/src/test/java/org/janusgraph/hadoop/CassandraIndexManagementIT.java b/src/test/java/org/janusgraph/hadoop/CassandraIndexManagementIT.java new file mode 100644 index 0000000..d2c3ceb --- /dev/null +++ b/src/test/java/org/janusgraph/hadoop/CassandraIndexManagementIT.java @@ -0,0 +1,31 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.hadoop; + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.diskstorage.configuration.WriteConfiguration; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +public class CassandraIndexManagementIT extends AbstractIndexManagementIT { + @Container + private static JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(true); + + @Override + public WriteConfiguration getConfiguration() { + return thriftContainer.getThriftConfiguration(getClass().getSimpleName().toLowerCase()).getConfiguration(); + } +} diff --git a/src/test/java/org/janusgraph/hadoop/CassandraInputFormatIT.java b/src/test/java/org/janusgraph/hadoop/CassandraInputFormatIT.java new file mode 100644 index 0000000..da65db5 --- /dev/null +++ b/src/test/java/org/janusgraph/hadoop/CassandraInputFormatIT.java @@ -0,0 +1,55 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.hadoop; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.diskstorage.configuration.WriteConfiguration; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.util.GraphFactory; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +@Testcontainers +public class CassandraInputFormatIT extends AbstractInputFormatIT { + + @Container + public static JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(true); + + protected PropertiesConfiguration getGraphConfiguration() throws ConfigurationException, IOException { + final PropertiesConfiguration config = new PropertiesConfiguration("target/test-classes/cassandra-read.properties"); + Path baseOutDir = Paths.get((String) config.getProperty("gremlin.hadoop.outputLocation")); + baseOutDir.toFile().mkdirs(); + String outDir = Files.createTempDirectory(baseOutDir, null).toAbsolutePath().toString(); + config.setProperty("gremlin.hadoop.outputLocation", outDir); + config.setProperty("janusgraphmr.ioformat.conf.storage.port", String.valueOf(thriftContainer.getMappedThirftPort())); + return config; + } + + protected Graph getGraph() throws ConfigurationException, IOException { + return GraphFactory.open(getGraphConfiguration()); + } + + @Override + public WriteConfiguration getConfiguration() { + return thriftContainer.getThriftConfiguration(getClass().getSimpleName()).getConfiguration(); + } +} diff --git a/src/test/java/org/janusgraph/hadoop/CassandraScanJobIT.java b/src/test/java/org/janusgraph/hadoop/CassandraScanJobIT.java new file mode 100644 index 0000000..fbe2672 --- /dev/null +++ b/src/test/java/org/janusgraph/hadoop/CassandraScanJobIT.java @@ -0,0 +1,170 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.janusgraph.hadoop; + + +import org.janusgraph.JanusGraphCassandraThriftContainer; +import org.janusgraph.core.JanusGraphVertex; +import org.janusgraph.diskstorage.*; +import org.janusgraph.diskstorage.cassandra.thrift.CassandraThriftStoreManager; +import org.janusgraph.diskstorage.configuration.*; +import org.janusgraph.diskstorage.keycolumnvalue.KeyColumnValueStore; +import org.janusgraph.diskstorage.keycolumnvalue.KeyColumnValueStoreManager; +import org.janusgraph.diskstorage.keycolumnvalue.StoreTransaction; +import org.janusgraph.diskstorage.keycolumnvalue.scan.ScanJob; +import org.janusgraph.diskstorage.util.StandardBaseTransactionConfig; +import org.janusgraph.diskstorage.util.time.TimestampProviders; +import org.janusgraph.graphdb.JanusGraphBaseTest; +import org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration; +import org.janusgraph.hadoop.config.JanusGraphHadoopConfiguration; +import org.janusgraph.hadoop.formats.cassandra.CassandraInputFormat; +import org.janusgraph.hadoop.scan.CassandraHadoopScanRunner; +import org.janusgraph.hadoop.scan.HadoopScanMapper; +import org.apache.hadoop.io.NullWritable; +import org.apache.hadoop.mapreduce.Job; +import org.apache.hadoop.mapreduce.lib.output.NullOutputFormat; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.io.IOException; +import java.util.Arrays; +import java.util.concurrent.ExecutionException; + +import static org.junit.jupiter.api.Assertions.*; + +@Testcontainers +public class CassandraScanJobIT extends JanusGraphBaseTest { + + @Container + private static JanusGraphCassandraThriftContainer thriftContainer = new JanusGraphCassandraThriftContainer(true); + + private static final Logger log = LoggerFactory.getLogger(CassandraScanJobIT.class); + + @Test + public void testSimpleScan() + throws InterruptedException, ExecutionException, IOException, BackendException { + + int keys = 1000; + int cols = 40; + + String[][] values = KeyValueStoreUtil.generateData(keys, cols); + //Make it only half the number of columns for every 2nd key + for (int i = 0; i < values.length; i++) { + if (i%2==0) values[i]= Arrays.copyOf(values[i], cols / 2); + } + log.debug("Loading values: " + keys + "x" + cols); + + KeyColumnValueStoreManager mgr = new CassandraThriftStoreManager(GraphDatabaseConfiguration.buildGraphConfiguration()); + KeyColumnValueStore store = mgr.openDatabase("edgestore"); + StoreTransaction tx = mgr.beginTransaction(StandardBaseTransactionConfig.of(TimestampProviders.MICRO)); + KeyColumnValueStoreUtil.loadValues(store, tx, values); + tx.commit(); // noop on Cassandra, but harmless + + SimpleScanJobRunner runner = (ScanJob job, Configuration jobConf, String rootNSName) -> { + try { + return new CassandraHadoopScanRunner(job).scanJobConf(jobConf).scanJobConfRoot(rootNSName) + .partitionerOverride("org.apache.cassandra.dht.Murmur3Partitioner").run(); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + }; + + SimpleScanJob.runBasicTests(keys, cols, runner); + } + + @Test + public void testPartitionedVertexScan() throws Exception { + tearDown(); + clearGraph(getConfiguration()); + WriteConfiguration partConf = getConfiguration(); + open(partConf); + mgmt.makeVertexLabel("part").partition().make(); + finishSchema(); + JanusGraphVertex supernode = graph.addVertex("part"); + for (int i = 0; i < 128; i++) { + JanusGraphVertex v = graph.addVertex("part"); + v.addEdge("default", supernode); + if (0 < i && 0 == i % 4) + graph.tx().commit(); + } + graph.tx().commit(); + + org.apache.hadoop.conf.Configuration c = new org.apache.hadoop.conf.Configuration(); + c.set(ConfigElement.getPath(JanusGraphHadoopConfiguration.GRAPH_CONFIG_KEYS, true) + "." + "storage.cassandra.keyspace", getClass().getSimpleName()); + c.set(ConfigElement.getPath(JanusGraphHadoopConfiguration.GRAPH_CONFIG_KEYS, true) + "." + "storage.backend", "cassandrathrift"); + c.set(ConfigElement.getPath(JanusGraphHadoopConfiguration.GRAPH_CONFIG_KEYS, true) + "." + "storage.port", String.valueOf(thriftContainer.getMappedThirftPort())); + c.set("cassandra.input.partitioner.class", "org.apache.cassandra.dht.Murmur3Partitioner"); + + Job job = getVertexJobWithDefaultMapper(c); + + // Should throw an exception since filter-partitioned-vertices wasn't enabled + assertFalse(job.waitForCompletion(true)); + } + + @Test + public void testPartitionedVertexFilteredScan() throws Exception { + tearDown(); + clearGraph(getConfiguration()); + WriteConfiguration partConf = getConfiguration(); + open(partConf); + mgmt.makeVertexLabel("part").partition().make(); + finishSchema(); + JanusGraphVertex supernode = graph.addVertex("part"); + for (int i = 0; i < 128; i++) { + JanusGraphVertex v = graph.addVertex("part"); + v.addEdge("default", supernode); + if (0 < i && 0 == i % 4) + graph.tx().commit(); + } + graph.tx().commit(); + + org.apache.hadoop.conf.Configuration c = new org.apache.hadoop.conf.Configuration(); + c.set(ConfigElement.getPath(JanusGraphHadoopConfiguration.GRAPH_CONFIG_KEYS, true) + "." + "storage.cassandra.keyspace", getClass().getSimpleName()); + c.set(ConfigElement.getPath(JanusGraphHadoopConfiguration.GRAPH_CONFIG_KEYS, true) + "." + "storage.backend", "cassandrathrift"); + c.set(ConfigElement.getPath(JanusGraphHadoopConfiguration.GRAPH_CONFIG_KEYS, true) + "." + "storage.port", String.valueOf(thriftContainer.getMappedThirftPort())); + c.set(ConfigElement.getPath(JanusGraphHadoopConfiguration.FILTER_PARTITIONED_VERTICES, true), "true"); + c.set("cassandra.input.partitioner.class", "org.apache.cassandra.dht.Murmur3Partitioner"); + + Job job = getVertexJobWithDefaultMapper(c); + + // Should succeed + assertTrue(job.waitForCompletion(true)); + } + + private Job getVertexJobWithDefaultMapper(org.apache.hadoop.conf.Configuration c) throws IOException { + + Job job = Job.getInstance(c); + + job.setJarByClass(HadoopScanMapper.class); + job.setJobName("testPartitionedVertexScan"); + job.setOutputKeyClass(NullWritable.class); + job.setOutputValueClass(NullWritable.class); + job.setMapOutputKeyClass(NullWritable.class); + job.setMapOutputValueClass(NullWritable.class); + job.setNumReduceTasks(0); + job.setOutputFormatClass(NullOutputFormat.class); + job.setInputFormatClass(CassandraInputFormat.class); + + return job; + } + + @Override + public WriteConfiguration getConfiguration() { + return thriftContainer.getThriftConfiguration(getClass().getSimpleName()).getConfiguration(); + } +} diff --git a/src/test/resources/cassandra-read.properties b/src/test/resources/cassandra-read.properties new file mode 100644 index 0000000..0c7515f --- /dev/null +++ b/src/test/resources/cassandra-read.properties @@ -0,0 +1,35 @@ +# Copyright 2019 JanusGraph Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +gremlin.graph=org.apache.tinkerpop.gremlin.hadoop.structure.HadoopGraph +gremlin.hadoop.graphReader=org.janusgraph.hadoop.formats.cassandra.CassandraInputFormat +gremlin.hadoop.graphWriter=org.apache.hadoop.mapreduce.lib.output.NullOutputFormat +#gremlin.hadoop.graphWriter=org.apache.hadoop.mapreduce.lib.output.SequenceFileOutputFormat + +gremlin.hadoop.jarsInDistributedCache=true +gremlin.hadoop.inputLocation=none +gremlin.hadoop.outputLocation=target/output +janusgraphmr.ioformat.conf.storage.backend=cassandrathrift +janusgraphmr.ioformat.conf.storage.cassandra.keyspace=CassandraInputFormatIT +janusgraphmr.ioformat.conf.storage.hostname=localhost +janusgraphmr.ioformat.conf.storage.port=9160 +#################################### +# SparkGraphComputer Configuration # +#################################### +spark.master=local[4] +spark.executor.memory=1g +spark.serializer=org.apache.spark.serializer.KryoSerializer +spark.kryo.registrator=org.janusgraph.hadoop.serialize.JanusGraphKryoRegistrator +cassandra.input.partitioner.class=org.apache.cassandra.dht.Murmur3Partitioner +cassandra.input.widerows=true diff --git a/src/test/resources/cassandra2-byteordered.yaml b/src/test/resources/cassandra2-byteordered.yaml new file mode 100644 index 0000000..9382508 --- /dev/null +++ b/src/test/resources/cassandra2-byteordered.yaml @@ -0,0 +1,106 @@ +# Copyright 2019 JanusGraph Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# Cassandra storage config YAML +cluster_name: 'Test Cluster' +initial_token: 0000000000000000000000000000000000 +hinted_handoff_enabled: false +max_hint_window_in_ms: 10800000 # 3 hours +hinted_handoff_throttle_in_kb: 1024 +max_hints_delivery_threads: 2 +batchlog_replay_throttle_in_kb: 1024 +authenticator: AllowAllAuthenticator +authorizer: AllowAllAuthorizer +permissions_validity_in_ms: 2000 +partitioner: org.apache.cassandra.dht.ByteOrderedPartitioner +data_file_directories: + - /var/lib/cassandra/data +commitlog_directory: /var/lib/cassandra/commitlog +disk_failure_policy: stop +commit_failure_policy: stop +key_cache_size_in_mb: +key_cache_save_period: 14400 +row_cache_size_in_mb: 0 +row_cache_save_period: 0 +counter_cache_size_in_mb: +counter_cache_save_period: 7200 +saved_caches_directory: /var/lib/cassandra/saved_caches +commitlog_sync: periodic +commitlog_sync_period_in_ms: 10000 +commitlog_segment_size_in_mb: 32 +seed_provider: + - class_name: org.apache.cassandra.locator.SimpleSeedProvider + parameters: + - seeds: "127.0.0.1" +concurrent_reads: 32 +concurrent_writes: 32 +concurrent_counter_writes: 32 +memtable_allocation_type: offheap_objects +index_summary_capacity_in_mb: +index_summary_resize_interval_in_minutes: 60 +trickle_fsync: false +trickle_fsync_interval_in_kb: 10240 +storage_port: 7000 +ssl_storage_port: 7001 +listen_address: +broadcast_address: 127.0.0.1 +start_native_transport: true +native_transport_port: 9042 +start_rpc: true +rpc_address: 0.0.0.0 +rpc_port: 9160 +broadcast_rpc_address: 127.0.0.1 +rpc_keepalive: true +rpc_server_type: sync +thrift_framed_transport_size_in_mb: 15 +incremental_backups: false +snapshot_before_compaction: false +auto_snapshot: true +column_index_size_in_kb: 64 +compaction_throughput_mb_per_sec: 16 +sstable_preemptive_open_interval_in_mb: 50 +read_request_timeout_in_ms: 5000 +range_request_timeout_in_ms: 10000 +write_request_timeout_in_ms: 2000 +counter_write_request_timeout_in_ms: 5000 +cas_contention_timeout_in_ms: 1000 +truncate_request_timeout_in_ms: 60000 +request_timeout_in_ms: 10000 +cross_node_timeout: false +endpoint_snitch: SimpleSnitch +dynamic_snitch_update_interval_in_ms: 100 +dynamic_snitch_reset_interval_in_ms: 600000 +dynamic_snitch_badness_threshold: 0.1 +request_scheduler: org.apache.cassandra.scheduler.NoScheduler +server_encryption_options: + internode_encryption: none + keystore: conf/.keystore + keystore_password: cassandra + truststore: conf/.truststore + truststore_password: cassandra +client_encryption_options: + enabled: false + # If enabled and optional is set to true encrypted and unencrypted connections are handled. + optional: false + keystore: conf/.keystore + keystore_password: cassandra +internode_compression: dc +inter_dc_tcp_nodelay: false +tombstone_warn_threshold: 1000 +tombstone_failure_threshold: 100000 +batch_size_warn_threshold_in_kb: 5 +unlogged_batch_across_partitions_warn_threshold: 10 +compaction_large_partition_warning_threshold_mb: 100 +gc_warn_threshold_in_ms: 1000 diff --git a/src/test/resources/cassandra2-murmur-ssl.yaml b/src/test/resources/cassandra2-murmur-ssl.yaml new file mode 100644 index 0000000..7adeb02 --- /dev/null +++ b/src/test/resources/cassandra2-murmur-ssl.yaml @@ -0,0 +1,106 @@ +# Copyright 2019 JanusGraph Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# Cassandra storage config YAML +cluster_name: 'Test Cluster' +num_tokens: 4 +hinted_handoff_enabled: false +max_hint_window_in_ms: 10800000 # 3 hours +hinted_handoff_throttle_in_kb: 1024 +max_hints_delivery_threads: 2 +batchlog_replay_throttle_in_kb: 1024 +authenticator: AllowAllAuthenticator +authorizer: AllowAllAuthorizer +permissions_validity_in_ms: 2000 +partitioner: org.apache.cassandra.dht.Murmur3Partitioner +data_file_directories: + - /var/lib/cassandra/data +commitlog_directory: /var/lib/cassandra/commitlog +disk_failure_policy: stop +commit_failure_policy: stop +key_cache_size_in_mb: +key_cache_save_period: 14400 +row_cache_size_in_mb: 0 +row_cache_save_period: 0 +counter_cache_size_in_mb: +counter_cache_save_period: 7200 +saved_caches_directory: /var/lib/cassandra/saved_caches +commitlog_sync: periodic +commitlog_sync_period_in_ms: 10000 +commitlog_segment_size_in_mb: 32 +seed_provider: + - class_name: org.apache.cassandra.locator.SimpleSeedProvider + parameters: + - seeds: "127.0.0.1" +concurrent_reads: 32 +concurrent_writes: 32 +concurrent_counter_writes: 32 +memtable_allocation_type: offheap_objects +index_summary_capacity_in_mb: +index_summary_resize_interval_in_minutes: 60 +trickle_fsync: false +trickle_fsync_interval_in_kb: 10240 +storage_port: 7000 +ssl_storage_port: 7001 +listen_address: +broadcast_address: 127.0.0.1 +start_native_transport: true +native_transport_port: 9042 +start_rpc: true +rpc_address: 0.0.0.0 +rpc_port: 9160 +broadcast_rpc_address: 127.0.0.1 +rpc_keepalive: true +rpc_server_type: sync +thrift_framed_transport_size_in_mb: 15 +incremental_backups: false +snapshot_before_compaction: false +auto_snapshot: true +column_index_size_in_kb: 64 +compaction_throughput_mb_per_sec: 16 +sstable_preemptive_open_interval_in_mb: 50 +read_request_timeout_in_ms: 5000 +range_request_timeout_in_ms: 10000 +write_request_timeout_in_ms: 2000 +counter_write_request_timeout_in_ms: 5000 +cas_contention_timeout_in_ms: 1000 +truncate_request_timeout_in_ms: 60000 +request_timeout_in_ms: 10000 +cross_node_timeout: false +endpoint_snitch: SimpleSnitch +dynamic_snitch_update_interval_in_ms: 100 +dynamic_snitch_reset_interval_in_ms: 600000 +dynamic_snitch_badness_threshold: 0.1 +request_scheduler: org.apache.cassandra.scheduler.NoScheduler +server_encryption_options: + internode_encryption: none + keystore: conf/.keystore + keystore_password: cassandra + truststore: conf/.truststore + truststore_password: cassandra +client_encryption_options: + enabled: true + # If enabled and optional is set to true encrypted and unencrypted connections are handled. + optional: false + keystore: /etc/ssl/test.keystore + keystore_password: cassandra +internode_compression: dc +inter_dc_tcp_nodelay: false +tombstone_warn_threshold: 1000 +tombstone_failure_threshold: 100000 +batch_size_warn_threshold_in_kb: 5 +unlogged_batch_across_partitions_warn_threshold: 10 +compaction_large_partition_warning_threshold_mb: 100 +gc_warn_threshold_in_ms: 1000 diff --git a/src/test/resources/cassandra2-murmur.yaml b/src/test/resources/cassandra2-murmur.yaml new file mode 100644 index 0000000..3cc62a8 --- /dev/null +++ b/src/test/resources/cassandra2-murmur.yaml @@ -0,0 +1,106 @@ +# Copyright 2019 JanusGraph Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# Cassandra storage config YAML +cluster_name: 'Test Cluster' +num_tokens: 4 +hinted_handoff_enabled: false +max_hint_window_in_ms: 10800000 # 3 hours +hinted_handoff_throttle_in_kb: 1024 +max_hints_delivery_threads: 2 +batchlog_replay_throttle_in_kb: 1024 +authenticator: AllowAllAuthenticator +authorizer: AllowAllAuthorizer +permissions_validity_in_ms: 2000 +partitioner: org.apache.cassandra.dht.Murmur3Partitioner +data_file_directories: + - /var/lib/cassandra/data +commitlog_directory: /var/lib/cassandra/commitlog +disk_failure_policy: stop +commit_failure_policy: stop +key_cache_size_in_mb: +key_cache_save_period: 14400 +row_cache_size_in_mb: 0 +row_cache_save_period: 0 +counter_cache_size_in_mb: +counter_cache_save_period: 7200 +saved_caches_directory: /var/lib/cassandra/saved_caches +commitlog_sync: periodic +commitlog_sync_period_in_ms: 10000 +commitlog_segment_size_in_mb: 32 +seed_provider: + - class_name: org.apache.cassandra.locator.SimpleSeedProvider + parameters: + - seeds: "127.0.0.1" +concurrent_reads: 32 +concurrent_writes: 32 +concurrent_counter_writes: 32 +memtable_allocation_type: offheap_objects +index_summary_capacity_in_mb: +index_summary_resize_interval_in_minutes: 60 +trickle_fsync: false +trickle_fsync_interval_in_kb: 10240 +storage_port: 7000 +ssl_storage_port: 7001 +listen_address: +broadcast_address: 127.0.0.1 +start_native_transport: true +native_transport_port: 9042 +start_rpc: true +rpc_address: 0.0.0.0 +rpc_port: 9160 +broadcast_rpc_address: 127.0.0.1 +rpc_keepalive: true +rpc_server_type: sync +thrift_framed_transport_size_in_mb: 15 +incremental_backups: false +snapshot_before_compaction: false +auto_snapshot: true +column_index_size_in_kb: 64 +compaction_throughput_mb_per_sec: 16 +sstable_preemptive_open_interval_in_mb: 50 +read_request_timeout_in_ms: 5000 +range_request_timeout_in_ms: 10000 +write_request_timeout_in_ms: 2000 +counter_write_request_timeout_in_ms: 5000 +cas_contention_timeout_in_ms: 1000 +truncate_request_timeout_in_ms: 60000 +request_timeout_in_ms: 10000 +cross_node_timeout: false +endpoint_snitch: SimpleSnitch +dynamic_snitch_update_interval_in_ms: 100 +dynamic_snitch_reset_interval_in_ms: 600000 +dynamic_snitch_badness_threshold: 0.1 +request_scheduler: org.apache.cassandra.scheduler.NoScheduler +server_encryption_options: + internode_encryption: none + keystore: conf/.keystore + keystore_password: cassandra + truststore: conf/.truststore + truststore_password: cassandra +client_encryption_options: + enabled: false + # If enabled and optional is set to true encrypted and unencrypted connections are handled. + optional: false + keystore: conf/.keystore + keystore_password: cassandra +internode_compression: dc +inter_dc_tcp_nodelay: false +tombstone_warn_threshold: 1000 +tombstone_failure_threshold: 100000 +batch_size_warn_threshold_in_kb: 5 +unlogged_batch_across_partitions_warn_threshold: 10 +compaction_large_partition_warning_threshold_mb: 100 +gc_warn_threshold_in_ms: 1000 diff --git a/src/test/resources/cassandra3-byteordered.yaml b/src/test/resources/cassandra3-byteordered.yaml new file mode 100644 index 0000000..004da66 --- /dev/null +++ b/src/test/resources/cassandra3-byteordered.yaml @@ -0,0 +1,119 @@ +# Copyright 2019 JanusGraph Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# Cassandra storage config YAML +cluster_name: 'Test Cluster' +initial_token: 0000000000000000000000000000000000 +hinted_handoff_enabled: false +max_hint_window_in_ms: 10800000 # 3 hours +hinted_handoff_throttle_in_kb: 1024 +max_hints_delivery_threads: 2 +hints_flush_period_in_ms: 10000 +max_hints_file_size_in_mb: 128 +batchlog_replay_throttle_in_kb: 1024 +authenticator: AllowAllAuthenticator +authorizer: AllowAllAuthorizer +role_manager: CassandraRoleManager +roles_validity_in_ms: 2000 +permissions_validity_in_ms: 2000 +partitioner: org.apache.cassandra.dht.ByteOrderedPartitioner +data_file_directories: + - /var/lib/cassandra/data +commitlog_directory: /var/lib/cassandra/commitlog +disk_failure_policy: stop +commit_failure_policy: stop +key_cache_size_in_mb: +key_cache_save_period: 14400 +row_cache_size_in_mb: 0 +row_cache_save_period: 0 +counter_cache_size_in_mb: +counter_cache_save_period: 7200 +saved_caches_directory: /var/lib/cassandra/saved_caches +commitlog_sync: periodic +commitlog_sync_period_in_ms: 10000 +commitlog_segment_size_in_mb: 32 +seed_provider: + - class_name: org.apache.cassandra.locator.SimpleSeedProvider + parameters: + - seeds: "127.0.0.1" +concurrent_reads: 32 +concurrent_writes: 32 +concurrent_counter_writes: 32 +concurrent_materialized_view_writes: 32 +memtable_allocation_type: heap_buffers +index_summary_capacity_in_mb: +index_summary_resize_interval_in_minutes: 60 +trickle_fsync: false +trickle_fsync_interval_in_kb: 10240 +storage_port: 7000 +ssl_storage_port: 7001 +listen_address: +broadcast_address: 127.0.0.1 +start_native_transport: true +native_transport_port: 9042 +start_rpc: true +rpc_address: 0.0.0.0 +rpc_port: 9160 +broadcast_rpc_address: 127.0.0.1 +rpc_keepalive: true +rpc_server_type: sync +thrift_framed_transport_size_in_mb: 15 +incremental_backups: false +snapshot_before_compaction: false +auto_snapshot: true +column_index_size_in_kb: 64 +compaction_throughput_mb_per_sec: 16 +sstable_preemptive_open_interval_in_mb: 50 +read_request_timeout_in_ms: 5000 +range_request_timeout_in_ms: 10000 +write_request_timeout_in_ms: 2000 +counter_write_request_timeout_in_ms: 5000 +cas_contention_timeout_in_ms: 1000 +truncate_request_timeout_in_ms: 60000 +request_timeout_in_ms: 10000 +cross_node_timeout: false +endpoint_snitch: SimpleSnitch +dynamic_snitch_update_interval_in_ms: 100 +dynamic_snitch_reset_interval_in_ms: 600000 +dynamic_snitch_badness_threshold: 0.1 +request_scheduler: org.apache.cassandra.scheduler.NoScheduler + +server_encryption_options: + internode_encryption: none + keystore: conf/.keystore + keystore_password: cassandra + truststore: conf/.truststore + truststore_password: cassandra +client_encryption_options: + enabled: false + optional: false + keystore: conf/.keystore + keystore_password: cassandra + +internode_compression: dc +inter_dc_tcp_nodelay: false +tracetype_query_ttl: 86400 +tracetype_repair_ttl: 604800 +enable_user_defined_functions: false +enable_scripted_user_defined_functions: false +enable_materialized_views: true +windows_timer_interval: 1 +tombstone_warn_threshold: 1000 +tombstone_failure_threshold: 100000 +batch_size_warn_threshold_in_kb: 5 +batch_size_fail_threshold_in_kb: 50 +unlogged_batch_across_partitions_warn_threshold: 10 +compaction_large_partition_warning_threshold_mb: 100 +gc_warn_threshold_in_ms: 1000 diff --git a/src/test/resources/cassandra3-murmur-ssl.yaml b/src/test/resources/cassandra3-murmur-ssl.yaml new file mode 100644 index 0000000..51ef169 --- /dev/null +++ b/src/test/resources/cassandra3-murmur-ssl.yaml @@ -0,0 +1,120 @@ +# Copyright 2019 JanusGraph Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# Cassandra storage config YAML +cluster_name: 'Test Cluster' +num_tokens: 4 +hinted_handoff_enabled: false +max_hint_window_in_ms: 10800000 # 3 hours +hinted_handoff_throttle_in_kb: 1024 +max_hints_delivery_threads: 2 +hints_flush_period_in_ms: 10000 +max_hints_file_size_in_mb: 128 +batchlog_replay_throttle_in_kb: 1024 +authenticator: AllowAllAuthenticator +authorizer: AllowAllAuthorizer +role_manager: CassandraRoleManager +roles_validity_in_ms: 2000 +permissions_validity_in_ms: 2000 +partitioner: org.apache.cassandra.dht.Murmur3Partitioner +data_file_directories: + - /var/lib/cassandra/data +commitlog_directory: /var/lib/cassandra/commitlog +disk_failure_policy: stop +commit_failure_policy: stop +key_cache_size_in_mb: +key_cache_save_period: 14400 +row_cache_size_in_mb: 0 +row_cache_save_period: 0 +counter_cache_size_in_mb: +counter_cache_save_period: 7200 +saved_caches_directory: /var/lib/cassandra/saved_caches +commitlog_sync: periodic +commitlog_sync_period_in_ms: 10000 +commitlog_segment_size_in_mb: 32 +seed_provider: + - class_name: org.apache.cassandra.locator.SimpleSeedProvider + parameters: + - seeds: "127.0.0.1" +concurrent_reads: 32 +concurrent_writes: 32 +concurrent_counter_writes: 32 +concurrent_materialized_view_writes: 32 +memtable_allocation_type: heap_buffers +index_summary_capacity_in_mb: +index_summary_resize_interval_in_minutes: 60 +trickle_fsync: false +trickle_fsync_interval_in_kb: 10240 +storage_port: 7000 +ssl_storage_port: 7001 +listen_address: +broadcast_address: 127.0.0.1 +start_native_transport: true +native_transport_port: 9042 +start_rpc: true +rpc_address: 0.0.0.0 +rpc_port: 9160 +broadcast_rpc_address: 127.0.0.1 +rpc_keepalive: true +rpc_server_type: sync +thrift_framed_transport_size_in_mb: 15 +incremental_backups: false +snapshot_before_compaction: false +auto_snapshot: true +column_index_size_in_kb: 64 +compaction_throughput_mb_per_sec: 16 +sstable_preemptive_open_interval_in_mb: 50 +read_request_timeout_in_ms: 5000 +range_request_timeout_in_ms: 10000 +write_request_timeout_in_ms: 2000 +counter_write_request_timeout_in_ms: 5000 +cas_contention_timeout_in_ms: 1000 +truncate_request_timeout_in_ms: 60000 +request_timeout_in_ms: 10000 +cross_node_timeout: false +endpoint_snitch: SimpleSnitch +dynamic_snitch_update_interval_in_ms: 100 +dynamic_snitch_reset_interval_in_ms: 600000 +dynamic_snitch_badness_threshold: 0.1 +request_scheduler: org.apache.cassandra.scheduler.NoScheduler + +server_encryption_options: + internode_encryption: none + keystore: conf/.keystore + keystore_password: cassandra + truststore: conf/.truststore + truststore_password: cassandra +client_encryption_options: + enabled: true + # If enabled and optional is set to true encrypted and unencrypted connections are handled. + optional: false + keystore: /etc/ssl/test.keystore + keystore_password: cassandra + +internode_compression: dc +inter_dc_tcp_nodelay: false +tracetype_query_ttl: 86400 +tracetype_repair_ttl: 604800 +enable_user_defined_functions: false +enable_scripted_user_defined_functions: false +enable_materialized_views: true +windows_timer_interval: 1 +tombstone_warn_threshold: 1000 +tombstone_failure_threshold: 100000 +batch_size_warn_threshold_in_kb: 5 +batch_size_fail_threshold_in_kb: 50 +unlogged_batch_across_partitions_warn_threshold: 10 +compaction_large_partition_warning_threshold_mb: 100 +gc_warn_threshold_in_ms: 1000 diff --git a/src/test/resources/cassandra3-murmur.yaml b/src/test/resources/cassandra3-murmur.yaml new file mode 100644 index 0000000..bba50c8 --- /dev/null +++ b/src/test/resources/cassandra3-murmur.yaml @@ -0,0 +1,120 @@ +# Copyright 2019 JanusGraph Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# Cassandra storage config YAML +cluster_name: 'Test Cluster' +num_tokens: 4 +hinted_handoff_enabled: false +max_hint_window_in_ms: 10800000 # 3 hours +hinted_handoff_throttle_in_kb: 1024 +max_hints_delivery_threads: 2 +hints_flush_period_in_ms: 10000 +max_hints_file_size_in_mb: 128 +batchlog_replay_throttle_in_kb: 1024 +authenticator: AllowAllAuthenticator +authorizer: AllowAllAuthorizer +role_manager: CassandraRoleManager +roles_validity_in_ms: 2000 +permissions_validity_in_ms: 2000 +partitioner: org.apache.cassandra.dht.Murmur3Partitioner +data_file_directories: + - /var/lib/cassandra/data +commitlog_directory: /var/lib/cassandra/commitlog +disk_failure_policy: stop +commit_failure_policy: stop +key_cache_size_in_mb: +key_cache_save_period: 14400 +row_cache_size_in_mb: 0 +row_cache_save_period: 0 +counter_cache_size_in_mb: +counter_cache_save_period: 7200 +saved_caches_directory: /var/lib/cassandra/saved_caches +commitlog_sync: periodic +commitlog_sync_period_in_ms: 10000 +commitlog_segment_size_in_mb: 32 +seed_provider: + - class_name: org.apache.cassandra.locator.SimpleSeedProvider + parameters: + - seeds: "127.0.0.1" +concurrent_reads: 32 +concurrent_writes: 32 +concurrent_counter_writes: 32 +concurrent_materialized_view_writes: 32 +memtable_allocation_type: heap_buffers +index_summary_capacity_in_mb: +index_summary_resize_interval_in_minutes: 60 +trickle_fsync: false +trickle_fsync_interval_in_kb: 10240 +storage_port: 7000 +ssl_storage_port: 7001 +listen_address: +broadcast_address: 127.0.0.1 +start_native_transport: true +native_transport_port: 9042 +start_rpc: true +rpc_address: 0.0.0.0 +rpc_port: 9160 +broadcast_rpc_address: 127.0.0.1 +rpc_keepalive: true +rpc_server_type: sync +thrift_framed_transport_size_in_mb: 15 +incremental_backups: false +snapshot_before_compaction: false +auto_snapshot: true +column_index_size_in_kb: 64 +compaction_throughput_mb_per_sec: 16 +sstable_preemptive_open_interval_in_mb: 50 +read_request_timeout_in_ms: 5000 +range_request_timeout_in_ms: 10000 +write_request_timeout_in_ms: 2000 +counter_write_request_timeout_in_ms: 5000 +cas_contention_timeout_in_ms: 1000 +truncate_request_timeout_in_ms: 60000 +request_timeout_in_ms: 10000 +cross_node_timeout: false +endpoint_snitch: SimpleSnitch +dynamic_snitch_update_interval_in_ms: 100 +dynamic_snitch_reset_interval_in_ms: 600000 +dynamic_snitch_badness_threshold: 0.1 +request_scheduler: org.apache.cassandra.scheduler.NoScheduler + +server_encryption_options: + internode_encryption: none + keystore: conf/.keystore + keystore_password: cassandra + truststore: conf/.truststore + truststore_password: cassandra +client_encryption_options: + enabled: false + # If enabled and optional is set to true encrypted and unencrypted connections are handled. + optional: false + keystore: conf/.keystore + keystore_password: cassandra + +internode_compression: dc +inter_dc_tcp_nodelay: false +tracetype_query_ttl: 86400 +tracetype_repair_ttl: 604800 +enable_user_defined_functions: false +enable_scripted_user_defined_functions: false +enable_materialized_views: true +windows_timer_interval: 1 +tombstone_warn_threshold: 1000 +tombstone_failure_threshold: 100000 +batch_size_warn_threshold_in_kb: 5 +batch_size_fail_threshold_in_kb: 50 +unlogged_batch_across_partitions_warn_threshold: 10 +compaction_large_partition_warning_threshold_mb: 100 +gc_warn_threshold_in_ms: 1000 diff --git a/src/test/resources/cert/README.md b/src/test/resources/cert/README.md new file mode 100644 index 0000000..961fb89 --- /dev/null +++ b/src/test/resources/cert/README.md @@ -0,0 +1,3 @@ +This keypair is just for testing. Under no circumstances should it be +used on any system that matters. It's been public on github and +compromised since the day it was generated. \ No newline at end of file diff --git a/src/test/resources/cert/test.crt b/src/test/resources/cert/test.crt new file mode 100644 index 0000000..607b3d1 --- /dev/null +++ b/src/test/resources/cert/test.crt @@ -0,0 +1,21 @@ +Bag Attributes + friendlyName: cassandra + 2.16.840.1.113894.746875.1.1: +subject=/C=Unknown/ST=Unknown/L=Unknown/O=Unknown/OU=Unknown/CN=localhost +issuer=/C=Unknown/ST=Unknown/L=Unknown/O=Unknown/OU=Unknown/CN=localhost +-----BEGIN CERTIFICATE----- +MIICdjCCAd+gAwIBAgIEctIFMzANBgkqhkiG9w0BAQsFADBuMRAwDgYDVQQGEwdV +bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD +VQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRIwEAYDVQQDEwlsb2NhbGhv +c3QwHhcNMTQwOTAxMDczNTI2WhcNMTQxMTMwMDczNTI2WjBuMRAwDgYDVQQGEwdV +bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD +VQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRIwEAYDVQQDEwlsb2NhbGhv +c3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMcvbCEQJfqxZJoGSLZSrpf1 +1BGnhDLZLorkCcERNKyduiHRWE62M9Y0h7mNcQ4EXaNIeJS9G9LvQrO3ojSyU2bx +vYEgiySnabcZbC7tR4Y9VdW7WRb/qkzmCXRCnj6merQXxsc3UxIRQOBaSnjfDMwv +OaXMG9pJddP1rSYKVLcPAgMBAAGjITAfMB0GA1UdDgQWBBSosZvuR4EuYrpf3mop +uif6CXEUWzANBgkqhkiG9w0BAQsFAAOBgQAAS54akNJUnlfj3a/vCM+Zf0C5wJIy +KMGj+hg1sSs6hrvH4xjti+p8fqMffjazxtjhho2orWd/A0/5pFMzGg1hy+OMu02K +mF9EyveKm5Dw35Y/6o2IXIeAz1/zdQfoPbEniu7MSjyX8E0ZmVWqnombOusOR/tE +mc2ku/RrFXlOUA== +-----END CERTIFICATE----- diff --git a/src/test/resources/cert/test.keystore b/src/test/resources/cert/test.keystore new file mode 100644 index 0000000..729078c Binary files /dev/null and b/src/test/resources/cert/test.keystore differ diff --git a/src/test/resources/cert/test.truststore b/src/test/resources/cert/test.truststore new file mode 100644 index 0000000..005300c Binary files /dev/null and b/src/test/resources/cert/test.truststore differ diff --git a/src/test/resources/cqlshrc b/src/test/resources/cqlshrc new file mode 100644 index 0000000..42f9b05 --- /dev/null +++ b/src/test/resources/cqlshrc @@ -0,0 +1,3 @@ +[ssl] +certfile = /etc/ssl/test.crt +validate = false diff --git a/src/test/resources/docker-compose.yml b/src/test/resources/docker-compose.yml new file mode 100644 index 0000000..a73b301 --- /dev/null +++ b/src/test/resources/docker-compose.yml @@ -0,0 +1,33 @@ +# Copyright 2017 JanusGraph Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +version: '2' +services: + cassandra: + image: cassandra:$CASSANDRA_VERSION + container_name: jg-cassandra + ports: + - "9042:9042" + - "9160:9160" + environment: + - CASSANDRA_START_RPC=true + - CASSANDRA_ENABLE_SSL=$CASSANDRA_ENABLE_SSL + - CASSANDRA_ENABLE_BOP=$CASSANDRA_ENABLE_BOP + volumes: + - ./update_config.py:/update_config.py + - ./janusgraph_docker_entrypoint.sh:/janusgraph_docker_entrypoint.sh + - ../../../config/static/test.keystore:/etc/ssl/test.keystore + - ../../../config/static/test.crt:/etc/ssl/test.crt + - ./cqlshrc:/root/.cassandra/cqlshrc + entrypoint: /janusgraph_docker_entrypoint.sh diff --git a/src/test/resources/janusgraph_docker_entrypoint.sh b/src/test/resources/janusgraph_docker_entrypoint.sh new file mode 100755 index 0000000..e177dfb --- /dev/null +++ b/src/test/resources/janusgraph_docker_entrypoint.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 2017 JanusGraph Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +/update_config.py +/docker-entrypoint.sh cassandra -f diff --git a/src/test/resources/log4j.properties b/src/test/resources/log4j.properties new file mode 100644 index 0000000..e7acff6 --- /dev/null +++ b/src/test/resources/log4j.properties @@ -0,0 +1,49 @@ +# Copyright 2019 JanusGraph Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# A1 is a FileAppender. +log4j.appender.A1=org.apache.log4j.FileAppender +log4j.appender.A1.File=target/test.log +log4j.appender.A1.Threshold=ALL +# A1 uses PatternLayout. +log4j.appender.A1.layout=org.apache.log4j.PatternLayout +log4j.appender.A1.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5p %c{2}: %m%n + +# A2 is a ConsoleAppender. +log4j.appender.A2=org.apache.log4j.ConsoleAppender +log4j.appender.A2.Threshold=ALL +# A2 uses PatternLayout. +log4j.appender.A2.layout=org.apache.log4j.PatternLayout +log4j.appender.A2.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5p %c{2}: %m%n + +# Set both appenders (A1 and A2) on the root logger. +#log4j.rootLogger=INFO, A1, A2 +log4j.rootLogger=ERROR, A1 + +# Restrict some of JanusGraph's dependencies to INFO and scarier. +# These restrictions are useful when reducing the severity threshold +# setting on one of the appenders below INFO. +log4j.logger.org.apache.cassandra=INFO +log4j.logger.org.apache.hadoop=INFO +log4j.logger.org.apache.zookeeper=INFO +# Disable all messages from ExpectedValueCheckingTransaction. The point is to +# suppress scary-looking ERROR messages that are deliberately induced by +# LockKeyColumnValueStoreTest. +log4j.logger.org.janusgraph.diskstorage.locking.consistentkey.ExpectedValueCheckingTransaction=OFF +# Lower dblog-related message thresholds +log4j.logger.org.janusgraph.diskstorage.log.kcvs.KCVSLog=DEBUG +log4j.logger.org.janusgraph.diskstorage.log.LogTest=DEBUG +log4j.logger.org.janusgraph.diskstorage.log.KCVSLogTest=DEBUG +log4j.logger.org.janusgraph.diskstorage.log.util.ProcessMessageJob=DEBUG +log4j.logger.org.janusgraph.diskstorage.util.time.Timestamps=DEBUG diff --git a/src/test/resources/rexster-fragment.xml b/src/test/resources/rexster-fragment.xml new file mode 100644 index 0000000..19f88f9 --- /dev/null +++ b/src/test/resources/rexster-fragment.xml @@ -0,0 +1,13 @@ + + + false + home + + local + + + + tp:gremlin + + + diff --git a/src/test/resources/update_config.py b/src/test/resources/update_config.py new file mode 100755 index 0000000..5dfb121 --- /dev/null +++ b/src/test/resources/update_config.py @@ -0,0 +1,43 @@ +#!/usr/bin/python +# +# Copyright 2017 JanusGraph Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os,re + +keystore = '/etc/ssl/test.keystore' +keystore_password = 'cassandra' +enableSsl = os.environ.get('CASSANDRA_ENABLE_SSL','').lower() == 'true' +enableBop = os.environ.get('CASSANDRA_ENABLE_BOP','').lower() == 'true' + +filename = '/etc/cassandra/cassandra.yaml' +with open(filename,'r') as f: + s = f.read() + +if enableSsl: + ssl_conf = re.search('(client_encryption_options:.*?)[\n\r]{2}',s,re.M|re.DOTALL).group(1) + ssl_conf_new = re.sub('enabled:.*','enabled: true', ssl_conf) + ssl_conf_new = re.sub('keystore:.*','keystore: %s' % keystore, ssl_conf_new) + ssl_conf_new = re.sub('keystore_password:.*','keystore_password: %s' % keystore_password, ssl_conf_new) + s = s.replace(ssl_conf, ssl_conf_new) + +if enableBop: + s = re.sub('partitioner:.*', 'partitioner: org.apache.cassandra.dht.ByteOrderedPartitioner', s) + s = re.sub('# initial_token:.*', 'initial_token: 0000000000000000000000000000000000', s) + s = re.sub('num_tokens:','#num_tokens:', s) +else: + s = re.sub('num_tokens:.*','num_tokens: 4', s) + +with open(filename,'w') as f: + f.write(s)