From 365b11fd6b64c60d6fda10760eded37d62ca486b Mon Sep 17 00:00:00 2001 From: Dennis Patrone Date: Wed, 2 Sep 2015 15:42:52 -0400 Subject: [PATCH] initial import --- .gitattributes | 6 + LICENSE | 202 +++++ README.md | 7 + pom.xml | 657 ++++++++++++++++ proxy-instance-docs/pom.xml | 111 +++ proxy-instance-docs/pom.xml.releaseBackup | 112 +++ .../src/main/asciidoc/chapters/building.txt | 16 + .../src/main/asciidoc/chapters/copyright.txt | 13 + .../main/asciidoc/chapters/development.txt | 64 ++ .../main/asciidoc/chapters/introduction.txt | 32 + .../main/asciidoc/chapters/known_issues.txt | 10 + .../src/main/asciidoc/chapters/usage.txt | 75 ++ .../asciidoc/images/apl_horizontal_blue.png | Bin 0 -> 28837 bytes .../src/main/asciidoc/images/proxy.png | Bin 0 -> 113768 bytes .../src/main/asciidoc/images/traditional.png | Bin 0 -> 94686 bytes .../proxy_instance_user_manual.asciidoc | 21 + proxy-instance/pom.xml | 95 +++ proxy-instance/pom.xml.releaseBackup | 96 +++ .../accumulo/proxy/AbstractProxyScanner.java | 90 +++ .../accumulo/proxy/ExceptionFactory.java | 113 +++ .../jhuapl/accumulo/proxy/MutationBuffer.java | 177 +++++ .../accumulo/proxy/ProxyActiveCompaction.java | 121 +++ .../accumulo/proxy/ProxyActiveScan.java | 141 ++++ .../accumulo/proxy/ProxyBatchScanner.java | 134 ++++ .../accumulo/proxy/ProxyBatchWriter.java | 148 ++++ .../proxy/ProxyConditionalWriter.java | 126 +++ .../jhuapl/accumulo/proxy/ProxyConnector.java | 170 +++++ .../jhuapl/accumulo/proxy/ProxyInstance.java | 722 ++++++++++++++++++ .../accumulo/proxy/ProxyInstanceError.java | 39 + .../proxy/ProxyInstanceOperations.java | 114 +++ .../proxy/ProxyMultiTableBatchWriter.java | 215 ++++++ .../jhuapl/accumulo/proxy/ProxyScanner.java | 172 +++++ .../proxy/ProxySecurityOperations.java | 209 +++++ .../accumulo/proxy/ProxyTableOperations.java | 528 +++++++++++++ .../accumulo/proxy/ScannerIterator.java | 99 +++ .../jhuapl/accumulo/proxy/ThriftHelper.java | 489 ++++++++++++ .../src/main/resources/Eclipse-Codestyle.xml | 292 +++++++ proxy-instance/src/main/resources/license.txt | 13 + .../jhuapl/accumulo/proxy/ConnectorBase.java | 116 +++ .../accumulo/proxy/IntegrationTest.java | 24 + .../jhuapl/accumulo/proxy/ScannerTest.java | 140 ++++ .../jhuapl/accumulo/proxy/ScannerTestIT.java | 18 + .../jhuapl/accumulo/proxy/TableOpsTest.java | 78 ++ .../jhuapl/accumulo/proxy/TableOpsTestIT.java | 18 + .../accumulo/proxy/ThiftHelperTest.java | 129 ++++ proxy-instance/src/test/resources/mock.props | 3 + 46 files changed, 6155 insertions(+) create mode 100644 .gitattributes create mode 100644 LICENSE create mode 100644 README.md create mode 100644 pom.xml create mode 100644 proxy-instance-docs/pom.xml create mode 100644 proxy-instance-docs/pom.xml.releaseBackup create mode 100644 proxy-instance-docs/src/main/asciidoc/chapters/building.txt create mode 100644 proxy-instance-docs/src/main/asciidoc/chapters/copyright.txt create mode 100644 proxy-instance-docs/src/main/asciidoc/chapters/development.txt create mode 100644 proxy-instance-docs/src/main/asciidoc/chapters/introduction.txt create mode 100644 proxy-instance-docs/src/main/asciidoc/chapters/known_issues.txt create mode 100644 proxy-instance-docs/src/main/asciidoc/chapters/usage.txt create mode 100644 proxy-instance-docs/src/main/asciidoc/images/apl_horizontal_blue.png create mode 100644 proxy-instance-docs/src/main/asciidoc/images/proxy.png create mode 100644 proxy-instance-docs/src/main/asciidoc/images/traditional.png create mode 100644 proxy-instance-docs/src/main/asciidoc/proxy_instance_user_manual.asciidoc create mode 100644 proxy-instance/pom.xml create mode 100644 proxy-instance/pom.xml.releaseBackup create mode 100644 proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/AbstractProxyScanner.java create mode 100644 proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ExceptionFactory.java create mode 100644 proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/MutationBuffer.java create mode 100644 proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyActiveCompaction.java create mode 100644 proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyActiveScan.java create mode 100644 proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyBatchScanner.java create mode 100644 proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyBatchWriter.java create mode 100644 proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyConditionalWriter.java create mode 100644 proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyConnector.java create mode 100644 proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyInstance.java create mode 100644 proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyInstanceError.java create mode 100644 proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyInstanceOperations.java create mode 100644 proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyMultiTableBatchWriter.java create mode 100644 proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyScanner.java create mode 100644 proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxySecurityOperations.java create mode 100644 proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyTableOperations.java create mode 100644 proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ScannerIterator.java create mode 100644 proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ThriftHelper.java create mode 100644 proxy-instance/src/main/resources/Eclipse-Codestyle.xml create mode 100644 proxy-instance/src/main/resources/license.txt create mode 100644 proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/ConnectorBase.java create mode 100644 proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/IntegrationTest.java create mode 100644 proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/ScannerTest.java create mode 100644 proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/ScannerTestIT.java create mode 100644 proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/TableOpsTest.java create mode 100644 proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/TableOpsTestIT.java create mode 100644 proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/ThiftHelperTest.java create mode 100644 proxy-instance/src/test/resources/mock.props diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9991357 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +* text eol=lf + +*.java text +*.txt text + +*.png binary diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8f71f43 --- /dev/null +++ b/LICENSE @@ -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/README.md b/README.md new file mode 100644 index 0000000..14b2efd --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +ProxyInstance +============= + +This is an implementation of the Accumulo `Instance` Application Programming Interface (API) which uses the Accumulo-supplied, Apache Thrift-based +proxy server as the back-end for communicating with an Accumulo instance. + +For more information, see the [Users' Manual](http://jhuapl.github.io/accumulo-proxy-instance/proxy_instance_user_manual.html). diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..3b0a6b8 --- /dev/null +++ b/pom.xml @@ -0,0 +1,657 @@ + + + 4.0.0 + edu.jhuapl.accumulo + proxy-instance-project + 1.0.0-SNAPSHOT + pom + Accumulo Proxy Instance Project + Provides an Accumulo Java API Instance implementation that communicates via the Accumulo Thrift-based Proxy server. + https://github.com/JHUAPL/accumulo-proxy-instance + + JHU/APL + http://www.jhuapl./edu/ + + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + Dennis Patrone + https://github.com/dennispatrone + + + David Patrone + + + + proxy-instance + proxy-instance-docs + + + scm:git:git@github.com/JHUAPL/accumulo-proxy-instance.git + scm:git:git@github.com:JHUAPL/accumulo-proxy-instance.git + git@github.com:JHUAPL/accumulo-proxy-instance.git + + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + + 1.6.2 + 3.0.1 + 1 + 1.7 + 1.7 + 3.0.4 + UTF-8 + false + + + + + junit + junit + 4.12 + + + org.slf4j + slf4j-api + 1.7.12 + + + org.slf4j + slf4j-log4j12 + 1.7.12 + + + org.slf4j + slf4j-nop + 1.7.12 + + + + + + + + org.codehaus.mojo + findbugs-maven-plugin + ${findbugs.version} + + true + Max + true + true + 16 + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.15 + + + com.github.ekryd.sortpom + sortpom-maven-plugin + 2.4.0 + + recommended_2008_06 + false + \n + false + 2 + scope,groupId,artifactId + true + Stop + + + + org.apache.maven.plugins + maven-javadoc-plugin + + ${project.reporting.outputEncoding} + true + docs + ${maven.compiler.target} + -J-Xmx512m + + + + org.apache.maven.plugins + maven-site-plugin + + true + + + + org.apache.maven.plugins + maven-failsafe-plugin + 2.18.1 + + + integration-test + + integration-test + + + + verify + + verify + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.18.1 + + -Xmx1G + + + + org.asciidoctor + asciidoctor-maven-plugin + 1.5.2.1 + + + org.codehaus.mojo + cobertura-maven-plugin + 2.7 + + true + + xml + html + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + org.apache.maven.plugins + maven-plugin-plugin + [3.2,) + + helpmojo + descriptor + + + + + + + + + com.googlecode.maven-java-formatter-plugin + maven-java-formatter-plugin + [0.4,) + + format + verify + + + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + [2.13,) + + check + + + + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + [1.0,) + + enforce + + + + + + + + + com.github.ekryd.sortpom + sortpom-maven-plugin + [2.4.0,) + + sort + verify + + + + + + + + + com.mycila + license-maven-plugin + [2.11,) + + format + check + + + + + + + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + 1.4 + + + + [${maven.min-version},) + + + + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + enforce-mvn + + enforce + + + + + + org.apache.maven.plugins + maven-release-plugin + 2.5 + + true + false + release + deploy + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.3 + true + + ossrh + https://oss.sonatype.org/ + true + + + + com.github.ekryd.sortpom + sortpom-maven-plugin + + + sort-pom + + sort + + process-sources + + + verify-sorted-pom + + verify + + process-resources + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + warning + true + + + + com.puppycrawl.tools + checkstyle + 6.3 + + + + + check-style + + check + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.codehaus.mojo + findbugs-maven-plugin + + + run-findbugs + + check + + + + + + org.apache.maven.plugins + maven-scm-publish-plugin + 1.1 + + + scm-publish + + publish-scm + + site-deploy + + + + + + + org.apache.maven.wagon + wagon-ssh + 2.8 + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.3 + + + + javadoc + + + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.5 + + html + true + ${maven.compiler.target} + + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 2.8 + + false + false + + + + + summary + index + dependencies + issue-tracking + scm + + + + + + org.apache.maven.plugins + maven-jxr-plugin + 2.5 + + + org.codehaus.mojo + findbugs-maven-plugin + ${findbugs.version} + + true + true + true + Max + Medium + false + + + + + + + release + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + + sign + + verify + + + + + + + + javadoc + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + + attach-javadocs + + jar + + + + + + + + + fatjar + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + + + shade + + package + + + + + + + + diff --git a/proxy-instance-docs/pom.xml b/proxy-instance-docs/pom.xml new file mode 100644 index 0000000..61a4808 --- /dev/null +++ b/proxy-instance-docs/pom.xml @@ -0,0 +1,111 @@ + + + 4.0.0 + + edu.jhuapl.accumulo + proxy-instance-project + 1.0.0-SNAPSHOT + + proxy-instance-docs + pom + Documentation + User documentation for Accumulo ProxyInstance. + + + edu.jhuapl.accumulo + proxy-instance + ${project.version} + + + + + + org.apache.maven.plugins + maven-resources-plugin + + + copy-asciidoc + + copy-resources + + compile + + ${project.build.directory}/asciidoc + + + src/main/asciidoc + + + + + + + + org.asciidoctor + asciidoctor-maven-plugin + + html + book + true + ${project.build.directory}/asciidoc/images + ${project.build.directory}/asciidoc + highlightjs + + ${project.version} + ${accumulo.version} + + + + + output-html + + process-asciidoc + + prepare-package + + + + + org.codehaus.mojo + exec-maven-plugin + + + prep-output-dir + + exec + + compile + + mkdir + + -p + ${project.build.directory}/asciidoc + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + attach-user-manual-html + + attach-artifact + + + + + ${project.build.directory}/generated-docs/proxy_instance_user_manual.html + html + user-manual + + + + + + + + + diff --git a/proxy-instance-docs/pom.xml.releaseBackup b/proxy-instance-docs/pom.xml.releaseBackup new file mode 100644 index 0000000..9191128 --- /dev/null +++ b/proxy-instance-docs/pom.xml.releaseBackup @@ -0,0 +1,112 @@ + + + 4.0.0 + + edu.jhuapl.accumulo + proxy-instance-project + 1.0.0-SNAPSHOT + .. + + proxy-instance-docs + pom + Documentation + User documentation for Accumulo ProxyInstance. + + + edu.jhuapl.accumulo + proxy-instance + ${project.version} + + + + + + org.apache.maven.plugins + maven-resources-plugin + + + copy-asciidoc + + copy-resources + + compile + + ${project.build.directory}/asciidoc + + + src/main/asciidoc + + + + + + + + org.asciidoctor + asciidoctor-maven-plugin + + html + book + true + ${project.build.directory}/asciidoc/images + ${project.build.directory}/asciidoc + highlightjs + + ${project.version} + ${accumulo.version} + + + + + output-html + + process-asciidoc + + prepare-package + + + + + org.codehaus.mojo + exec-maven-plugin + + + prep-output-dir + + exec + + compile + + mkdir + + -p + ${project.build.directory}/asciidoc + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + attach-user-manual-html + + attach-artifact + + + + + ${project.build.directory}/generated-docs/proxy_instance_user_manual.html + html + user-manual + + + + + + + + + diff --git a/proxy-instance-docs/src/main/asciidoc/chapters/building.txt b/proxy-instance-docs/src/main/asciidoc/chapters/building.txt new file mode 100644 index 0000000..192a9af --- /dev/null +++ b/proxy-instance-docs/src/main/asciidoc/chapters/building.txt @@ -0,0 +1,16 @@ +== Building + +The source hierarchy for `ProxyInstance` is: + +* *proxy-instance-project*: POM contains general plugin versions and configurations +** *proxy-instance*: The actual source and test code for the `ProxyInstance` +** *proxy-instance-docs*: This documentation +** *proxy-instance-build*: Resources necessary for building (e.g, license information, formatting templates) + +To build the system, you can execute: + + mvn package + +To build it while executing the integration tests (requires an external Accumulo instance and Proxy Server configured and running): + + mvn failsafe:integration-test package -Daccumulo.proxy.host=myhost -Daccumulo.proxy.port=myport -Daccumulo.proxy.user=myuser -Daccumulo.proxy.password=mypassword diff --git a/proxy-instance-docs/src/main/asciidoc/chapters/copyright.txt b/proxy-instance-docs/src/main/asciidoc/chapters/copyright.txt new file mode 100644 index 0000000..99d049e --- /dev/null +++ b/proxy-instance-docs/src/main/asciidoc/chapters/copyright.txt @@ -0,0 +1,13 @@ +Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + +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. \ No newline at end of file diff --git a/proxy-instance-docs/src/main/asciidoc/chapters/development.txt b/proxy-instance-docs/src/main/asciidoc/chapters/development.txt new file mode 100644 index 0000000..8a9fad4 --- /dev/null +++ b/proxy-instance-docs/src/main/asciidoc/chapters/development.txt @@ -0,0 +1,64 @@ +== Development + +This chapter includes information for those wishing to further development on the `ProxyInstance`. + +=== Unit and Integration Tests + +The `ProxyInstance` repository contains a number of unit tests (`XxxTest.java`) and integration tests (`XxxIT.java`) in +the `src/test` directory. But more are needed. + +As a convenience, developers may choose to have their unit and integration test classes subclass the provided `ConnectorBase` +class. The ConnectorBase handles obtaining an `Instance` and `Connector` references that can be utilized by the +tests. + +A special tag interface called `IntegrationTest` has also been created. If a sub-class of ConnectorBase containing tests +implements the `IntegrationTest` interface, it will be provided a `ProxyInstance` configured using the required `System` +properties: `accumulo.proxy.host`, `accumulo.proxy.port`, `accumulo.proxy.user`, and +`accumulo.proxy.password`. The integration tests assume a real Proxy Server is running somewhere on the network. If the +test class does not implement `IntegrationTest`, a local Proxy Server will be created backed by a `MockInstance` +Accumulo (we do not use the mini due to https://issues.apache.org/jira/browse/ACCUMULO-3293[ACCUMULO-3293]). + +A common pattern when testing is to be able to test the logic via the local mock instance proxy server as a unit test and +test the same capabilities again against an external proxy server as an integration test. The current infrastructure +enables this without duplication of code. The general pattern is: + +. Create your unit tests by subclassing `ConnectorBase` and writing your tests against the `protected` member variables +`instance` and `connector`. When run as a unit tests, these will automatically be connected to a local, mock-based +proxy server. ++ +[source,java] +---- +public class SomeClassTest extends ConnectorBase { + + @Test + public void testSomething() { + // make use of member variables 'instance' and 'connector' as needed, + // already initialized by parent ConnectorBase + BatchWriter bw = connector.createBatchWriter(...); + + // do more stuff... + } +} +---- + +. Create your integration test by simply sub-classing your unit test and implement the tag interface `IntegrationTest`. +That is it. When you run the integration tests, the `instance` and `connector` member variables will be connected to +the remote proxy server based on the required system parameters. ++ +[source,java] +---- +public class SomeClassIT extends SomeClassTest implements IntegrationTest { + + // Do nothing more; the ConnectorBase parent will recoginize this is an + // IntegrationTest and provide the appropriate 'instance' and + // 'connector' references and then execute all of the same logic the unit + // test executed. + + // Of course, you can create new methods here if there is additional + // integration testing desired beyond what the unit test performed. + +} +---- + + + diff --git a/proxy-instance-docs/src/main/asciidoc/chapters/introduction.txt b/proxy-instance-docs/src/main/asciidoc/chapters/introduction.txt new file mode 100644 index 0000000..eb14af1 --- /dev/null +++ b/proxy-instance-docs/src/main/asciidoc/chapters/introduction.txt @@ -0,0 +1,32 @@ +== Introduction + + +This is an implementation of the Accumulo `Instance` Application Programming Interface (API) which uses the Accumulo-supplied, Apache Thrift-based +proxy server as the back-end for communicating with an Accumulo instance. + +Apache Accumulo provides two methods for interacting with an Accumulo instance: a Java-based client library and an Apache Thrift-based +API. The Java-based client library requires that the client application have direct network access to all data nodes in the Accumulo instance +cloud as the client application communicates directly with the tablet servers where the data is stored. + +image::traditional.png["Traditional",width="800",align="center"] + +The Thrift-based API interacts with an Accumulo instance through a Proxy Server; the client application only needs direct network +access to the Proxy Server (the proxy server, in turn, communicates with the tablet servers directly on behalf of the requesting +client application). While providing similar capabilities as the Java-based client library, the Thrift-based API is significantly +different than the Java-based API. The Thrift API was originally developed to provide Accumulo access to non-Java applications. However, +in situations where the Accumulo cloud is not entirely network addressable by Java-based client applications (e.g., isolated behind a firewall), +it is useful to allow Java clients to utilize the proxy service. Furthermore, It would be ideal to expose the proxy service through the same API +as the traditional Java-based client library to protect client source code from significant changes based only on differences in the +network topology. + +This Proxy `Instance` implementation provides such an implementation. It is a Java-based client library for interacting with Accumulo's +ProxyService via the Thrift interface, but exposes the same Java API as the traditional Java-based client library. This enables, +in the future (e.g., after development and testing) by moving the client code onto the isolated Accumulo network and with a simple switch +of the `Instance` type created, the Java client application can take advantage of the performance increase using the traditional Java +client library. + + +image::proxy.png["Proxy",width="800",align="center"] + + +This version was written, compiled, and tested against Accumulo {accumuloVersion}. \ No newline at end of file diff --git a/proxy-instance-docs/src/main/asciidoc/chapters/known_issues.txt b/proxy-instance-docs/src/main/asciidoc/chapters/known_issues.txt new file mode 100644 index 0000000..cdde789 --- /dev/null +++ b/proxy-instance-docs/src/main/asciidoc/chapters/known_issues.txt @@ -0,0 +1,10 @@ +== Known Issues + +Here we document the known issues. + +* Not all Java APIs are supported through the Proxy interface. Currently, the proxy throws an `UnsupportedOperationException` in +such cases. We should enter tickets to update the proxy thrift interface to support these additional capabilities. +* We currently need to close the instance which is not part of the public API. Maybe we should open and close the transport every +time we access the instance so we can open/close the transport each time? Is there a better way to handle closing the transport +without incurring the overhead of re-establishing the connection on every call? +* Others? diff --git a/proxy-instance-docs/src/main/asciidoc/chapters/usage.txt b/proxy-instance-docs/src/main/asciidoc/chapters/usage.txt new file mode 100644 index 0000000..d323c1b --- /dev/null +++ b/proxy-instance-docs/src/main/asciidoc/chapters/usage.txt @@ -0,0 +1,75 @@ +== Usage + +This section contains a brief introduction to getting setup using the Proxy Instance. + +. You must have the Accumulo Proxy Server up and running. See http://accumulo.apache.org/1.6/accumulo_user_manual.html#_proxy for more information. + +. Include this for maven (or download the latest JARs from http://search.maven.org/[Maven Central]) ++ +[source,xml,indent=0] +---- + + edu.jhuapl.accumulo + proxy-instance + ${proxy.version} + +---- ++ +The current version is {docVersion}. + +. Create an instance of `ProxyInstance` and provide it the hostname (or IP address) and port where the proxy is running. ++ +[source,java] +---- + Instance instance = new ProxyInstance("proxyhost", 4567); + Connector connector = instance.getConnector(user, new PassworkToken(password)); +---- + +. Use the `instance` and `connector` objects as you normally would using the traditional Java API. + +. When finished, we also need to close the `ProxyInstance`. Unfortunately, a method to close an `Instance` does not exist in the public +API. Therefore, we must add something like this when we are done with the `Instance`: ++ +[source,java] +---- + if (instance instanceof ProxyInstance) { + ((ProxyInstance)instance).close(); + } + +---- + +=== Configuration Parameters + +==== BatchScanner Fetch Size + +When fetching data from a `BatchScanner,` the Proxy Server API allows you to request data in batches. By batching multiple {key,value} +pairs up into a single fetch request, data latency and network bandwidth consumed may be reduced. The pairs are queued in the Proxy Server +and returned in a batch when the fetch size (or the end of the `Scanner`) is reached. + +The `ProxyInstance` `BatchScanner` implementation defaults to a fetch size of 1,000 pairs. In general, this is good for fetching a lot of +"small" data from very large tables. However, in certain circumstances this batching can actually increase the data latency for the first +data element. The ProxyServer will fill the entire fetch size buffer before sending any data to the client. If very selective filtering is +applied to the scanner on the server-side, it may take a long time for the Proxy Server to find 1,000 pairs to return even if a few are found +very quickly. Furthermore, if the keys and/or values are sufficiently large (or the RAM available to the Proxy Server is sufficiently +limited), queuing 1,000 pairs in RAM can cause the Proxy Server to crash with an `OutOfMemoryException`. + +Therefore, the `ProxyInstance` provides a way for clients to modify the default fetch size via an optional argument to the `ProxyInstance` +constructor. Unfortunately, there is no place within the Java API to specify a fetch size for a `BatchScanner` on a per-scanner +basis. Therefore, changing the fetch size via the `ProxyInstance` constructor changes the fetch size for all ``BatchScanner``s created +by that instance. If multiple fetch sizes are required/desired, the client application will have to create and manage multiple +``ProxyInstance``s and utilize the correct instance to create individual ``BatchScanner``s based on the fetch size requirements. + +The fetch size specified must be strictly greater than 0 and less than or equal to 2,000. If the value provided is outside of that +range, a warning will be logged and the default value of 1,000 will be used. + +To set a new fetch size via the constructor, use the 3-argument constructor: +[source,java] +---- + String host = "myhost"; + int port = 5432; + int fetchSize = 10; + + Instance inst = new ProxyInstance(host, port, fetchSize); + ... +---- + diff --git a/proxy-instance-docs/src/main/asciidoc/images/apl_horizontal_blue.png b/proxy-instance-docs/src/main/asciidoc/images/apl_horizontal_blue.png new file mode 100644 index 0000000000000000000000000000000000000000..49787a11a44ac0d5066fc64b0bf5051b5f90fc0b GIT binary patch literal 28837 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 JiboEw{tvE5>NVKInPhbH023 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
license.txt
+ true + + **/pom.xml + **/resources/** + **/**.txt + **/**.asciidoc + LICENSE + + + + + + format + check + + process-resources + + + + + + diff --git a/proxy-instance/pom.xml.releaseBackup b/proxy-instance/pom.xml.releaseBackup new file mode 100644 index 0000000..244170e --- /dev/null +++ b/proxy-instance/pom.xml.releaseBackup @@ -0,0 +1,96 @@ + + + 4.0.0 + + edu.jhuapl.accumulo + proxy-instance-project + 1.0.0-SNAPSHOT + .. + + proxy-instance + proxy-instance + Implements Accumulo Java API through the Accumulo Thrift-based Proxy server. + + 1.7 + 1.7 + + + + org.apache.accumulo + accumulo-core + ${accumulo.version} + + + org.apache.accumulo + accumulo-proxy + ${accumulo.version} + + + junit + junit + test + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + maven-assembly-plugin + + + + stec.va.core.Main + + + + jar-with-dependencies + + + + + com.googlecode.maven-java-formatter-plugin + maven-java-formatter-plugin + 0.4 + + ${project.basedir}/src/main/resources/Eclipse-Codestyle.xml + LF + + + + + format + + + + + + com.mycila + license-maven-plugin + 2.11 + +
license.txt
+ true + + **/pom.xml + **/resources/** + **/**.txt + **/**.asciidoc + LICENSE + +
+ + + + format + check + + process-resources + + +
+
+
+
diff --git a/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/AbstractProxyScanner.java b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/AbstractProxyScanner.java new file mode 100644 index 0000000..b67cc66 --- /dev/null +++ b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/AbstractProxyScanner.java @@ -0,0 +1,90 @@ +/** + * Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + * + * 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 edu.jhuapl.accumulo.proxy; + +import java.nio.ByteBuffer; +import java.util.concurrent.TimeUnit; + +import org.apache.accumulo.core.client.ScannerBase; +import org.apache.accumulo.proxy.thrift.ScanColumn; +import org.apache.hadoop.io.Text; + +/** + * Parent class for proxy scanners. + */ +abstract class AbstractProxyScanner implements ScannerBase { + + /** + * The connector that created this scanner. + */ + protected ProxyConnector connector; + + /** + * The token used when making proxy requests. + */ + protected ByteBuffer token; + + /** + * Table name for this scanner. + */ + protected String tableName; + + /** + * Id assigned to this scanner by the proxy server. + */ + protected String scannerId = null; + + protected AbstractProxyScanner(ProxyConnector connector, ByteBuffer token, String tableName) { + this.connector = connector; + + this.token = token; + this.tableName = tableName; + } + + public void setTimeout(long timeOut, TimeUnit timeUnit) { + // proxy API does not support time outs for scanners + throw ExceptionFactory.unsupported(); + } + + public long getTimeout(TimeUnit timeUnit) { + // proxy API does not support time outs for scanners + throw ExceptionFactory.unsupported(); + } + + public void fetchColumnFamily(Text col) { + fetchColumn(col, null); + } + + public void fetchColumn(Text colFam, Text colQual) { + ScanColumn sc = new ScanColumn(); + if (colFam != null) { + sc.setColFamily(colFam.getBytes()); + } + if (colQual != null) { + sc.setColQualifier(colQual.getBytes()); + } + addToFetchOptions(sc); + } + + /** + * Subclasses must set themselves up to fetch the given ScanColumn. This allows Scanners and BatchScanners to handle the ScanColumn option differently. + * + * @param col + * the column to add to the current set of fetch options + */ + protected abstract void addToFetchOptions(ScanColumn col); + +} diff --git a/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ExceptionFactory.java b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ExceptionFactory.java new file mode 100644 index 0000000..09a26ff --- /dev/null +++ b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ExceptionFactory.java @@ -0,0 +1,113 @@ +/** + * Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + * + * 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 edu.jhuapl.accumulo.proxy; + +import org.apache.accumulo.core.client.AccumuloException; +import org.apache.accumulo.core.client.TableExistsException; +import org.apache.accumulo.core.client.TableNotFoundException; + +/** + * A factory to create standard exceptions for specific conditions. + * */ +final class ExceptionFactory { + + /** + * As a utility, this should not be instantiated. + */ + private ExceptionFactory() {} + + /** + * Exception to throw when a Java Client API method is not supported in the Proxy Client API. + * + * @return a new UnsupportedOperationException that tells the user this feature is not supported by the Proxy Thrift interface. + */ + public static UnsupportedOperationException unsupported() { + return new UnsupportedOperationException("Operation not supported by Accumulo Proxy API."); + } + + /** + * Exception to throw when the ProxyInstace has not yet implemented a Java API method. + * + * @return a new UnsupportedOperationException that tells the user this feature is not implemented yet. + */ + + public static UnsupportedOperationException notYetImplemented() { + return new UnsupportedOperationException("Not yet implemented."); + } + + /** + * Creates an AccumuloException with the given Throwable embedded as the cause. + * + * @param cause + * the root cause of this exception + * @return a new AccumuloException with the given root cause + */ + public static AccumuloException accumuloException(Throwable cause) { + return new AccumuloException(cause); + } + + /** + * Wraps the thrift exception with a core client exception. + * + * @param tableName + * the table name that was not found. + * @param tnfe + * the Thrift TableNotFoundException + * @return a new (non-Thrift) TableNotFoundException + */ + public static TableNotFoundException tableNotFoundException(String tableName, org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + return new TableNotFoundException(null, tableName, tnfe.getMsg(), tnfe); + } + + /** + * Wraps the thrift exception with a core client exception. + * + * @param tableName + * the table name that already exists + * @param tee + * the Thrift TableExistsException + * @return a new (non-Thrift) TableExistsException + */ + public static TableExistsException tableExistsException(String tableName, org.apache.accumulo.proxy.thrift.TableExistsException tee) { + return new TableExistsException(null, tableName, tee.getMsg(), tee); + } + + /** + * Creates a new RuntimeException with the given Throwable as the cause. + * + * @param cause + * the cause of this exception + * @return a new RuntimeException with the given cause + */ + public static RuntimeException runtimeException(Throwable cause) { + return new RuntimeException(cause); + } + + /** + * Used when we expect to be able to convert a particular value from one enum type (e.g., Java API) in another enum type (e.g., Proxy API) but can't. As we + * expect this to always work, this is an Error, not an Exception. + * + * @param val + * the original enumeration value + * @param otherCls + * the other Enumeration type we were trying to which we were trying to map {@code val} + * @return a new ProxyInstanceError that informs the user we expected but were unable to find a valid enumeration mapping. + */ + public static ProxyInstanceError noEnumMapping(Enum val, Class> otherCls) { + return new ProxyInstanceError("Expected mapping from value " + val + " (" + val.getClass().getCanonicalName() + ") to " + otherCls.getCanonicalName() + + " but it does not exist."); + } +} diff --git a/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/MutationBuffer.java b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/MutationBuffer.java new file mode 100644 index 0000000..eb6b077 --- /dev/null +++ b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/MutationBuffer.java @@ -0,0 +1,177 @@ +/** + * Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + * + * 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 edu.jhuapl.accumulo.proxy; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.TimeUnit; + +import org.apache.accumulo.core.client.BatchWriterConfig; +import org.apache.accumulo.core.data.Mutation; +import org.apache.accumulo.proxy.thrift.ColumnUpdate; +import org.apache.accumulo.proxy.thrift.MutationsRejectedException; +import org.apache.thrift.TException; + +/** + * A class used to buffer mutation on the client side in a user-sized memory buffer and/or for a user-defined amount of time before sending to the + * ProxyInstance. By buffering and sending many requests at once, performance may be improved some situations. + * + * This class does not actually do the sending when thresholds are exceeded; rather it just provides the access to the buffered Mutations and tracks the time + * and memory in order to be able to inform other classes when the buffer need to be flushed. + * + */ +class MutationBuffer { + + /** + * The map of writerIds to a list of buffered mutations. + */ + private Map> mutations; + + /** + * The maximum amount of memory (in bytes) to use before automatically pushing the buffered mutations to the Proxy server. + */ + long maxMemory; + + /** + * The maximum amount of time (in milliseconds) to wait before automatically pushing the buffered mutations to the Proxy server. + */ + long maxLatencyMs; + + /** + * The current estimate of buffered mutation memory in bytes. + */ + long memory; + + ProxyConnector connector; + + Timer timer; + + /** + * Create a new MutationBuffer with the given configuration parameters. + * + * @param config + * the configuration for this buffer + */ + MutationBuffer(ProxyConnector connector, BatchWriterConfig config) { + this.connector = connector; + this.maxMemory = config.getMaxMemory(); + this.maxLatencyMs = config.getMaxLatency(TimeUnit.MILLISECONDS); + + this.memory = 0L; + this.mutations = new HashMap>(); + } + + /** + * Add a mutation to this buffer. + * + * @param mutation + * the mutation to add + * @throws org.apache.accumulo.core.client.MutationsRejectedException + * throws if the mutation cannot be accepted + */ + public synchronized void addMutation(String writerId, Mutation mutation) throws org.apache.accumulo.core.client.MutationsRejectedException { + if (mutations.isEmpty()) { + // this is the first entry... start watching latency... + timer = new Timer("", true); + timer.schedule(new TimerTask() { + + @Override + public void run() { + try { + latencyFlush(); + } catch (org.apache.accumulo.core.client.MutationsRejectedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + }, maxLatencyMs); + } + + // create our own copy... + mutation = new Mutation(mutation); + + List list = mutations.get(writerId); + if (list == null) { + list = new ArrayList(); + mutations.put(writerId, list); + } + list.add(mutation); + + memory += mutation.estimatedMemoryUsed(); + checkMemoryFlush(); + } + + void checkMemoryFlush() throws org.apache.accumulo.core.client.MutationsRejectedException { + if (memory >= maxMemory) { + // memory threshold exceeded + flush(); + } + } + + void latencyFlush() throws org.apache.accumulo.core.client.MutationsRejectedException { + if (mutations.size() != 0) { + // latency threshold exceeded + flush(); + } + } + + /** + * Called to flush the buffer. The method returns a map of rowIDs (as ByteBuffers) to lists of Thrift-based ColumnUpdates suitable for sending directly to the + * ProxyInstance. As a side effect, this method also resets this buffer. + * + * @throws org.apache.accumulo.core.client.MutationsRejectedException + * thrown if any buffered mutations are rejected while flushing + */ + public synchronized void flush() throws org.apache.accumulo.core.client.MutationsRejectedException { + for (Entry> entry : mutations.entrySet()) { + String writerId = entry.getKey(); + Map> updates = new HashMap>(); + for (Mutation m : entry.getValue()) { + ByteBuffer key = ByteBuffer.wrap(m.getRow()); + List updateList = updates.get(key); + if (updateList == null) { + updateList = new ArrayList(); + updates.put(key, updateList); + } + ThriftHelper.addThriftColumnUpdates(updateList, m.getUpdates()); + } + try { + connector.getClient().update(writerId, updates); + connector.getClient().flush(writerId); + } catch (MutationsRejectedException mre) { + throw ThriftHelper.fromThrift(mre, connector.getInstance()); + } catch (TException e) { + throw ExceptionFactory.runtimeException(e); + } + } + + // and reset... + memory = 0; + if (timer != null) { + timer.cancel(); + timer = null; + } + mutations.clear(); + } + +} diff --git a/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyActiveCompaction.java b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyActiveCompaction.java new file mode 100644 index 0000000..06a7153 --- /dev/null +++ b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyActiveCompaction.java @@ -0,0 +1,121 @@ +/** + * Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + * + * 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 edu.jhuapl.accumulo.proxy; + +import java.util.List; + +import org.apache.accumulo.core.client.IteratorSetting; +import org.apache.accumulo.core.client.TableNotFoundException; +import org.apache.accumulo.core.client.admin.ActiveCompaction; +import org.apache.accumulo.core.data.KeyExtent; + +/** + * Implementation of an ActiveCompaction for the ProxyInstance. This is basically a data structure to hold all of the provided details about an active + * compaction. + */ +class ProxyActiveCompaction extends ActiveCompaction { + + String table; + KeyExtent extent; + long age; + List inputFiles; + String outputFile; + CompactionType type; + CompactionReason reason; + String localityGroup; + long entriesRead; + long entriesWritten; + List iterators; + + ProxyActiveCompaction(String table, KeyExtent extent, long age, List inputFiles, String outputFile, CompactionType type, CompactionReason reason, + String localityGroup, long entriesRead, long entriesWritten, List iterators) { + super(); + this.table = table; + this.extent = extent; + this.age = age; + this.inputFiles = inputFiles; + this.outputFile = outputFile; + this.type = type; + this.reason = reason; + this.localityGroup = localityGroup; + this.entriesRead = entriesRead; + this.entriesWritten = entriesWritten; + this.iterators = iterators; + } + + @Override + public String getTable() throws TableNotFoundException { + return table; + } + + @Override + public KeyExtent getExtent() { + return extent; + } + + @Override + public long getAge() { + return age; + } + + @Override + public List getInputFiles() { + return inputFiles; + } + + @Override + public String getOutputFile() { + return outputFile; + } + + @Override + public CompactionType getType() { + return type; + } + + @Override + public CompactionReason getReason() { + return reason; + } + + @Override + public String getLocalityGroup() { + return localityGroup; + } + + @Override + public long getEntriesRead() { + return entriesRead; + } + + @Override + public long getEntriesWritten() { + return entriesWritten; + } + + @Override + public List getIterators() { + return iterators; + } + + @Override + public String toString() { + return "ProxyActiveCompaction [table=" + table + ", extent=" + extent + ", age=" + age + ", inputFiles=" + inputFiles + ", outputFile=" + outputFile + + ", type=" + type + ", reason=" + reason + ", localityGroup=" + localityGroup + ", entriesRead=" + entriesRead + ", entriesWritten=" + entriesWritten + + ", iterators=" + iterators + "]"; + } + +} diff --git a/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyActiveScan.java b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyActiveScan.java new file mode 100644 index 0000000..7d3fcb9 --- /dev/null +++ b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyActiveScan.java @@ -0,0 +1,141 @@ +/** + * Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + * + * 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 edu.jhuapl.accumulo.proxy; + +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; + +import org.apache.accumulo.core.client.admin.ActiveScan; +import org.apache.accumulo.core.client.admin.ScanState; +import org.apache.accumulo.core.client.admin.ScanType; +import org.apache.accumulo.core.data.Column; +import org.apache.accumulo.core.data.KeyExtent; +import org.apache.accumulo.core.security.Authorizations; + +/** + * Implementation of an ActiveScan for the ProxyInstance. This is basically a data structure to hold all of the provided details about an active scan. + */ +class ProxyActiveScan extends ActiveScan { + + long scanId; + String client; + String user; + String table; + long age; + long lastContactTime; + ScanType type; + ScanState state; + KeyExtent extent; + List columns; + Authorizations authorizations; + long idleTime; + + ProxyActiveScan(long scanId, String client, String user, String table, long age, long lastContactTime, ScanType type, ScanState state, KeyExtent extent, + List columns, List authorizations, long idleTime) { + super(); + this.scanId = scanId; + this.client = client; + this.user = user; + this.table = table; + this.age = age; + this.lastContactTime = lastContactTime; + this.type = type; + this.state = state; + this.extent = extent; + this.columns = columns; + this.authorizations = new Authorizations(authorizations); + this.idleTime = idleTime; + } + + @Override + public long getScanid() { + return scanId; + } + + @Override + public String getClient() { + return client; + } + + @Override + public String getUser() { + return user; + } + + @Override + public String getTable() { + return table; + } + + @Override + public long getAge() { + return age; + } + + @Override + public long getLastContactTime() { + return lastContactTime; + } + + @Override + public ScanType getType() { + return type; + } + + @Override + public ScanState getState() { + return state; + } + + @Override + public KeyExtent getExtent() { + return extent; + } + + @Override + public List getColumns() { + return columns; + } + + @Override + public List getSsiList() { + throw ExceptionFactory.unsupported(); + } + + @Override + public Map> getSsio() { + throw ExceptionFactory.unsupported(); + } + + @Override + public Authorizations getAuthorizations() { + return authorizations; + } + + @Override + public long getIdleTime() { + return idleTime; + } + + @Override + public String toString() { + return "ProxyActiveScan [scanId=" + scanId + ", client=" + client + ", user=" + user + ", table=" + table + ", age=" + age + ", lastContactTime=" + + lastContactTime + ", type=" + type + ", state=" + state + ", extent=" + extent + ", columns=" + columns + ", authorizations=" + authorizations + + ", idleTime=" + idleTime + "]"; + } + +} diff --git a/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyBatchScanner.java b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyBatchScanner.java new file mode 100644 index 0000000..c8bf048 --- /dev/null +++ b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyBatchScanner.java @@ -0,0 +1,134 @@ +/** + * Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + * + * 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 edu.jhuapl.accumulo.proxy; + +import java.nio.ByteBuffer; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map.Entry; + +import org.apache.accumulo.core.client.BatchScanner; +import org.apache.accumulo.core.client.IteratorSetting; +import org.apache.accumulo.core.data.Key; +import org.apache.accumulo.core.data.Range; +import org.apache.accumulo.core.data.Value; +import org.apache.accumulo.core.security.Authorizations; +import org.apache.accumulo.proxy.thrift.AccumuloProxy; +import org.apache.accumulo.proxy.thrift.BatchScanOptions; +import org.apache.accumulo.proxy.thrift.ScanColumn; +import org.apache.thrift.TException; +import org.slf4j.LoggerFactory; + +/** + * Implementation of a BatchScanner for the ProxyInstance. + * + */ +class ProxyBatchScanner extends AbstractProxyScanner implements BatchScanner { + + /** + * The scan options set for this batch scanner. + */ + protected BatchScanOptions batchOptions; + + protected int fetchSize; + + ProxyBatchScanner(ProxyConnector connector, ByteBuffer token, String tableName, Authorizations authorizations, int numQueryThreads, int fetchSize) { + super(connector, token, tableName); + this.fetchSize = fetchSize; + + batchOptions = new BatchScanOptions(); + batchOptions.setThreads(numQueryThreads); + for (ByteBuffer auth : authorizations.getAuthorizationsBB()) { + batchOptions.addToAuthorizations(auth); + } + } + + public void setRanges(Collection ranges) { + if (ranges == null) { + batchOptions.unsetRanges(); + } else { + batchOptions.setRanges(ThriftHelper.toThriftRanges(ranges)); + } + } + + public void addScanIterator(IteratorSetting cfg) { + batchOptions.addToIterators(ThriftHelper.toThrift(cfg)); + } + + public void removeScanIterator(String iteratorName) { + for (org.apache.accumulo.proxy.thrift.IteratorSetting is : batchOptions.iterators) { + if (is.getName().equals(iteratorName)) { + batchOptions.iterators.remove(is); + break; + } + } + } + + public void updateScanIteratorOption(String iteratorName, String key, String value) { + for (org.apache.accumulo.proxy.thrift.IteratorSetting is : batchOptions.iterators) { + if (is.getName().equals(iteratorName)) { + is.putToProperties(key, value); + } + } + } + + @Override + protected void addToFetchOptions(ScanColumn col) { + batchOptions.addToColumns(col); + } + + public void clearColumns() { + if (batchOptions.getColumns() != null) { + batchOptions.getColumns().clear(); + } + } + + public void clearScanIterators() { + if (batchOptions.getIterators() != null) { + batchOptions.getIterators().clear(); + } + } + + public Iterator> iterator() { + AccumuloProxy.Iface client = connector.getClient(); + try { + scannerId = client.createBatchScanner(token, tableName, batchOptions); + return new ScannerIterator(scannerId, connector, fetchSize); + } catch (TException e) { + throw ExceptionFactory.runtimeException(e); + } + } + + public void close() { + try { + connector.getClient().closeScanner(scannerId); + } catch (TException e) { + throw ExceptionFactory.runtimeException(e); + } finally { + scannerId = null; + } + } + + @Override + protected void finalize() { + if (scannerId != null) { + close(); + LoggerFactory.getLogger(ProxyBatchScanner.class).warn( + "BatchScanner " + scannerId + " in finalize but not closed; " + "you forgot to close a batch scanner!"); + } + } + +} diff --git a/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyBatchWriter.java b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyBatchWriter.java new file mode 100644 index 0000000..6edc06b --- /dev/null +++ b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyBatchWriter.java @@ -0,0 +1,148 @@ +/** + * Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + * + * 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 edu.jhuapl.accumulo.proxy; + +import java.nio.ByteBuffer; +import java.util.concurrent.TimeUnit; + +import org.apache.accumulo.core.client.BatchWriter; +import org.apache.accumulo.core.client.BatchWriterConfig; +import org.apache.accumulo.core.client.MutationsRejectedException; +import org.apache.accumulo.core.data.Mutation; +import org.apache.accumulo.proxy.thrift.AccumuloException; +import org.apache.accumulo.proxy.thrift.AccumuloProxy; +import org.apache.accumulo.proxy.thrift.AccumuloSecurityException; +import org.apache.accumulo.proxy.thrift.TableNotFoundException; +import org.apache.accumulo.proxy.thrift.UnknownWriter; +import org.apache.accumulo.proxy.thrift.WriterOptions; +import org.apache.thrift.TException; +import org.slf4j.LoggerFactory; + +/** + * Implementation of a BatchWriter to a Proxy Server. + */ +class ProxyBatchWriter implements BatchWriter { + + /** + * Parent connector. + */ + ProxyConnector connector; + + /** + * Local copy of connector's Thrift RPC object to Proxy Server. + */ + private AccumuloProxy.Iface client; + + /** + * Token used when communicating with the proxy server. + */ + ByteBuffer token; + + /** + * The id for this writer. + */ + String writerId; + + /** + * An internal buffer to provide in-memory, time-based buffering of mutations to send in batches. This uses the same memory and latency parameters the actual + * BatchWriter will use on the Proxy Server side as well. + */ + private MutationBuffer mutationBuffer; + + private boolean closed; + + ProxyBatchWriter(ProxyConnector connector, ByteBuffer token, String table, BatchWriterConfig config) throws AccumuloException, AccumuloSecurityException, + TableNotFoundException, TException { + this.connector = connector; + this.client = connector.getClient(); + + this.token = token; + WriterOptions opts = new WriterOptions(); + opts.setLatencyMs(config.getMaxLatency(TimeUnit.MILLISECONDS)); + opts.setMaxMemory(config.getMaxMemory()); + opts.setThreads(config.getMaxWriteThreads()); + writerId = client.createWriter(token, table, opts); + + mutationBuffer = new MutationBuffer(connector, config); + closed = false; + } + + private void checkClosed() throws IllegalStateException { + if (closed) { + throw new IllegalStateException("Closed."); + } + } + + @Override + public void addMutation(Mutation m) throws MutationsRejectedException { + checkClosed(); + addMutationNoCloseCheck(m); + } + + @Override + public void addMutations(Iterable iterable) throws MutationsRejectedException { + checkClosed(); + for (Mutation m : iterable) { + addMutationNoCloseCheck(m); + } + } + + private void addMutationNoCloseCheck(Mutation m) throws MutationsRejectedException { + if (m.size() == 0) { + throw new IllegalArgumentException("Cannot add empty mutation."); + } + + mutationBuffer.addMutation(writerId, m); + } + + @Override + public void flush() throws MutationsRejectedException { + checkClosed(); + mutationBuffer.flush(); + } + + @Override + public void close() throws MutationsRejectedException { + checkClosed(); + mutationBuffer.flush(); + + try { + client.closeWriter(writerId); + closed = true; + } catch (UnknownWriter e) { + throw ExceptionFactory.runtimeException(e); + } catch (org.apache.accumulo.proxy.thrift.MutationsRejectedException e) { + throw ThriftHelper.fromThrift(e, connector.getInstance()); + } catch (TException e) { + throw ExceptionFactory.runtimeException(e); + } + } + + @Override + protected void finalize() { + if (!closed) { + LoggerFactory.getLogger(ProxyBatchWriter.class).warn( + "ProxyBatchWriter ID " + writerId + " in finalize but not closed; " + "you forgot to close a ProxyBatchWriter!"); + + try { + close(); + } catch (MutationsRejectedException mre) { + LoggerFactory.getLogger(ProxyBatchScanner.class).warn("Problem closing ProxyMultiTableBatchWriter.", mre); + } + } + } + +} diff --git a/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyConditionalWriter.java b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyConditionalWriter.java new file mode 100644 index 0000000..dd5d5fb --- /dev/null +++ b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyConditionalWriter.java @@ -0,0 +1,126 @@ +/** + * Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + * + * 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 edu.jhuapl.accumulo.proxy; + +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; + +import org.apache.accumulo.core.client.ConditionalWriter; +import org.apache.accumulo.core.client.ConditionalWriterConfig; +import org.apache.accumulo.core.data.ConditionalMutation; +import org.apache.accumulo.proxy.thrift.AccumuloProxy; +import org.apache.accumulo.proxy.thrift.ColumnUpdate; +import org.apache.accumulo.proxy.thrift.ConditionalStatus; +import org.apache.accumulo.proxy.thrift.ConditionalUpdates; +import org.apache.accumulo.proxy.thrift.ConditionalWriterOptions; +import org.apache.thrift.TException; + +class ProxyConditionalWriter implements ConditionalWriter { + + AccumuloProxy.Iface client; + + String writerId; + + ProxyConditionalWriter(ProxyConnector connector, ByteBuffer token, String table, ConditionalWriterConfig config) { + this.client = connector.getClient(); + + ConditionalWriterOptions options = new ConditionalWriterOptions(); + options.setAuthorizations(new HashSet(config.getAuthorizations().getAuthorizationsBB())); + options.setThreads(config.getMaxWriteThreads()); + options.setTimeoutMs(config.getTimeout(TimeUnit.MILLISECONDS)); + try { + writerId = client.createConditionalWriter(token, table, options); + } catch (TException e) { + throw ExceptionFactory.runtimeException(e); + } + + } + + public Iterator write(Iterator mutations) { + Map updates = new HashMap(); + Map mutationMap = new HashMap(); + + while (mutations.hasNext()) { + ConditionalMutation mutation = mutations.next(); + ByteBuffer key = ByteBuffer.wrap(mutation.getRow()); + ConditionalUpdates update = updates.get(key); + + // NOTE: API seems to imply you will provide a mutation against + // any single row ID at most once within this iterator or + // conditional mutations. Otherwise, the mutations and the + // conditions all get co-mingled when placed in the parameter + // map! TODO- should we check for this and throw an exception? + + // working under the assumption you do not want to co-mingle updates + // for a single row within a single call here, we will *replace* + // (vs. update) anything that was existing in the map. This feels + // wrong... + + update = new ConditionalUpdates(); + updates.put(key, update); + mutationMap.put(key, mutation); + update.setConditions(ThriftHelper.toThriftCondition(mutation.getConditions())); + List tupdates = new LinkedList(); + ThriftHelper.addThriftColumnUpdates(tupdates, mutation.getUpdates()); + update.setUpdates(tupdates); + } + + try { + Map status = client.updateRowsConditionally(writerId, updates); + + List results = new LinkedList(); + for (Entry entry : status.entrySet()) { + ConditionalMutation cm = mutationMap.get(entry.getKey()); + Status staus = ThriftHelper.convertEnum(entry.getValue(), Status.class); + Result r = new Result(staus, cm, null) { + + @Override + public String getTabletServer() { + // we are not given the tablet server nor have any way + // to find out what it is... + throw ExceptionFactory.unsupported(); + } + }; + results.add(r); + } + return results.iterator(); + } catch (TException e) { + throw ExceptionFactory.runtimeException(e); + } + } + + public Result write(ConditionalMutation mutation) { + Iterator results = write(Collections.singleton(mutation).iterator()); + return results.next(); + } + + public void close() { + try { + client.closeConditionalWriter(writerId); + } catch (TException e) { + throw ExceptionFactory.runtimeException(e); + } + } + +} diff --git a/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyConnector.java b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyConnector.java new file mode 100644 index 0000000..f534cb3 --- /dev/null +++ b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyConnector.java @@ -0,0 +1,170 @@ +/** + * Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + * + * 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 edu.jhuapl.accumulo.proxy; + +import static edu.jhuapl.accumulo.proxy.ThriftHelper.UTF8; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.apache.accumulo.core.client.BatchDeleter; +import org.apache.accumulo.core.client.BatchScanner; +import org.apache.accumulo.core.client.BatchWriter; +import org.apache.accumulo.core.client.BatchWriterConfig; +import org.apache.accumulo.core.client.ConditionalWriter; +import org.apache.accumulo.core.client.ConditionalWriterConfig; +import org.apache.accumulo.core.client.Connector; +import org.apache.accumulo.core.client.Instance; +import org.apache.accumulo.core.client.MultiTableBatchWriter; +import org.apache.accumulo.core.client.Scanner; +import org.apache.accumulo.core.client.TableNotFoundException; +import org.apache.accumulo.core.client.admin.InstanceOperations; +import org.apache.accumulo.core.client.admin.NamespaceOperations; +import org.apache.accumulo.core.client.admin.SecurityOperations; +import org.apache.accumulo.core.client.admin.TableOperations; +import org.apache.accumulo.core.client.security.tokens.AuthenticationToken; +import org.apache.accumulo.core.client.security.tokens.PasswordToken; +import org.apache.accumulo.core.security.Authorizations; +import org.apache.accumulo.proxy.thrift.AccumuloProxy; +import org.apache.accumulo.proxy.thrift.AccumuloSecurityException; +import org.apache.thrift.TException; + +class ProxyConnector extends Connector { + + ProxyInstance instance; + String principal; + ByteBuffer token; + + ProxyConnector(ProxyInstance instance, String principal, AuthenticationToken auth) throws AccumuloSecurityException, TException { + // TODO probably a better way to do this... + if (!(auth instanceof PasswordToken)) { + throw new IllegalArgumentException("Currently only works with PasswordTokens."); + } + + this.instance = instance; + this.principal = principal; + String passwd = new String(((PasswordToken) auth).getPassword(), UTF8); + Map password = new HashMap(); + password.put("password", passwd); + token = instance.getClient().login(principal, password); + } + + @Override + public BatchScanner createBatchScanner(String tableName, Authorizations authorizations, int numQueryThreads) throws TableNotFoundException { + if (!tableOperations().exists(tableName)) { + throw new TableNotFoundException(null, tableName, null); + } + return new ProxyBatchScanner(this, token, tableName, authorizations, numQueryThreads, instance.getBatchScannerFetchSize()); + } + + @Override + @Deprecated + public BatchDeleter createBatchDeleter(String tableName, Authorizations authorizations, int numQueryThreads, long maxMemory, long maxLatency, + int maxWriteThreads) throws TableNotFoundException { + return createBatchDeleter(tableName, authorizations, numQueryThreads, new BatchWriterConfig().setMaxLatency(maxLatency, TimeUnit.MILLISECONDS) + .setMaxMemory(maxMemory).setMaxWriteThreads(maxWriteThreads)); + } + + @Override + public BatchDeleter createBatchDeleter(String tableName, Authorizations authorizations, int numQueryThreads, BatchWriterConfig config) + throws TableNotFoundException { + throw ExceptionFactory.notYetImplemented(); + } + + @Override + @Deprecated + public BatchWriter createBatchWriter(String tableName, long maxMemory, long maxLatency, int maxWriteThreads) throws TableNotFoundException { + return createBatchWriter(tableName, + new BatchWriterConfig().setMaxMemory(maxMemory).setMaxLatency(maxLatency, TimeUnit.MILLISECONDS).setMaxWriteThreads(maxWriteThreads)); + } + + @Override + public BatchWriter createBatchWriter(String tableName, BatchWriterConfig config) throws TableNotFoundException { + if (!tableOperations().exists(tableName)) { + throw new TableNotFoundException(null, tableName, null); + } + + try { + return new ProxyBatchWriter(this, token, tableName, config); + } catch (TException e) { + throw ExceptionFactory.runtimeException(e); + } + } + + @Override + @Deprecated + public MultiTableBatchWriter createMultiTableBatchWriter(long maxMemory, long maxLatency, int maxWriteThreads) { + return createMultiTableBatchWriter(new BatchWriterConfig().setMaxMemory(maxMemory).setMaxLatency(maxLatency, TimeUnit.MILLISECONDS) + .setMaxWriteThreads(maxWriteThreads)); + } + + @Override + public MultiTableBatchWriter createMultiTableBatchWriter(BatchWriterConfig config) { + return new ProxyMultiTableBatchWriter(this, this.token, config); + } + + @Override + public Scanner createScanner(String tableName, Authorizations authorizations) throws TableNotFoundException { + if (!tableOperations().exists(tableName)) { + throw new TableNotFoundException(null, tableName, null); + } + return new ProxyScanner(this, token, tableName, authorizations); + } + + @Override + public ConditionalWriter createConditionalWriter(String tableName, ConditionalWriterConfig config) throws TableNotFoundException { + if (!tableOperations().exists(tableName)) { + throw new TableNotFoundException(null, tableName, null); + } + return new ProxyConditionalWriter(this, token, tableName, config); + } + + AccumuloProxy.Iface getClient() { + return instance.getClient(); + } + + @Override + public Instance getInstance() { + return instance; + } + + @Override + public String whoami() { + return principal; + } + + @Override + public TableOperations tableOperations() { + return new ProxyTableOperations(this, token); + } + + @Override + public NamespaceOperations namespaceOperations() { + throw ExceptionFactory.unsupported(); + } + + @Override + public SecurityOperations securityOperations() { + return new ProxySecurityOperations(this, token); + } + + @Override + public InstanceOperations instanceOperations() { + return new ProxyInstanceOperations(this, token); + } +} diff --git a/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyInstance.java b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyInstance.java new file mode 100644 index 0000000..515b0e3 --- /dev/null +++ b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyInstance.java @@ -0,0 +1,722 @@ +/** + * Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + * + * 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 edu.jhuapl.accumulo.proxy; + +import java.io.Closeable; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.accumulo.core.client.AccumuloException; +import org.apache.accumulo.core.client.AccumuloSecurityException; +import org.apache.accumulo.core.client.Connector; +import org.apache.accumulo.core.client.Instance; +import org.apache.accumulo.core.client.security.tokens.AuthenticationToken; +import org.apache.accumulo.core.client.security.tokens.PasswordToken; +import org.apache.accumulo.core.conf.AccumuloConfiguration; +import org.apache.accumulo.proxy.thrift.AccumuloProxy; +import org.apache.accumulo.proxy.thrift.ActiveCompaction; +import org.apache.accumulo.proxy.thrift.ActiveScan; +import org.apache.accumulo.proxy.thrift.BatchScanOptions; +import org.apache.accumulo.proxy.thrift.ColumnUpdate; +import org.apache.accumulo.proxy.thrift.ConditionalStatus; +import org.apache.accumulo.proxy.thrift.ConditionalUpdates; +import org.apache.accumulo.proxy.thrift.ConditionalWriterOptions; +import org.apache.accumulo.proxy.thrift.DiskUsage; +import org.apache.accumulo.proxy.thrift.IteratorScope; +import org.apache.accumulo.proxy.thrift.IteratorSetting; +import org.apache.accumulo.proxy.thrift.Key; +import org.apache.accumulo.proxy.thrift.KeyValueAndPeek; +import org.apache.accumulo.proxy.thrift.MutationsRejectedException; +import org.apache.accumulo.proxy.thrift.NoMoreEntriesException; +import org.apache.accumulo.proxy.thrift.PartialKey; +import org.apache.accumulo.proxy.thrift.Range; +import org.apache.accumulo.proxy.thrift.ScanOptions; +import org.apache.accumulo.proxy.thrift.ScanResult; +import org.apache.accumulo.proxy.thrift.SystemPermission; +import org.apache.accumulo.proxy.thrift.TableExistsException; +import org.apache.accumulo.proxy.thrift.TableNotFoundException; +import org.apache.accumulo.proxy.thrift.TablePermission; +import org.apache.accumulo.proxy.thrift.TimeType; +import org.apache.accumulo.proxy.thrift.UnknownScanner; +import org.apache.accumulo.proxy.thrift.UnknownWriter; +import org.apache.accumulo.proxy.thrift.WriterOptions; +import org.apache.thrift.TException; +import org.apache.thrift.protocol.TCompactProtocol; +import org.apache.thrift.protocol.TProtocol; +import org.apache.thrift.transport.TFramedTransport; +import org.apache.thrift.transport.TSocket; +import org.apache.thrift.transport.TTransport; +import org.apache.thrift.transport.TTransportException; +import org.slf4j.LoggerFactory; + +/** + * A proxy instance that uses the Accumulo Proxy Thrift interface to fulfill the Accumulo client APIs. Note this instance implements Closeable and, while not + * part of the public Instance API, should be closed when no longer needed. + */ +public class ProxyInstance implements Instance, Closeable { + + /** + * Default fetch size to be used for BatchScanners. Currently equal to 1,000. + */ + private static final int DEFAULT_FETCH_SIZE = 1_000; + + private AccumuloProxy.Iface client; + + private TTransport transport; + private int fetchSize; + + /** + * Assumes a TSocket transport wrapped by a TFramedTransport. + * + * @param host + * the host name or IP address where the Accumulo Thrift Proxy server is running + * @param port + * the port where the Accumulo Thrift Proxy server is listening + * @throws TTransportException + * thrown if the Thrift TTransport cannot be established. + */ + public ProxyInstance(String host, int port) throws TTransportException { + this(host, port, DEFAULT_FETCH_SIZE); + } + + /** + * Assumes a TSocket transport wrapped by a TFramedTransport. + * + * @param host + * the host name or IP address where the Accumulo Thrift Proxy server is running + * @param port + * the port where the Accumulo Thrift Proxy server is listening + * @param fetchSize + * the fetch size for BatchScanners + * @throws TTransportException + * thrown if the Thrift TTransport cannot be established. + */ + public ProxyInstance(String host, int port, int fetchSize) throws TTransportException { + this(new TFramedTransport(new TSocket(host, port)), fetchSize); + } + + public ProxyInstance(TTransport transport) throws TTransportException { + this(transport, DEFAULT_FETCH_SIZE); + } + + /** + * + * @param transport + * Thrift transport to communicate with Proxy Server + * @param fetchSize + * the fetch size for BatchScanners. Must be 0 < fetchSize <= 2000. If fetchSize is outside of this range, a warning will be logged and the + * {@link #DEFAULT_FETCH_SIZE} will be used. + * @throws TTransportException + * thrown if the Thrift TTransport cannot be established. + */ + public ProxyInstance(TTransport transport, int fetchSize) throws TTransportException { + if (!transport.isOpen()) { + transport.open(); + } + TProtocol protocol = new TCompactProtocol(transport); + client = new SynchronizedProxy(new AccumuloProxy.Client(protocol)); + this.transport = transport; + + if (fetchSize <= 0 || fetchSize > 2000) { + LoggerFactory.getLogger(ProxyInstance.class).warn( + "Fetch size out of range (0 < fetchSize <= 2000): " + fetchSize + "; using default: " + DEFAULT_FETCH_SIZE); + this.fetchSize = DEFAULT_FETCH_SIZE; + } else { + this.fetchSize = fetchSize; + } + } + + public int getBatchScannerFetchSize() { + return fetchSize; + } + + public String getRootTabletLocation() { + throw ExceptionFactory.unsupported(); + } + + public List getMasterLocations() { + throw ExceptionFactory.unsupported(); + } + + public String getInstanceID() { + throw ExceptionFactory.unsupported(); + } + + public String getInstanceName() { + throw ExceptionFactory.unsupported(); + } + + public String getZooKeepers() { + throw ExceptionFactory.unsupported(); + } + + public int getZooKeepersSessionTimeOut() { + throw ExceptionFactory.unsupported(); + } + + @Deprecated + public Connector getConnector(String user, byte[] pass) throws AccumuloException, AccumuloSecurityException { + return getConnector(user, new PasswordToken(pass)); + } + + @Deprecated + public Connector getConnector(String user, ByteBuffer pass) throws AccumuloException, AccumuloSecurityException { + return getConnector(user, new PasswordToken(pass)); + } + + @Deprecated + public Connector getConnector(String user, CharSequence pass) throws AccumuloException, AccumuloSecurityException { + return getConnector(user, new PasswordToken(pass)); + } + + @Deprecated + public AccumuloConfiguration getConfiguration() { + throw ExceptionFactory.unsupported(); + } + + @Deprecated + public void setConfiguration(AccumuloConfiguration conf) { + throw ExceptionFactory.unsupported(); + } + + public Connector getConnector(String principal, AuthenticationToken token) throws AccumuloException, AccumuloSecurityException { + try { + return new ProxyConnector(this, principal, token); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + AccumuloProxy.Iface getClient() { + return client; + } + + public void close() { + // TODO- Neither Instance API nor Connector have a "close" method. But + // we need to close the transport when done. How to handle it? Currently + // clients must cast their Instance as a ProxyInstance and call close, + // *hope* the finalize method is called, or just be a bad citizen and + // not clean up resources on the proxy server. + if (transport != null) { + try { + transport.close(); + } finally { + transport = null; + client = null; + } + } + } + + @Override + protected void finalize() { + close(); + } + + /** + * A wrapper class that synchronizes every method in the Iface interface against this object, and then passes the call through to the wrapped Iface delegate. + * Due to the asynchronous nature of Thrift internals, if multiple threads use the same Iface object, the server may receive requests out-of-order and fail. + * This class helps mitigate that risk by ensuring only one thread is ever communicating with the proxy at one time. This incurs a performance hit, but if you + * are using the proxy instance, you probably aren't too concerned about throughput anyway... + */ + private static class SynchronizedProxy implements AccumuloProxy.Iface { + + AccumuloProxy.Iface delegate; + + SynchronizedProxy(AccumuloProxy.Iface delegate) { + this.delegate = delegate; + } + + @Override + public synchronized ByteBuffer login(String principal, Map loginProperties) + throws org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TException { + return delegate.login(principal, loginProperties); + } + + @Override + public synchronized int addConstraint(ByteBuffer login, String tableName, String constraintClassName) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, + TException { + return delegate.addConstraint(login, tableName, constraintClassName); + } + + @Override + public synchronized void addSplits(ByteBuffer login, String tableName, Set splits) throws org.apache.accumulo.proxy.thrift.AccumuloException, + org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, TException { + delegate.addSplits(login, tableName, splits); + } + + @Override + public synchronized void attachIterator(ByteBuffer login, String tableName, IteratorSetting setting, Set scopes) + throws org.apache.accumulo.proxy.thrift.AccumuloSecurityException, org.apache.accumulo.proxy.thrift.AccumuloException, TableNotFoundException, + TException { + delegate.attachIterator(login, tableName, setting, scopes); + } + + @Override + public synchronized void checkIteratorConflicts(ByteBuffer login, String tableName, IteratorSetting setting, Set scopes) + throws org.apache.accumulo.proxy.thrift.AccumuloSecurityException, org.apache.accumulo.proxy.thrift.AccumuloException, TableNotFoundException, + TException { + delegate.checkIteratorConflicts(login, tableName, setting, scopes); + } + + @Override + public synchronized void clearLocatorCache(ByteBuffer login, String tableName) throws TableNotFoundException, TException { + delegate.clearLocatorCache(login, tableName); + } + + @Override + public synchronized void cloneTable(ByteBuffer login, String tableName, String newTableName, boolean flush, Map propertiesToSet, + Set propertiesToExclude) throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, + TableNotFoundException, TableExistsException, TException { + delegate.cloneTable(login, tableName, newTableName, flush, propertiesToSet, propertiesToExclude); + } + + @Override + public synchronized void compactTable(ByteBuffer login, String tableName, ByteBuffer startRow, ByteBuffer endRow, List iterators, + boolean flush, boolean wait) throws org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, + org.apache.accumulo.proxy.thrift.AccumuloException, TException { + delegate.compactTable(login, tableName, startRow, endRow, iterators, flush, wait); + } + + @Override + public synchronized void cancelCompaction(ByteBuffer login, String tableName) throws org.apache.accumulo.proxy.thrift.AccumuloSecurityException, + TableNotFoundException, org.apache.accumulo.proxy.thrift.AccumuloException, TException { + delegate.cancelCompaction(login, tableName); + } + + @Override + public synchronized void createTable(ByteBuffer login, String tableName, boolean versioningIter, TimeType type) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableExistsException, TException { + delegate.createTable(login, tableName, versioningIter, type); + } + + @Override + public synchronized void deleteTable(ByteBuffer login, String tableName) throws org.apache.accumulo.proxy.thrift.AccumuloException, + org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, TException { + delegate.deleteTable(login, tableName); + } + + @Override + public synchronized void deleteRows(ByteBuffer login, String tableName, ByteBuffer startRow, ByteBuffer endRow) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, + TException { + delegate.deleteRows(login, tableName, startRow, endRow); + } + + @Override + public synchronized void exportTable(ByteBuffer login, String tableName, String exportDir) throws org.apache.accumulo.proxy.thrift.AccumuloException, + org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, TException { + delegate.exportTable(login, tableName, exportDir); + } + + @Override + public synchronized void flushTable(ByteBuffer login, String tableName, ByteBuffer startRow, ByteBuffer endRow, boolean wait) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, + TException { + delegate.flushTable(login, tableName, startRow, endRow, wait); + } + + @Override + public synchronized List getDiskUsage(ByteBuffer login, Set tables) throws org.apache.accumulo.proxy.thrift.AccumuloException, + org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, TException { + return delegate.getDiskUsage(login, tables); + } + + @Override + public synchronized Map> getLocalityGroups(ByteBuffer login, String tableName) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, + TException { + return delegate.getLocalityGroups(login, tableName); + } + + @Override + public synchronized IteratorSetting getIteratorSetting(ByteBuffer login, String tableName, String iteratorName, IteratorScope scope) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, + TException { + return delegate.getIteratorSetting(login, tableName, iteratorName, scope); + } + + @Override + public synchronized ByteBuffer getMaxRow(ByteBuffer login, String tableName, Set auths, ByteBuffer startRow, boolean startInclusive, + ByteBuffer endRow, boolean endInclusive) throws org.apache.accumulo.proxy.thrift.AccumuloException, + org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, TException { + return delegate.getMaxRow(login, tableName, auths, startRow, startInclusive, endRow, endInclusive); + } + + @Override + public synchronized Map getTableProperties(ByteBuffer login, String tableName) throws org.apache.accumulo.proxy.thrift.AccumuloException, + org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, TException { + return delegate.getTableProperties(login, tableName); + } + + @Override + public synchronized void importDirectory(ByteBuffer login, String tableName, String importDir, String failureDir, boolean setTime) + throws TableNotFoundException, org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, + TException { + delegate.importDirectory(login, tableName, importDir, failureDir, setTime); + } + + @Override + public synchronized void importTable(ByteBuffer login, String tableName, String importDir) throws TableExistsException, + org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TException { + delegate.importTable(login, tableName, importDir); + } + + @Override + public synchronized List listSplits(ByteBuffer login, String tableName, int maxSplits) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, + TException { + return delegate.listSplits(login, tableName, maxSplits); + } + + @Override + public synchronized Set listTables(ByteBuffer login) throws TException { + return delegate.listTables(login); + } + + @Override + public synchronized Map> listIterators(ByteBuffer login, String tableName) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, + TException { + return delegate.listIterators(login, tableName); + } + + @Override + public synchronized Map listConstraints(ByteBuffer login, String tableName) throws org.apache.accumulo.proxy.thrift.AccumuloException, + org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, TException { + return delegate.listConstraints(login, tableName); + } + + @Override + public synchronized void mergeTablets(ByteBuffer login, String tableName, ByteBuffer startRow, ByteBuffer endRow) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, + TException { + delegate.mergeTablets(login, tableName, startRow, endRow); + } + + @Override + public synchronized void offlineTable(ByteBuffer login, String tableName, boolean wait) throws org.apache.accumulo.proxy.thrift.AccumuloException, + org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, TException { + delegate.offlineTable(login, tableName, wait); + } + + @Override + public synchronized void onlineTable(ByteBuffer login, String tableName, boolean wait) throws org.apache.accumulo.proxy.thrift.AccumuloException, + org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, TException { + delegate.onlineTable(login, tableName, wait); + } + + @Override + public synchronized void removeConstraint(ByteBuffer login, String tableName, int constraint) throws org.apache.accumulo.proxy.thrift.AccumuloException, + org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, TException { + delegate.removeConstraint(login, tableName, constraint); + } + + @Override + public synchronized void removeIterator(ByteBuffer login, String tableName, String iterName, Set scopes) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, + TException { + delegate.removeIterator(login, tableName, iterName, scopes); + } + + @Override + public synchronized void removeTableProperty(ByteBuffer login, String tableName, String property) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, + TException { + delegate.removeTableProperty(login, tableName, property); + } + + @Override + public synchronized void renameTable(ByteBuffer login, String oldTableName, String newTableName) throws org.apache.accumulo.proxy.thrift.AccumuloException, + org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, TableExistsException, TException { + delegate.renameTable(login, oldTableName, newTableName); + } + + @Override + public synchronized void setLocalityGroups(ByteBuffer login, String tableName, Map> groups) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, + TException { + delegate.setLocalityGroups(login, tableName, groups); + } + + @Override + public synchronized void setTableProperty(ByteBuffer login, String tableName, String property, String value) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, + TException { + delegate.setTableProperty(login, tableName, property, value); + } + + @Override + public synchronized Set splitRangeByTablets(ByteBuffer login, String tableName, Range range, int maxSplits) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, + TException { + return delegate.splitRangeByTablets(login, tableName, range, maxSplits); + } + + @Override + public synchronized boolean tableExists(ByteBuffer login, String tableName) throws TException { + return delegate.tableExists(login, tableName); + } + + @Override + public synchronized Map tableIdMap(ByteBuffer login) throws TException { + return delegate.tableIdMap(login); + } + + @Override + public synchronized boolean testTableClassLoad(ByteBuffer login, String tableName, String className, String asTypeName) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, + TException { + return delegate.testClassLoad(login, className, asTypeName); + } + + @Override + public synchronized void pingTabletServer(ByteBuffer login, String tserver) throws org.apache.accumulo.proxy.thrift.AccumuloException, + org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TException { + delegate.pingTabletServer(login, tserver); + } + + @Override + public synchronized List getActiveScans(ByteBuffer login, String tserver) throws org.apache.accumulo.proxy.thrift.AccumuloException, + org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TException { + return delegate.getActiveScans(login, tserver); + } + + @Override + public synchronized List getActiveCompactions(ByteBuffer login, String tserver) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TException { + return delegate.getActiveCompactions(login, tserver); + } + + @Override + public synchronized Map getSiteConfiguration(ByteBuffer login) throws org.apache.accumulo.proxy.thrift.AccumuloException, + org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TException { + return delegate.getSiteConfiguration(login); + } + + @Override + public synchronized Map getSystemConfiguration(ByteBuffer login) throws org.apache.accumulo.proxy.thrift.AccumuloException, + org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TException { + return delegate.getSystemConfiguration(login); + } + + @Override + public synchronized List getTabletServers(ByteBuffer login) throws TException { + return delegate.getTabletServers(login); + } + + @Override + public synchronized void removeProperty(ByteBuffer login, String property) throws org.apache.accumulo.proxy.thrift.AccumuloException, + org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TException { + delegate.removeProperty(login, property); + } + + @Override + public synchronized void setProperty(ByteBuffer login, String property, String value) throws org.apache.accumulo.proxy.thrift.AccumuloException, + org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TException { + delegate.setProperty(login, property, value); + } + + @Override + public synchronized boolean testClassLoad(ByteBuffer login, String className, String asTypeName) throws org.apache.accumulo.proxy.thrift.AccumuloException, + org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TException { + return delegate.testClassLoad(login, className, asTypeName); + } + + @Override + public synchronized boolean authenticateUser(ByteBuffer login, String user, Map properties) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TException { + return delegate.authenticateUser(login, user, properties); + } + + @Override + public synchronized void changeUserAuthorizations(ByteBuffer login, String user, Set authorizations) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TException { + delegate.changeUserAuthorizations(login, user, authorizations); + } + + @Override + public synchronized void changeLocalUserPassword(ByteBuffer login, String user, ByteBuffer password) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TException { + delegate.changeLocalUserPassword(login, user, password); + } + + @Override + public synchronized void createLocalUser(ByteBuffer login, String user, ByteBuffer password) throws org.apache.accumulo.proxy.thrift.AccumuloException, + org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TException { + delegate.createLocalUser(login, user, password); + } + + @Override + public synchronized void dropLocalUser(ByteBuffer login, String user) throws org.apache.accumulo.proxy.thrift.AccumuloException, + org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TException { + delegate.dropLocalUser(login, user); + } + + @Override + public synchronized List getUserAuthorizations(ByteBuffer login, String user) throws org.apache.accumulo.proxy.thrift.AccumuloException, + org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TException { + return delegate.getUserAuthorizations(login, user); + } + + @Override + public synchronized void grantSystemPermission(ByteBuffer login, String user, SystemPermission perm) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TException { + delegate.grantSystemPermission(login, user, perm); + } + + @Override + public synchronized void grantTablePermission(ByteBuffer login, String user, String table, TablePermission perm) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, + TException { + delegate.grantTablePermission(login, user, table, perm); + } + + @Override + public synchronized boolean hasSystemPermission(ByteBuffer login, String user, SystemPermission perm) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TException { + return delegate.hasSystemPermission(login, user, perm); + } + + @Override + public synchronized boolean hasTablePermission(ByteBuffer login, String user, String table, TablePermission perm) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, + TException { + return delegate.hasTablePermission(login, user, table, perm); + } + + @Override + public synchronized Set listLocalUsers(ByteBuffer login) throws org.apache.accumulo.proxy.thrift.AccumuloException, + org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, TException { + return delegate.listLocalUsers(login); + } + + @Override + public synchronized void revokeSystemPermission(ByteBuffer login, String user, SystemPermission perm) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TException { + delegate.revokeSystemPermission(login, user, perm); + } + + @Override + public synchronized void revokeTablePermission(ByteBuffer login, String user, String table, TablePermission perm) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, + TException { + delegate.revokeTablePermission(login, user, table, perm); + } + + @Override + public synchronized String createBatchScanner(ByteBuffer login, String tableName, BatchScanOptions options) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, + TException { + return delegate.createBatchScanner(login, tableName, options); + } + + @Override + public synchronized String createScanner(ByteBuffer login, String tableName, ScanOptions options) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, + TException { + return delegate.createScanner(login, tableName, options); + } + + @Override + public synchronized boolean hasNext(String scanner) throws UnknownScanner, TException { + return delegate.hasNext(scanner); + } + + @Override + public synchronized KeyValueAndPeek nextEntry(String scanner) throws NoMoreEntriesException, UnknownScanner, + org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TException { + return delegate.nextEntry(scanner); + } + + @Override + public synchronized ScanResult nextK(String scanner, int k) throws NoMoreEntriesException, UnknownScanner, + org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TException { + return delegate.nextK(scanner, k); + } + + @Override + public synchronized void closeScanner(String scanner) throws UnknownScanner, TException { + delegate.closeScanner(scanner); + } + + @Override + public synchronized void updateAndFlush(ByteBuffer login, String tableName, Map> cells) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, + MutationsRejectedException, TException { + delegate.updateAndFlush(login, tableName, cells); + } + + @Override + public synchronized String createWriter(ByteBuffer login, String tableName, WriterOptions opts) throws org.apache.accumulo.proxy.thrift.AccumuloException, + org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, TException { + return delegate.createWriter(login, tableName, opts); + } + + @Override + public synchronized void update(String writer, Map> cells) throws TException { + delegate.update(writer, cells); + } + + @Override + public synchronized void flush(String writer) throws UnknownWriter, MutationsRejectedException, TException { + delegate.flush(writer); + } + + @Override + public synchronized void closeWriter(String writer) throws UnknownWriter, MutationsRejectedException, TException { + delegate.closeWriter(writer); + } + + @Override + public synchronized ConditionalStatus updateRowConditionally(ByteBuffer login, String tableName, ByteBuffer row, ConditionalUpdates updates) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, + TException { + return delegate.updateRowConditionally(login, tableName, row, updates); + } + + @Override + public synchronized String createConditionalWriter(ByteBuffer login, String tableName, ConditionalWriterOptions options) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TableNotFoundException, + TException { + return delegate.createConditionalWriter(login, tableName, options); + } + + @Override + public synchronized Map updateRowsConditionally(String conditionalWriter, Map updates) + throws UnknownWriter, org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, TException { + return delegate.updateRowsConditionally(conditionalWriter, updates); + } + + @Override + public synchronized void closeConditionalWriter(String conditionalWriter) throws TException { + delegate.closeConditionalWriter(conditionalWriter); + } + + @Override + public synchronized Range getRowRange(ByteBuffer row) throws TException { + return delegate.getRowRange(row); + } + + @Override + public synchronized Key getFollowing(Key key, PartialKey part) throws TException { + return delegate.getFollowing(key, part); + } + } + +} diff --git a/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyInstanceError.java b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyInstanceError.java new file mode 100644 index 0000000..3120a6f --- /dev/null +++ b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyInstanceError.java @@ -0,0 +1,39 @@ +/** + * Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + * + * 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 edu.jhuapl.accumulo.proxy; + +/** + * Error throws when an unrecoverable condition has been reached. + */ +public class ProxyInstanceError extends Error { + + private static final long serialVersionUID = -6542283312575106269L; + + /** + * Creates a new error with the given cause. + * + * @param cause + * the root cause of this error + */ + public ProxyInstanceError(Throwable cause) { + super(cause); + } + + public ProxyInstanceError(String msg) { + super(msg); + } + +} diff --git a/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyInstanceOperations.java b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyInstanceOperations.java new file mode 100644 index 0000000..e29bbca --- /dev/null +++ b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyInstanceOperations.java @@ -0,0 +1,114 @@ +/** + * Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + * + * 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 edu.jhuapl.accumulo.proxy; + +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; + +import org.apache.accumulo.core.client.AccumuloException; +import org.apache.accumulo.core.client.AccumuloSecurityException; +import org.apache.accumulo.core.client.admin.ActiveCompaction; +import org.apache.accumulo.core.client.admin.ActiveScan; +import org.apache.accumulo.core.client.admin.InstanceOperations; +import org.apache.thrift.TException; + +/** + * Provides a pass through for InstaceOperations to a proxy server. + */ +class ProxyInstanceOperations implements InstanceOperations { + + ProxyConnector connector; + ByteBuffer token; + + ProxyInstanceOperations(ProxyConnector connector, ByteBuffer token) { + this.connector = connector; + this.token = token; + } + + public void setProperty(String property, String value) throws AccumuloException, AccumuloSecurityException { + try { + connector.getClient().setProperty(token, property, value); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public void removeProperty(String property) throws AccumuloException, AccumuloSecurityException { + try { + connector.getClient().removeProperty(token, property); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public Map getSystemConfiguration() throws AccumuloException, AccumuloSecurityException { + try { + return connector.getClient().getSystemConfiguration(token); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public Map getSiteConfiguration() throws AccumuloException, AccumuloSecurityException { + try { + return connector.getClient().getSiteConfiguration(token); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public List getTabletServers() { + try { + return connector.getClient().getTabletServers(token); + } catch (TException e) { + throw ExceptionFactory.runtimeException(e); + } + } + + public List getActiveScans(String tserver) throws AccumuloException, AccumuloSecurityException { + try { + return ThriftHelper.fromThriftActiveScans(connector.getClient().getActiveScans(token, tserver)); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public List getActiveCompactions(String tserver) throws AccumuloException, AccumuloSecurityException { + try { + return ThriftHelper.fromThriftActiveCompactions(connector.tableOperations().tableIdMap(), connector.getClient().getActiveCompactions(token, tserver)); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public void ping(String tserver) throws AccumuloException { + try { + connector.getClient().pingTabletServer(token, tserver); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public boolean testClassLoad(String className, String asTypeName) throws AccumuloException, AccumuloSecurityException { + try { + return connector.getClient().testClassLoad(token, className, asTypeName); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + +} diff --git a/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyMultiTableBatchWriter.java b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyMultiTableBatchWriter.java new file mode 100644 index 0000000..e353b4b --- /dev/null +++ b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyMultiTableBatchWriter.java @@ -0,0 +1,215 @@ +/** + * Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + * + * 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 edu.jhuapl.accumulo.proxy; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.accumulo.core.client.AccumuloException; +import org.apache.accumulo.core.client.AccumuloSecurityException; +import org.apache.accumulo.core.client.BatchWriter; +import org.apache.accumulo.core.client.BatchWriterConfig; +import org.apache.accumulo.core.client.MultiTableBatchWriter; +import org.apache.accumulo.core.client.MutationsRejectedException; +import org.apache.accumulo.core.client.TableNotFoundException; +import org.apache.accumulo.core.client.security.SecurityErrorCode; +import org.apache.accumulo.core.data.ConstraintViolationSummary; +import org.apache.accumulo.core.data.KeyExtent; +import org.apache.accumulo.core.data.Mutation; +import org.apache.accumulo.proxy.thrift.UnknownWriter; +import org.apache.accumulo.proxy.thrift.WriterOptions; +import org.apache.thrift.TException; +import org.slf4j.LoggerFactory; + +/** + * Implementation of a MultiTableBatchWriter for the Proxy Server. The ProxyServer API does not actually expose a MultiTableBatchWriter. Instead, we will "fake" + * it by managing our own internal list of "regular" ProxyBatchWriters in this class, one for each table requested. + */ +class ProxyMultiTableBatchWriter implements MultiTableBatchWriter { + + ProxyConnector connector; + ByteBuffer token; + BatchWriterConfig config; + + MutationBuffer buffer; + Map writers; + + ProxyMultiTableBatchWriter(ProxyConnector connector, ByteBuffer token, BatchWriterConfig config) { + this.connector = connector; + this.token = token; + this.config = config; + writers = new HashMap(); + buffer = new MutationBuffer(connector, config); + } + + private void checkClosed() throws IllegalStateException { + if (isClosed()) { + throw new IllegalStateException("Closed."); + } + } + + public BatchWriter getBatchWriter(String table) throws AccumuloException, AccumuloSecurityException, TableNotFoundException { + checkClosed(); + + MultiProxyBatchWriter pbw = writers.get(table); + + if (pbw == null) { + try { + pbw = new MultiProxyBatchWriter(connector, token, table, buffer); + writers.put(table, pbw); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + return pbw; + } + + public void flush() throws MutationsRejectedException { + flushOrClose(false); + } + + public void close() throws MutationsRejectedException { + flushOrClose(true); + } + + private void flushOrClose(boolean close) throws MutationsRejectedException { + checkClosed(); + + buffer.flush(); + + List exceptions = null; + try { + try { + for (MultiProxyBatchWriter pbw : writers.values()) { + if (close) { + pbw.multiClose(); + } else { + pbw.multiFlush(); + } + } + } catch (UnknownWriter e) { + throw ExceptionFactory.runtimeException(e); + } catch (org.apache.accumulo.proxy.thrift.MutationsRejectedException e) { + if (exceptions == null) { + exceptions = new ArrayList(); + } + exceptions.add(ThriftHelper.fromThrift(e, connector.getInstance())); + } catch (TException e) { + throw ExceptionFactory.runtimeException(e); + } + + writers.clear(); + } finally { + writers = null; + } + + checkExceptions(exceptions); + } + + private void checkExceptions(List mres) throws MutationsRejectedException { + if (mres == null || mres.isEmpty()) { + return; + } + + List cvsList = new LinkedList(); + HashMap> map = new HashMap>(); + Collection serverSideErrors = new LinkedList(); + int unknownErrors = 0; + + for (MutationsRejectedException mre : mres) { + cvsList.addAll(mre.getConstraintViolationSummaries()); + map.putAll(mre.getAuthorizationFailuresMap()); + serverSideErrors.addAll(mre.getErrorServers()); + unknownErrors += mre.getUnknownExceptions(); + } + + throw new MutationsRejectedException(null, cvsList, map, serverSideErrors, unknownErrors, null); + + } + + public boolean isClosed() { + return writers == null; + } + + @Override + protected void finalize() { + if (!isClosed()) { + LoggerFactory.getLogger(ProxyMultiTableBatchWriter.class).warn( + "ProxyMultiTableBatchWriter" + " in finalize but not closed; " + "you forgot to close a MultiTableBatchWriter!"); + + try { + close(); + } catch (MutationsRejectedException mre) { + LoggerFactory.getLogger(ProxyBatchScanner.class).warn("Problem closing ProxyMultiTableBatchWriter.", mre); + } + } + } + + private class MultiProxyBatchWriter implements BatchWriter { + + ProxyConnector connector; + String writerId; + MutationBuffer buffer; + + MultiProxyBatchWriter(ProxyConnector connector, ByteBuffer token, String table, MutationBuffer buffer) + throws org.apache.accumulo.proxy.thrift.AccumuloException, org.apache.accumulo.proxy.thrift.AccumuloSecurityException, + org.apache.accumulo.proxy.thrift.TableNotFoundException, TException { + this.connector = connector; + this.buffer = buffer; + writerId = connector.getClient().createWriter(token, table, new WriterOptions()); + } + + @Override + public void addMutation(Mutation m) throws MutationsRejectedException { + checkClosed(); + + buffer.addMutation(writerId, m); + } + + @Override + public void addMutations(Iterable iterable) throws MutationsRejectedException { + for (Mutation m : iterable) { + addMutation(m); + } + } + + @Override + public void flush() throws MutationsRejectedException { + throw new RuntimeException("Cannot flush BatchWriter created by MultiTableBatchWriter directly; " + "flush MultiTableBatchWriter instead."); + } + + @Override + public void close() throws MutationsRejectedException { + throw new RuntimeException("Cannot close BatchWriter created by MultiTableBatchWriter directly; " + "close MultiTableBatchWriter instead."); + + } + + void multiClose() throws UnknownWriter, org.apache.accumulo.proxy.thrift.MutationsRejectedException, TException { + connector.getClient().closeWriter(writerId); + } + + void multiFlush() throws UnknownWriter, org.apache.accumulo.proxy.thrift.MutationsRejectedException, TException { + connector.getClient().flush(writerId); + } + } + +} diff --git a/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyScanner.java b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyScanner.java new file mode 100644 index 0000000..da39e82 --- /dev/null +++ b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyScanner.java @@ -0,0 +1,172 @@ +/** + * Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + * + * 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 edu.jhuapl.accumulo.proxy; + +import java.nio.ByteBuffer; +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; + +import org.apache.accumulo.core.client.IteratorSetting; +import org.apache.accumulo.core.client.Scanner; +import org.apache.accumulo.core.data.Key; +import org.apache.accumulo.core.data.Range; +import org.apache.accumulo.core.data.Value; +import org.apache.accumulo.core.security.Authorizations; +import org.apache.accumulo.proxy.thrift.ScanColumn; +import org.apache.accumulo.proxy.thrift.ScanOptions; +import org.apache.thrift.TException; +import org.slf4j.LoggerFactory; + +class ProxyScanner extends AbstractProxyScanner implements Scanner { + + protected ScanOptions options; + int batchSize = 50; + + ProxyScanner(ProxyConnector connector, ByteBuffer token, String tableName, Authorizations auths) { + super(connector, token, tableName); + options = new ScanOptions(); + for (ByteBuffer auth : auths.getAuthorizationsBB()) { + options.addToAuthorizations(auth); + } + } + + public void addScanIterator(IteratorSetting cfg) { + options.addToIterators(ThriftHelper.toThrift(cfg)); + } + + public void removeScanIterator(String iteratorName) { + for (org.apache.accumulo.proxy.thrift.IteratorSetting is : options.iterators) { + if (is.getName().equals(iteratorName)) { + options.iterators.remove(is); + break; + } + } + } + + public void updateScanIteratorOption(String iteratorName, String key, String value) { + for (org.apache.accumulo.proxy.thrift.IteratorSetting is : options.iterators) { + if (is.getName().equals(iteratorName)) { + is.putToProperties(key, value); + } + } + } + + @Override + protected void addToFetchOptions(ScanColumn col) { + options.addToColumns(col); + } + + public void clearColumns() { + if (options.getColumns() != null) { + options.getColumns().clear(); + } + } + + public void clearScanIterators() { + if (options.getIterators() != null) { + options.getIterators().clear(); + } + } + + public Iterator> iterator() { + // TODO Close if older instance open? + try { + scannerId = connector.getClient().createScanner(token, tableName, options); + + // TODO Someone needs to close this scannerID! + return new ScannerIterator(scannerId, connector, batchSize); + } catch (TException e) { + throw ExceptionFactory.runtimeException(e); + } + } + + public void setTimeout(long timeOut, TimeUnit timeUnit) { + throw ExceptionFactory.unsupported(); + } + + public long getTimeout(TimeUnit timeUnit) { + throw ExceptionFactory.unsupported(); + } + + public void close() { + try { + connector.getClient().closeScanner(scannerId); + } catch (TException e) { + throw ExceptionFactory.runtimeException(e); + } finally { + scannerId = null; + } + } + + @Override + protected void finalize() { + if (scannerId != null) { + close(); + LoggerFactory.getLogger(ProxyBatchScanner.class).warn("Scanner " + scannerId + " in finalize but not closed; " + "you forgot to close a scanner!"); + } + } + + public void setRange(Range range) { + if (range == null) { + options.unsetRange(); + } else { + options.setRange(ThriftHelper.toThrift(range)); + } + } + + public Range getRange() { + return ThriftHelper.fromThrift(options.getRange()); + } + + public void setBatchSize(int size) { + if (size < 1) { + throw new IllegalArgumentException("Batch size must be > 0; provided " + size); + } + this.batchSize = size; + } + + public int getBatchSize() { + return batchSize; + } + + public void enableIsolation() { + throw ExceptionFactory.unsupported(); + } + + public void disableIsolation() { + throw ExceptionFactory.unsupported(); + } + + public long getReadaheadThreshold() { + throw ExceptionFactory.unsupported(); + } + + public void setReadaheadThreshold(long batches) { + throw ExceptionFactory.unsupported(); + } + + @Deprecated + public void setTimeOut(int timeOut) { + setTimeout(timeOut, TimeUnit.SECONDS); + } + + @Deprecated + public int getTimeOut() { + return (int) this.getTimeout(TimeUnit.SECONDS); + } + +} diff --git a/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxySecurityOperations.java b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxySecurityOperations.java new file mode 100644 index 0000000..94795bd --- /dev/null +++ b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxySecurityOperations.java @@ -0,0 +1,209 @@ +/** + * Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + * + * 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 edu.jhuapl.accumulo.proxy; + +import static edu.jhuapl.accumulo.proxy.ThriftHelper.UTF8; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.accumulo.core.client.AccumuloException; +import org.apache.accumulo.core.client.AccumuloSecurityException; +import org.apache.accumulo.core.client.admin.SecurityOperations; +import org.apache.accumulo.core.client.security.tokens.AuthenticationToken; +import org.apache.accumulo.core.client.security.tokens.PasswordToken; +import org.apache.accumulo.core.security.Authorizations; +import org.apache.accumulo.core.security.NamespacePermission; +import org.apache.accumulo.core.security.SystemPermission; +import org.apache.accumulo.core.security.TablePermission; +import org.apache.accumulo.proxy.thrift.AccumuloProxy; +import org.apache.thrift.TException; + +/** + * Implements SecurityOperations as a pass through to a proxy server. + */ + +class ProxySecurityOperations implements SecurityOperations { + + AccumuloProxy.Iface client; + ByteBuffer token; + + ProxySecurityOperations(ProxyConnector connector, ByteBuffer token) { + this.client = connector.getClient(); + this.token = token; + } + + @Deprecated + public void createUser(String user, byte[] password, Authorizations authorizations) throws AccumuloException, AccumuloSecurityException { + createLocalUser(user, new PasswordToken(password)); + changeUserAuthorizations(user, authorizations); + } + + public void createLocalUser(String principal, PasswordToken password) throws AccumuloException, AccumuloSecurityException { + try { + client.createLocalUser(token, principal, ByteBuffer.wrap(password.getPassword())); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + @Deprecated + public void dropUser(String user) throws AccumuloException, AccumuloSecurityException { + dropLocalUser(user); + } + + public void dropLocalUser(String principal) throws AccumuloException, AccumuloSecurityException { + try { + client.dropLocalUser(token, principal); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + @Deprecated + public boolean authenticateUser(String user, byte[] password) throws AccumuloException, AccumuloSecurityException { + return this.authenticateUser(user, new PasswordToken(password)); + } + + public boolean authenticateUser(String principal, AuthenticationToken token) throws AccumuloException, AccumuloSecurityException { + + if (!(token instanceof PasswordToken)) { + throw ExceptionFactory.notYetImplemented(); + } + PasswordToken passwd = (PasswordToken) token; + try { + Map properties = new HashMap(); + properties.put("password", new String(passwd.getPassword(), UTF8)); + return client.authenticateUser(this.token, principal, properties); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + @Deprecated + public void changeUserPassword(String user, byte[] password) throws AccumuloException, AccumuloSecurityException { + changeLocalUserPassword(user, new PasswordToken(password)); + } + + public void changeLocalUserPassword(String principal, PasswordToken token) throws AccumuloException, AccumuloSecurityException { + try { + client.changeLocalUserPassword(this.token, principal, ByteBuffer.wrap(token.getPassword())); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public void changeUserAuthorizations(String principal, Authorizations authorizations) throws AccumuloException, AccumuloSecurityException { + try { + client.changeUserAuthorizations(token, principal, new HashSet(authorizations.getAuthorizationsBB())); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public Authorizations getUserAuthorizations(String principal) throws AccumuloException, AccumuloSecurityException { + try { + return new Authorizations(client.getUserAuthorizations(token, principal)); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + /** + * ProxyAPI specifies AccumuloException and AccumuloSecurityException may be thrown. The IllegalArgumentException may be thrown if the + * {@link ThriftHelper#convertEnum(Enum, Class)} fails because the Java and Thrift SystemPermission no longer match; this should never happen. + */ + public boolean hasSystemPermission(String principal, SystemPermission perm) throws AccumuloException, AccumuloSecurityException, InternalError { + try { + return client.hasSystemPermission(token, principal, ThriftHelper.convertEnum(perm, org.apache.accumulo.proxy.thrift.SystemPermission.class)); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public boolean hasTablePermission(String principal, String table, TablePermission perm) throws AccumuloException, AccumuloSecurityException { + try { + return client.hasTablePermission(token, principal, table, ThriftHelper.convertEnum(perm, org.apache.accumulo.proxy.thrift.TablePermission.class)); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public boolean hasNamespacePermission(String principal, String namespace, NamespacePermission perm) throws AccumuloException, AccumuloSecurityException { + throw ExceptionFactory.unsupported(); + } + + public void grantSystemPermission(String principal, SystemPermission permission) throws AccumuloException, AccumuloSecurityException { + try { + client.grantSystemPermission(token, principal, ThriftHelper.convertEnum(permission, org.apache.accumulo.proxy.thrift.SystemPermission.class)); + + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public void grantTablePermission(String principal, String table, TablePermission permission) throws AccumuloException, AccumuloSecurityException { + try { + client.grantTablePermission(token, principal, table, ThriftHelper.convertEnum(permission, org.apache.accumulo.proxy.thrift.TablePermission.class)); + + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public void grantNamespacePermission(String principal, String namespace, NamespacePermission permission) throws AccumuloException, AccumuloSecurityException { + throw ExceptionFactory.unsupported(); + } + + public void revokeSystemPermission(String principal, SystemPermission permission) throws AccumuloException, AccumuloSecurityException { + try { + client.revokeSystemPermission(token, principal, ThriftHelper.convertEnum(permission, org.apache.accumulo.proxy.thrift.SystemPermission.class)); + + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public void revokeTablePermission(String principal, String table, TablePermission permission) throws AccumuloException, AccumuloSecurityException { + try { + client.revokeTablePermission(token, principal, table, ThriftHelper.convertEnum(permission, org.apache.accumulo.proxy.thrift.TablePermission.class)); + + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public void revokeNamespacePermission(String principal, String namespace, NamespacePermission permission) throws AccumuloException, AccumuloSecurityException { + throw ExceptionFactory.unsupported(); + } + + @Deprecated + public Set listUsers() throws AccumuloException, AccumuloSecurityException { + return listLocalUsers(); + } + + public Set listLocalUsers() throws AccumuloException, AccumuloSecurityException { + try { + return client.listLocalUsers(token); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + +} diff --git a/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyTableOperations.java b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyTableOperations.java new file mode 100644 index 0000000..dfbac89 --- /dev/null +++ b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ProxyTableOperations.java @@ -0,0 +1,528 @@ +/** + * Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + * + * 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 edu.jhuapl.accumulo.proxy; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.apache.accumulo.core.client.AccumuloException; +import org.apache.accumulo.core.client.AccumuloSecurityException; +import org.apache.accumulo.core.client.IteratorSetting; +import org.apache.accumulo.core.client.TableExistsException; +import org.apache.accumulo.core.client.TableNotFoundException; +import org.apache.accumulo.core.client.admin.DiskUsage; +import org.apache.accumulo.core.client.admin.TableOperations; +import org.apache.accumulo.core.client.admin.TimeType; +import org.apache.accumulo.core.client.impl.thrift.SecurityErrorCode; +import org.apache.accumulo.core.data.Range; +import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope; +import org.apache.accumulo.core.security.Authorizations; +import org.apache.accumulo.proxy.thrift.AccumuloProxy; +import org.apache.hadoop.io.Text; +import org.apache.thrift.TException; + +/** + * Implements TableOperations as a pass through to a proxy server. + */ +class ProxyTableOperations implements TableOperations { + + ProxyConnector connector; + AccumuloProxy.Iface client; + ByteBuffer token; + + ProxyTableOperations(ProxyConnector connector, ByteBuffer token) { + this.connector = connector; + this.client = connector.getClient(); + this.token = token; + } + + public SortedSet list() { + try { + return new TreeSet(client.listTables(token)); + } catch (TException te) { + throw ExceptionFactory.runtimeException(te); + } + } + + public boolean exists(String tableName) { + return list().contains(tableName); + } + + public void create(String tableName) throws AccumuloException, AccumuloSecurityException, TableExistsException { + create(tableName, true); + } + + public void create(String tableName, boolean limitVersion) throws AccumuloException, AccumuloSecurityException, TableExistsException { + create(tableName, limitVersion, TimeType.MILLIS); + } + + public void create(String tableName, boolean versioningIter, TimeType timeType) throws AccumuloException, AccumuloSecurityException, TableExistsException { + + org.apache.accumulo.proxy.thrift.TimeType ttype = org.apache.accumulo.proxy.thrift.TimeType.valueOf(timeType.toString()); + try { + client.createTable(token, tableName, versioningIter, ttype); + } catch (org.apache.accumulo.proxy.thrift.TableExistsException tee) { + throw ExceptionFactory.tableExistsException(tableName, tee); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public void importTable(String tableName, String importDir) throws TableExistsException, AccumuloException, AccumuloSecurityException { + try { + client.importTable(token, tableName, importDir); + } catch (org.apache.accumulo.proxy.thrift.TableExistsException tee) { + throw ExceptionFactory.tableExistsException(tableName, tee); + } catch (TException te) { + throw ExceptionFactory.accumuloException(te); + } + } + + public void exportTable(String tableName, String exportDir) throws TableNotFoundException, AccumuloException, AccumuloSecurityException { + try { + client.exportTable(token, tableName, exportDir); + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(tableName, tnfe); + } catch (TException te) { + throw ExceptionFactory.accumuloException(te); + } + } + + public void addSplits(String tableName, SortedSet partitionKeys) throws TableNotFoundException, AccumuloException, AccumuloSecurityException { + Set tsplits = new TreeSet(); + for (Text key : partitionKeys) { + tsplits.add(ByteBuffer.wrap(key.getBytes())); + } + + try { + client.addSplits(token, tableName, tsplits); + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(tableName, tnfe); + } catch (TException te) { + throw ExceptionFactory.accumuloException(te); + } + } + + @Deprecated + public Collection getSplits(String tableName) throws TableNotFoundException { + try { + return listSplits(tableName); + } catch (AccumuloSecurityException e) { + throw ExceptionFactory.runtimeException(e); + } catch (AccumuloException e) { + throw ExceptionFactory.runtimeException(e); + } + } + + public Collection listSplits(String tableName) throws TableNotFoundException, AccumuloSecurityException, AccumuloException { + return listSplits(tableName, Integer.MAX_VALUE); + } + + @Deprecated + public Collection getSplits(String tableName, int maxSplits) throws TableNotFoundException { + try { + return listSplits(tableName, maxSplits); + } catch (AccumuloSecurityException e) { + throw ExceptionFactory.runtimeException(e); + } catch (AccumuloException e) { + throw ExceptionFactory.runtimeException(e); + } + } + + public Collection listSplits(String tableName, int maxSplits) throws TableNotFoundException, AccumuloSecurityException, AccumuloException { + try { + List list = client.listSplits(token, tableName, maxSplits); + + List text = new ArrayList(list.size()); + for (ByteBuffer bb : list) { + text.add(new Text(bb.array())); + } + return text; + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(tableName, tnfe); + } catch (TException te) { + throw ExceptionFactory.accumuloException(te); + } + } + + public Text getMaxRow(String tableName, Authorizations auths, Text startRow, boolean startInclusive, Text endRow, boolean endInclusive) + throws TableNotFoundException, AccumuloException, AccumuloSecurityException { + try { + ByteBuffer answer = client.getMaxRow(token, tableName, new HashSet(auths.getAuthorizationsBB()), ThriftHelper.toByteBuffer(startRow), + startInclusive, ThriftHelper.toByteBuffer(endRow), endInclusive); + return new Text(answer.array()); + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(tableName, tnfe); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public void merge(String tableName, Text start, Text end) throws AccumuloException, AccumuloSecurityException, TableNotFoundException { + try { + client.mergeTablets(token, tableName, ThriftHelper.toByteBuffer(start), ThriftHelper.toByteBuffer(end)); + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(tableName, tnfe); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public void deleteRows(String tableName, Text start, Text end) throws AccumuloException, AccumuloSecurityException, TableNotFoundException { + try { + client.deleteRows(token, tableName, ThriftHelper.toByteBuffer(start), ThriftHelper.toByteBuffer(end)); + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(tableName, tnfe); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public void compact(String tableName, Text start, Text end, boolean flush, boolean wait) throws AccumuloSecurityException, TableNotFoundException, + AccumuloException { + compact(tableName, start, end, null, flush, wait); + } + + public void compact(String tableName, Text start, Text end, List iterators, boolean flush, boolean wait) throws AccumuloSecurityException, + TableNotFoundException, AccumuloException { + try { + client.compactTable(token, tableName, ThriftHelper.toByteBuffer(start), ThriftHelper.toByteBuffer(end), ThriftHelper.toThriftIteratorSettings(iterators), + flush, wait); + } catch (org.apache.accumulo.proxy.thrift.AccumuloSecurityException e) { + throw new AccumuloSecurityException(connector.whoami(), SecurityErrorCode.DEFAULT_SECURITY_ERROR, "", e); + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(tableName, tnfe); + } catch (org.apache.accumulo.proxy.thrift.AccumuloException e) { + throw ExceptionFactory.accumuloException(e); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public void cancelCompaction(String tableName) throws AccumuloSecurityException, TableNotFoundException, AccumuloException { + try { + client.cancelCompaction(token, tableName); + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(tableName, tnfe); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public void delete(String tableName) throws AccumuloException, AccumuloSecurityException, TableNotFoundException { + try { + client.deleteTable(token, tableName); + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(tableName, tnfe); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public void clone(String srcTableName, String newTableName, boolean flush, Map propertiesToSet, Set propertiesToExclude) + throws AccumuloException, AccumuloSecurityException, TableNotFoundException, TableExistsException { + try { + client.cloneTable(token, srcTableName, newTableName, flush, propertiesToSet, propertiesToExclude); + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(srcTableName, tnfe); + } catch (org.apache.accumulo.proxy.thrift.TableExistsException tee) { + throw ExceptionFactory.tableExistsException(newTableName, tee); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public void rename(String oldTableName, String newTableName) throws AccumuloSecurityException, TableNotFoundException, AccumuloException, + TableExistsException { + try { + client.renameTable(token, oldTableName, newTableName); + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(oldTableName, tnfe); + } catch (org.apache.accumulo.proxy.thrift.TableExistsException tee) { + throw ExceptionFactory.tableExistsException(newTableName, tee); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + @Deprecated + public void flush(String tableName) throws AccumuloException, AccumuloSecurityException { + try { + flush(tableName, null, null, true); + } catch (TableNotFoundException tnfe) { + throw ExceptionFactory.accumuloException(tnfe); + } + } + + public void flush(String tableName, Text start, Text end, boolean wait) throws AccumuloException, AccumuloSecurityException, TableNotFoundException { + try { + client.flushTable(token, tableName, ThriftHelper.toByteBuffer(start), ThriftHelper.toByteBuffer(end), wait); + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(tableName, tnfe); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public void setProperty(String tableName, String property, String value) throws AccumuloException, AccumuloSecurityException { + try { + client.setProperty(token, property, value); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public void removeProperty(String tableName, String property) throws AccumuloException, AccumuloSecurityException { + try { + client.removeProperty(token, property); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public Iterable> getProperties(String tableName) throws AccumuloException, TableNotFoundException { + try { + return client.getTableProperties(token, tableName).entrySet(); + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(tableName, tnfe); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public void setLocalityGroups(String tableName, Map> groups) throws AccumuloException, AccumuloSecurityException, TableNotFoundException { + + try { + Map> tgroups = new HashMap>(); + for (Entry> entry : groups.entrySet()) { + Set tset = new HashSet(); + tgroups.put(entry.getKey(), tset); + for (Text t : entry.getValue()) { + tset.add(t.toString()); + } + } + client.setLocalityGroups(token, tableName, tgroups); + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(tableName, tnfe); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public Map> getLocalityGroups(String tableName) throws AccumuloException, TableNotFoundException { + try { + Map> tgroups = client.getLocalityGroups(token, tableName); + Map> groups = new HashMap>(); + for (Entry> tentry : tgroups.entrySet()) { + Set set = new HashSet(); + groups.put(tentry.getKey(), set); + for (String t : tentry.getValue()) { + set.add(new Text(t)); + } + } + return groups; + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(tableName, tnfe); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public Set splitRangeByTablets(String tableName, Range range, int maxSplits) throws AccumuloException, AccumuloSecurityException, + TableNotFoundException { + try { + return ThriftHelper.fromThriftRanges(client.splitRangeByTablets(token, tableName, ThriftHelper.toThrift(range), maxSplits)); + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(tableName, tnfe); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public void importDirectory(String tableName, String dir, String failureDir, boolean setTime) throws TableNotFoundException, IOException, AccumuloException, + AccumuloSecurityException { + try { + client.importDirectory(token, tableName, dir, failureDir, setTime); + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(tableName, tnfe); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public void offline(String tableName) throws AccumuloSecurityException, AccumuloException, TableNotFoundException { + offline(tableName, true); + } + + public void offline(String tableName, boolean wait) throws AccumuloSecurityException, AccumuloException, TableNotFoundException { + try { + client.offlineTable(token, tableName, wait); + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(tableName, tnfe); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public void online(String tableName) throws AccumuloSecurityException, AccumuloException, TableNotFoundException { + online(tableName, true); + } + + public void online(String tableName, boolean wait) throws AccumuloSecurityException, AccumuloException, TableNotFoundException { + try { + client.onlineTable(token, tableName, wait); + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(tableName, tnfe); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + + } + + public void clearLocatorCache(String tableName) throws TableNotFoundException { + try { + client.clearLocatorCache(token, tableName); + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(tableName, tnfe); + } catch (TException e) { + throw ExceptionFactory.runtimeException(e); + } + } + + public Map tableIdMap() { + try { + return client.tableIdMap(token); + } catch (TException e) { + throw ExceptionFactory.runtimeException(e); + } + } + + public void attachIterator(String tableName, IteratorSetting setting) throws AccumuloSecurityException, AccumuloException, TableNotFoundException { + attachIterator(tableName, setting, null); + } + + public void attachIterator(String tableName, IteratorSetting setting, EnumSet scopes) throws AccumuloSecurityException, AccumuloException, + TableNotFoundException { + try { + client.attachIterator(token, tableName, ThriftHelper.toThrift(setting), ThriftHelper.toThrift(scopes)); + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(tableName, tnfe); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public void removeIterator(String tableName, String name, EnumSet scopes) throws AccumuloSecurityException, AccumuloException, + TableNotFoundException { + try { + client.removeIterator(token, tableName, name, ThriftHelper.toThrift(scopes)); + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(tableName, tnfe); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public IteratorSetting getIteratorSetting(String tableName, String name, IteratorScope scope) throws AccumuloSecurityException, AccumuloException, + TableNotFoundException { + try { + return ThriftHelper.fromThrift(client.getIteratorSetting(token, tableName, name, ThriftHelper.toThrift(scope))); + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(tableName, tnfe); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public Map> listIterators(String tableName) throws AccumuloSecurityException, AccumuloException, TableNotFoundException { + try { + return ThriftHelper.fromThrift(client.listIterators(token, tableName)); + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(tableName, tnfe); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public void checkIteratorConflicts(String tableName, IteratorSetting setting, EnumSet scopes) throws AccumuloException, TableNotFoundException { + try { + client.checkIteratorConflicts(token, tableName, ThriftHelper.toThrift(setting), ThriftHelper.toThrift(scopes)); + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(tableName, tnfe); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public int addConstraint(String tableName, String constraintClassName) throws AccumuloException, AccumuloSecurityException, TableNotFoundException { + try { + return client.addConstraint(token, tableName, constraintClassName); + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(tableName, tnfe); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public void removeConstraint(String tableName, int constraint) throws AccumuloException, AccumuloSecurityException { + try { + client.removeConstraint(token, tableName, constraint); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public Map listConstraints(String tableName) throws AccumuloException, TableNotFoundException { + try { + return client.listConstraints(token, tableName); + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(tableName, tnfe); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public List getDiskUsage(Set tables) throws AccumuloException, AccumuloSecurityException, TableNotFoundException { + try { + return new ArrayList(ThriftHelper.fromThriftDiskUsage(client.getDiskUsage(token, tables))); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + + public boolean testClassLoad(String tableName, String className, String asTypeName) throws AccumuloException, AccumuloSecurityException, + TableNotFoundException { + try { + return client.testClassLoad(token, className, asTypeName); + } catch (org.apache.accumulo.proxy.thrift.TableNotFoundException tnfe) { + throw ExceptionFactory.tableNotFoundException(tableName, tnfe); + } catch (TException e) { + throw ExceptionFactory.accumuloException(e); + } + } + +} diff --git a/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ScannerIterator.java b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ScannerIterator.java new file mode 100644 index 0000000..d89cc23 --- /dev/null +++ b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ScannerIterator.java @@ -0,0 +1,99 @@ +/** + * Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + * + * 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 edu.jhuapl.accumulo.proxy; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.NoSuchElementException; + +import org.apache.accumulo.core.data.Key; +import org.apache.accumulo.core.data.KeyValue; +import org.apache.accumulo.core.data.Value; +import org.apache.accumulo.proxy.thrift.ScanResult; +import org.apache.thrift.TException; + +/** + * Givens a scanner id and connection to a proxy server, creates an iterator for the KV provided for the scanner id from the proxy. + */ +class ScannerIterator implements Iterator> { + + String id; + ProxyConnector connector; + int fetchSize; + + private boolean hasMore; + private Iterator results; + + ScannerIterator(String id, ProxyConnector connector, int fetchSize) { + this.id = id; + this.connector = connector; + this.fetchSize = fetchSize; + + // setup to make the class look for "more" data on 1st pass... + this.results = Collections.emptyListIterator(); + this.hasMore = true; + } + + public boolean hasNext() { + try { + while (!results.hasNext()) { + // either first pass or we are at the end of the current batch. + if (!hasMore) { + // last check told us we were done at the end of this; + // hasMore is initialized to true to ensure this does not + // fail on 1st call + return false; + } + + ScanResult sr = connector.getClient().nextK(id, fetchSize); + results = sr.getResultsIterator(); + hasMore = sr.isMore(); + + // We cannot return true from this method unless there is at + // least one result in the results list. There is a possibility + // that the results are empty now, but hasMore is still true. + // Therefore, we iterate until either hasMore is false (we are + // done!) or results.hasNext() is true + } + + // if we got here, there is something in results + return true; + } catch (TException e) { + throw ExceptionFactory.runtimeException(e); + } + } + + public Entry next() { + if (!hasNext()) { + // need to call hasNext to make sure results is setup correctly; + // might as well use the return to throw our own exception here + // instead of waiting for the call to results.next() to generate + // it... + throw new NoSuchElementException(); + } + + org.apache.accumulo.proxy.thrift.KeyValue kv = results.next(); + org.apache.accumulo.proxy.thrift.Key k = kv.getKey(); + + Key key = new Key(k.getRow(), k.getColFamily(), k.getColQualifier(), k.getColVisibility(), k.getTimestamp()); + return new KeyValue(key, kv.getValue()); + } + + public void remove() { + throw new UnsupportedOperationException(); + } +} diff --git a/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ThriftHelper.java b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ThriftHelper.java new file mode 100644 index 0000000..4372d51 --- /dev/null +++ b/proxy-instance/src/main/java/edu/jhuapl/accumulo/proxy/ThriftHelper.java @@ -0,0 +1,489 @@ +/** + * Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + * + * 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 edu.jhuapl.accumulo.proxy; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeSet; + +import org.apache.accumulo.core.client.Instance; +import org.apache.accumulo.core.client.IteratorSetting; +import org.apache.accumulo.core.client.MutationsRejectedException; +import org.apache.accumulo.core.client.admin.ActiveCompaction; +import org.apache.accumulo.core.client.admin.ActiveCompaction.CompactionReason; +import org.apache.accumulo.core.client.admin.ActiveCompaction.CompactionType; +import org.apache.accumulo.core.client.admin.ActiveScan; +import org.apache.accumulo.core.client.admin.DiskUsage; +import org.apache.accumulo.core.client.admin.ScanState; +import org.apache.accumulo.core.client.admin.ScanType; +import org.apache.accumulo.core.data.Column; +import org.apache.accumulo.core.data.ColumnUpdate; +import org.apache.accumulo.core.data.Condition; +import org.apache.accumulo.core.data.Key; +import org.apache.accumulo.core.data.KeyExtent; +import org.apache.accumulo.core.data.Range; +import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope; +import org.apache.hadoop.io.Text; + +/** + * Utility class to translate between Accumulo<->Thrift objects. + * + */ +final class ThriftHelper { + + /** + * UTF-8 charset that can be used to convert String to/from byte arrays. + */ + static final Charset UTF8 = Charset.forName("UTF-8"); + + /** + * As a utility should not be instantiated. + */ + private ThriftHelper() { + + } + + public static Map> fromThrift(Map> tmap) { + if (tmap == null) { + return null; + } + + Map> map = new HashMap>(); + + for (Entry> entry : tmap.entrySet()) { + map.put(entry.getKey(), fromThrift(entry.getValue())); + } + return map; + } + + public static Set toThrift(EnumSet scopes) { + if (scopes == null) { + return null; + } + + HashSet set = new HashSet(); + for (IteratorScope is : scopes) { + set.add(toThrift(is)); + } + return set; + } + + public static EnumSet fromThrift(Set tscopes) { + if (tscopes == null) { + return null; + } + + LinkedList list = new LinkedList(); + for (org.apache.accumulo.proxy.thrift.IteratorScope tis : tscopes) { + list.add(fromThrift(tis)); + } + + return EnumSet.copyOf(list); + } + + public static org.apache.accumulo.proxy.thrift.IteratorScope toThrift(IteratorScope is) { + // we can't just use the enum values, since thrift MINC=0 but Apache + // minc=1. we can't just use the names, since thrift are in upper case, + // and/ Apache are lower. assuming Thrift will remain uppercase and use + // the same characters as Apache + try { + return org.apache.accumulo.proxy.thrift.IteratorScope.valueOf(is.name().toUpperCase()); + } catch (IllegalArgumentException ex) { + throw ExceptionFactory.noEnumMapping(is, org.apache.accumulo.proxy.thrift.IteratorScope.class); + } + } + + public static IteratorScope fromThrift(org.apache.accumulo.proxy.thrift.IteratorScope tis) { + if (tis == null) { + return null; + } + + // we can't just use the enum values, since thrift MINC=0 but Apache + // minc=1. we can't just use the names, since thrift are in upper case, + // and Apache are lower. assuming Thrift will remain uppercase and use + // the same characters as Apache + try { + return IteratorScope.valueOf(tis.name().toLowerCase()); + } catch (IllegalArgumentException ex) { + throw ExceptionFactory.noEnumMapping(tis, IteratorScope.class); + } + } + + public static org.apache.accumulo.proxy.thrift.IteratorSetting toThrift(IteratorSetting is) { + if (is == null) { + return null; + } + + org.apache.accumulo.proxy.thrift.IteratorSetting tis = new org.apache.accumulo.proxy.thrift.IteratorSetting(); + tis.setIteratorClass(is.getIteratorClass()); + tis.setName(is.getName()); + tis.setPriority(is.getPriority()); + tis.setProperties(is.getOptions()); + return tis; + } + + public static org.apache.accumulo.proxy.thrift.Key toThrift(Key key) { + if (key == null) { + return null; + } + + org.apache.accumulo.proxy.thrift.Key tkey = new org.apache.accumulo.proxy.thrift.Key(); + tkey.setRow(key.getRow().getBytes()); + tkey.setColFamily(key.getColumnFamily().getBytes()); + tkey.setColQualifier(key.getColumnQualifier().getBytes()); + tkey.setColVisibility(key.getColumnVisibility().getBytes()); + tkey.setTimestamp(key.getTimestamp()); + return tkey; + } + + public static Key fromThrift(org.apache.accumulo.proxy.thrift.Key tkey) { + if (tkey == null) { + return null; + } + + return new Key(tkey.getRow(), tkey.getColFamily(), tkey.getColQualifier(), tkey.getColVisibility(), tkey.getTimestamp()); + } + + public static List toThriftRanges(Collection ranges) { + if (ranges == null) { + return null; + } + + List tranges = new ArrayList(ranges.size()); + for (Range range : ranges) { + tranges.add(toThrift(range)); + } + return tranges; + } + + public static org.apache.accumulo.proxy.thrift.Range toThrift(Range range) { + if (range == null) { + return null; + } + + org.apache.accumulo.proxy.thrift.Range trange = new org.apache.accumulo.proxy.thrift.Range(); + trange.setStart(toThrift(range.getStartKey())); + trange.setStop(toThrift(range.getEndKey())); + trange.setStartInclusive(range.isStartKeyInclusive()); + trange.setStopInclusive(range.isEndKeyInclusive()); + return trange; + } + + public static Set fromThriftRanges(Set tranges) { + if (tranges == null) { + return null; + } + + Set ranges = new HashSet(); + for (org.apache.accumulo.proxy.thrift.Range trange : tranges) { + ranges.add(fromThrift(trange)); + } + return ranges; + } + + public static Range fromThrift(org.apache.accumulo.proxy.thrift.Range trange) { + if (trange == null) { + return null; + } + + return new Range(fromThrift(trange.getStart()), trange.isStartInclusive(), fromThrift(trange.getStop()), trange.isStopInclusive()); + } + + /** + * Converts each non-thrift ColumnUpdate in {@code list} to a Thrift ColumnUpdate (see {@link #toThrift(ColumnUpdate)}) and adds it to the provided + * {@code thriftUpdates} list. If {@code updates} is null or empty, {@code thriftUpdates} will not be changed. + * + * @param thriftUpdates + * the list where the newly created Thrift ColumnUpdates will be added. + * @param updates + * the list of ColumnUpdates to be converted to Thrift ColumnUpdates + */ + public static void addThriftColumnUpdates(List thriftUpdates, List updates) { + if (updates == null) { + return; + } + + for (ColumnUpdate cu : updates) { + thriftUpdates.add(toThrift(cu)); + } + } + + public static org.apache.accumulo.proxy.thrift.ColumnUpdate toThrift(ColumnUpdate update) { + if (update == null) { + return null; + } + + org.apache.accumulo.proxy.thrift.ColumnUpdate tcu = new org.apache.accumulo.proxy.thrift.ColumnUpdate(); + tcu.setColFamily(update.getColumnFamily()); + tcu.setColQualifier(update.getColumnQualifier()); + tcu.setColVisibility(update.getColumnVisibility()); + tcu.setTimestamp(update.getTimestamp()); + tcu.setValue(update.getValue()); + // Work around for ACCUMULO-3474 + if (update.isDeleted()) { + tcu.setDeleteCell(update.isDeleted()); + } + return tcu; + } + + public static List fromThriftActiveScans(List tscans) { + if (tscans == null) { + return null; + } + + List scans = new ArrayList(tscans.size()); + for (org.apache.accumulo.proxy.thrift.ActiveScan tscan : tscans) { + scans.add(fromThrift(tscan)); + } + return scans; + } + + public static ActiveScan fromThrift(org.apache.accumulo.proxy.thrift.ActiveScan scan) { + if (scan == null) { + return null; + } + + // Note- scanId is not provided by the server and does not appear to be + // set/used in actual implementation... we will just set it to 0. + return new ProxyActiveScan(0, scan.getClient(), scan.getUser(), scan.getTable(), scan.getAge(), System.currentTimeMillis() - scan.getIdleTime(), + convertEnum(scan.getType(), ScanType.class), convertEnum(scan.getState(), ScanState.class), fromThrift(scan.getExtent()), + fromThriftColumns(scan.getColumns()), scan.getAuthorizations(), scan.getIdleTime()); + + } + + public static KeyExtent fromThrift(org.apache.accumulo.proxy.thrift.KeyExtent textent) { + if (textent == null) { + return null; + } + + return new KeyExtent(toText(textent.getTableId()), toText(textent.getEndRow()), toText(textent.getPrevEndRow())); + } + + public static List fromThriftColumns(List tcolumns) { + if (tcolumns == null) { + return null; + } + + List columns = new ArrayList(tcolumns.size()); + for (org.apache.accumulo.proxy.thrift.Column tcolumn : tcolumns) { + columns.add(fromThrift(tcolumn)); + } + return columns; + } + + public static Column fromThrift(org.apache.accumulo.proxy.thrift.Column tcolumn) { + if (tcolumn == null) { + return null; + } + + return new Column(tcolumn.getColFamily(), tcolumn.getColQualifier(), tcolumn.getColVisibility()); + } + + public static List fromThriftActiveCompactions(Map tableIdMap, + List tcompactions) { + if (tcompactions == null) { + return null; + } + + List compactions = new ArrayList(tcompactions.size()); + for (org.apache.accumulo.proxy.thrift.ActiveCompaction tcompaction : tcompactions) { + compactions.add(fromThrift(getTableNameFromId(tableIdMap, tcompaction.getExtent().getTableId()), tcompaction)); + } + return compactions; + } + + public static ActiveCompaction fromThrift(String tableName, org.apache.accumulo.proxy.thrift.ActiveCompaction tcompaction) { + if (tcompaction == null) { + return null; + } + + return new ProxyActiveCompaction(tableName, fromThrift(tcompaction.getExtent()), tcompaction.getAge(), tcompaction.getInputFiles(), + tcompaction.getOutputFile(), convertEnum(tcompaction.getType(), CompactionType.class), convertEnum(tcompaction.getReason(), CompactionReason.class), + tcompaction.getLocalityGroup(), tcompaction.getEntriesRead(), tcompaction.getEntriesWritten(), fromThriftIteratorSettings(tcompaction.getIterators())); + } + + public static String getTableNameFromId(Map tableIdMap, String id) { + if (id == null) { + return null; + } + + for (Entry entry : tableIdMap.entrySet()) { + if (id.equalsIgnoreCase(entry.getKey())) { + return entry.getValue(); + } + } + return null; + } + + public static List fromThriftIteratorSettings(List tsettings) { + if (tsettings == null) { + return null; + } + + List settings = new ArrayList(tsettings.size()); + for (org.apache.accumulo.proxy.thrift.IteratorSetting tsetting : tsettings) { + settings.add(fromThrift(tsetting)); + } + return settings; + } + + public static IteratorSetting fromThrift(org.apache.accumulo.proxy.thrift.IteratorSetting tsetting) { + if (tsetting == null) { + return null; + } + + return new IteratorSetting(tsetting.getPriority(), tsetting.getName(), tsetting.getIteratorClass(), tsetting.getProperties()); + } + + public static List toThriftIteratorSettings(List settings) { + if (settings == null) { + return null; + } + + List tsettings = new ArrayList(settings.size()); + for (IteratorSetting setting : settings) { + tsettings.add(toThrift(setting)); + } + return tsettings; + } + + public static List fromThriftDiskUsage(List tusage) { + if (tusage == null) { + return null; + } + + List usage = new ArrayList(tusage.size()); + for (org.apache.accumulo.proxy.thrift.DiskUsage tdu : tusage) { + usage.add(fromThrift(tdu)); + } + return usage; + } + + public static DiskUsage fromThrift(org.apache.accumulo.proxy.thrift.DiskUsage tusage) { + if (tusage == null) { + return null; + } + + return new DiskUsage(new TreeSet(tusage.getTables()), tusage.getUsage()); + } + + public static List toThriftCondition(List conditions) { + if (conditions == null) { + return null; + } + + List tconditions = new ArrayList(conditions.size()); + for (Condition c : conditions) { + tconditions.add(toThrift(c)); + } + return tconditions; + } + + public static org.apache.accumulo.proxy.thrift.Condition toThrift(Condition condition) { + if (condition == null) { + return null; + } + + org.apache.accumulo.proxy.thrift.Condition tcondition = new org.apache.accumulo.proxy.thrift.Condition(); + + org.apache.accumulo.proxy.thrift.Column col = new org.apache.accumulo.proxy.thrift.Column(); + if (condition.getFamily() != null) { + col.setColFamily(condition.getFamily().getBackingArray()); + } + if (condition.getQualifier() != null) { + col.setColQualifier(condition.getQualifier().getBackingArray()); + } + + if (col.getColVisibility() != null) { + col.setColVisibility(condition.getQualifier().getBackingArray()); + } + tcondition.setColumn(col); + + if (condition.getTimestamp() != null) { + tcondition.setTimestamp(condition.getTimestamp()); + } + + if (condition.getValue() != null) { + tcondition.setValue(condition.getValue().getBackingArray()); + } + tcondition.setIterators(toThriftIteratorSettings(Arrays.asList(condition.getIterators()))); + return tcondition; + } + + public static MutationsRejectedException fromThrift(org.apache.accumulo.proxy.thrift.MutationsRejectedException mre, Instance instance) { + return new MutationsRejectedException(instance, null, null, Collections.singleton(mre.getMsg()), 0, mre); + } + + /** + * Converts the enumeration of class F to the enumeration of class S assuming both F and S define the same values. This is used in mapping between Accumulo + * client-side enumerations and their equivalent Thrift-based enumerations. + * + * @param first + * the enumeration to convert + * @param cls + * the new class of the enumeration + * @return an enumeration of type cls with the same name as first. + * @exception ProxyInstanceError + * thrown if the text of first is not a valid enumerated value in the class cls. + */ + public static ,S extends Enum> S convertEnum(F first, Class cls) throws ProxyInstanceError { + if (first == null) { + return null; + } + + try { + return Enum.valueOf(cls, first.toString()); + } catch (IllegalArgumentException e) { + throw ExceptionFactory.noEnumMapping(first, cls); + } + } + + public static Text toText(String val) { + if (val == null) { + return null; + } else { + return new Text(val.getBytes(UTF8)); + } + } + + public static Text toText(byte[] val) { + if (val == null) { + return null; + } else { + return new Text(val); + } + } + + public static ByteBuffer toByteBuffer(Text val) { + if (val == null) { + return null; + } else { + return ByteBuffer.wrap(val.getBytes()); + } + } +} diff --git a/proxy-instance/src/main/resources/Eclipse-Codestyle.xml b/proxy-instance/src/main/resources/Eclipse-Codestyle.xml new file mode 100644 index 0000000..50cf49f --- /dev/null +++ b/proxy-instance/src/main/resources/Eclipse-Codestyle.xml @@ -0,0 +1,292 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/proxy-instance/src/main/resources/license.txt b/proxy-instance/src/main/resources/license.txt new file mode 100644 index 0000000..4c4a47d --- /dev/null +++ b/proxy-instance/src/main/resources/license.txt @@ -0,0 +1,13 @@ +Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + +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/proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/ConnectorBase.java b/proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/ConnectorBase.java new file mode 100644 index 0000000..a74b0f5 --- /dev/null +++ b/proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/ConnectorBase.java @@ -0,0 +1,116 @@ +/** + * Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + * + * 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 edu.jhuapl.accumulo.proxy; + +import org.apache.accumulo.core.client.AccumuloException; +import org.apache.accumulo.core.client.AccumuloSecurityException; +import org.apache.accumulo.core.client.Connector; +import org.apache.accumulo.core.client.security.tokens.PasswordToken; +import org.apache.accumulo.proxy.Proxy; +import org.apache.thrift.transport.TTransportException; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; + +/** + * Base class for unit and integration tests that want to get access to an Instance and/or Connector. This class provides the means to setup a ProxyInstace + * connection to a local ProxyServer backed by a mock (in-memory) Accumulo instance for unit tests, or to create a ProxyInstace connection to a real (external) + * ProxyServer for integration tests. + */ +public class ConnectorBase { + + protected static volatile ProxyInstance instance = null; + protected static volatile Connector connector = null; + private static Thread proxyServerThread = null; + + /** + * Initializes the instance and connector variables (and proxyServerThread, if needed) if they are currently null. This will be done once per class, but we + * cannot do this with a @BeforeClass because we want to use the type of the subclass to determine if it is a unit or integration test. + */ + @Before + public void prepare() throws TTransportException, AccumuloException, AccumuloSecurityException, InterruptedException { + if (instance == null) { + synchronized (ConnectorBase.class) { + if (instance == null) { + ProxyInstance locInst = null; + Connector locConn = null; + if (IntegrationTest.class.isAssignableFrom(getClass())) { + String host = getString("accumulo.proxy.host"); + int port = getInt("accumulo.proxy.port"); + String user = getString("accumulo.proxy.user"); + String passwd = getString("accumulo.proxy.password"); + locInst = new ProxyInstance(host, port); + locConn = locInst.getConnector(user, new PasswordToken(passwd)); + } else { + createLocalServer(); + locInst = new ProxyInstance("localhost", 45678); + locConn = locInst.getConnector("root", new PasswordToken("")); + } + instance = locInst; + connector = locConn; + } + } + } + } + + @AfterClass + public static void teardown() { + try { + if (instance != null) { + instance.close(); + } + } finally { + instance = null; + } + + try { + if (proxyServerThread != null) { + proxyServerThread.interrupt(); + } + } finally { + proxyServerThread = null; + } + } + + private static String getString(String key) { + String val = System.getProperty(key); + if (val == null) { + Assert.fail("You must specify the system property: " + key); + } + return val.trim(); + } + + private static int getInt(String key) throws NumberFormatException { + return Integer.parseInt(getString(key)); + } + + private static void createLocalServer() throws InterruptedException { + proxyServerThread = new Thread() { + public void run() { + try { + String[] args = new String[] {"-p", "src/test/resources/mock.props"}; + Proxy.main(args); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + }; + proxyServerThread.setDaemon(true); + proxyServerThread.start(); + Thread.sleep(150); + } + +} diff --git a/proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/IntegrationTest.java b/proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/IntegrationTest.java new file mode 100644 index 0000000..67b8b1e --- /dev/null +++ b/proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/IntegrationTest.java @@ -0,0 +1,24 @@ +/** + * Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + * + * 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 edu.jhuapl.accumulo.proxy; + +/** + * A flag interface that signals a test is an integration test (vs. a unit test). When used in conjunction with the {@link ConnectorBase} class, the appropriate + * Instance type will be instantiated. + */ +public interface IntegrationTest { + +} diff --git a/proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/ScannerTest.java b/proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/ScannerTest.java new file mode 100644 index 0000000..7255354 --- /dev/null +++ b/proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/ScannerTest.java @@ -0,0 +1,140 @@ +/** + * Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + * + * 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 edu.jhuapl.accumulo.proxy; + +import java.nio.charset.StandardCharsets; +import java.util.Map.Entry; + +import org.apache.accumulo.core.client.AccumuloException; +import org.apache.accumulo.core.client.AccumuloSecurityException; +import org.apache.accumulo.core.client.BatchWriter; +import org.apache.accumulo.core.client.BatchWriterConfig; +import org.apache.accumulo.core.client.Scanner; +import org.apache.accumulo.core.client.TableExistsException; +import org.apache.accumulo.core.client.TableNotFoundException; +import org.apache.accumulo.core.data.Key; +import org.apache.accumulo.core.data.Mutation; +import org.apache.accumulo.core.data.Range; +import org.apache.accumulo.core.data.Value; +import org.apache.accumulo.core.security.Authorizations; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class ScannerTest extends ConnectorBase { + + String table = "__TEST_TABLE__"; + + int noAuthCount; + + @Before + public void createTable() throws AccumuloException, AccumuloSecurityException, TableExistsException, TableNotFoundException { + connector.tableOperations().create(table); + BatchWriter bw = connector.createBatchWriter(table, new BatchWriterConfig()); + for (char ch = 'a'; ch <= 'z'; ch++) { + Mutation m = new Mutation(ch + "_row"); + + m.put("fam1", "qual1", "val1:1"); + m.put("fam1", "qual2", "val1:2"); + m.put("fam2", "qual1", "val2:1"); + m.put("fam2", "qual2", "val2:2"); + m.put("fam2", "qual3", "val2:3"); + noAuthCount = m.getUpdates().size(); + bw.addMutation(m); + } + bw.close(); + } + + @After + public void dropTable() throws AccumuloException, AccumuloSecurityException, TableNotFoundException { + connector.tableOperations().delete(table); + } + + @Test + public void simpleTest() throws TableNotFoundException { + Scanner s = connector.createScanner(table, Authorizations.EMPTY); + validate(noAuthCount * 26, s); + } + + @Test + public void testRanges() throws TableNotFoundException { + // first row + Scanner scanner = connector.createScanner(table, Authorizations.EMPTY); + testRange(new Range("a_row"), noAuthCount, scanner); + + // last row + scanner = connector.createScanner(table, Authorizations.EMPTY); + testRange(new Range("z_row"), noAuthCount, scanner); + + // "random" inner row + scanner = connector.createScanner(table, Authorizations.EMPTY); + testRange(new Range("q_row"), noAuthCount, scanner); + + // some actual ranges + scanner = connector.createScanner(table, Authorizations.EMPTY); + testRange(new Range("d_row", true, "h_row", true), 5 * noAuthCount, scanner); + + scanner = connector.createScanner(table, Authorizations.EMPTY); + testRange(new Range("d_row", false, "h_row", true), 4 * noAuthCount, scanner); + + scanner = connector.createScanner(table, Authorizations.EMPTY); + testRange(new Range("d_row", true, "h_row", false), 4 * noAuthCount, scanner); + + scanner = connector.createScanner(table, Authorizations.EMPTY); + testRange(new Range("d_row", false, "h_row", false), 3 * noAuthCount, scanner); + + // no start + scanner = connector.createScanner(table, Authorizations.EMPTY); + testRange(new Range(null, "j_row"), 10 * noAuthCount, scanner); + + // no end + scanner = connector.createScanner(table, Authorizations.EMPTY); + testRange(new Range("j_row", null), 17 * noAuthCount, scanner); + + } + + private void testRange(Range range, int expected, Scanner scanner) { + scanner.setRange(range); + Assert.assertEquals(range, scanner.getRange()); + validate(expected, scanner); + } + + private void validate(int expected, Scanner scanner) { + int count = 0; + for (Entry entry : scanner) { + validate(entry); + count++; + } + Assert.assertEquals(expected, count); + scanner.close(); + } + + /** + * expects ("famX", "qualY") -> "valX:Y" + * + * @param entry + * entry to validate + */ + private void validate(Entry entry) { + String fam = entry.getKey().getColumnFamily().toString().substring(3); + String qual = entry.getKey().getColumnQualifier().toString().substring(4); + String val = new String(entry.getValue().get(), StandardCharsets.UTF_8).substring(3); + String[] parts = val.split(":"); + Assert.assertEquals("Family and value did not line up.", fam, parts[0]); + Assert.assertEquals("Qualifier and value did not line up.", qual, parts[1]); + } +} diff --git a/proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/ScannerTestIT.java b/proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/ScannerTestIT.java new file mode 100644 index 0000000..76451f8 --- /dev/null +++ b/proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/ScannerTestIT.java @@ -0,0 +1,18 @@ +/** + * Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + * + * 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 edu.jhuapl.accumulo.proxy; + +public class ScannerTestIT extends ScannerTest implements IntegrationTest {} diff --git a/proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/TableOpsTest.java b/proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/TableOpsTest.java new file mode 100644 index 0000000..faa1a0b --- /dev/null +++ b/proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/TableOpsTest.java @@ -0,0 +1,78 @@ +/** + * Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + * + * 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 edu.jhuapl.accumulo.proxy; + +import org.apache.accumulo.core.client.AccumuloException; +import org.apache.accumulo.core.client.AccumuloSecurityException; +import org.apache.accumulo.core.client.TableExistsException; +import org.apache.accumulo.core.client.TableNotFoundException; +import org.apache.accumulo.core.client.admin.TableOperations; +import org.junit.Assert; +import org.junit.Test; + +public class TableOpsTest extends ConnectorBase { + + String table = "__TEST_TABLE__"; + + @Test + public void testTableOps() throws AccumuloException, AccumuloSecurityException, TableExistsException, TableNotFoundException { + TableOperations tops = connector.tableOperations(); + + Assert.assertFalse(tops.exists(table)); + tops.create(table); + Assert.assertTrue(tops.exists(table)); + tops.delete(table); + Assert.assertFalse(tops.exists(table)); + } + + @Test + public void testCreateExistingTable() throws AccumuloException, AccumuloSecurityException, TableNotFoundException, TableExistsException { + TableOperations tops = connector.tableOperations(); + try { + Assert.assertFalse(tops.exists(table)); + tops.create(table); + Assert.assertTrue(tops.exists(table)); + + try { + tops.create(table); + Assert.fail("Expected second table create to fail."); + } catch (TableExistsException tee) { + // expected + Assert.assertTrue(true); + } + } finally { + if (tops.exists(table)) { + tops.delete(table); + } + Assert.assertFalse(tops.exists(table)); + } + } + + @Test + public void testDeleteNonExistentTable() throws AccumuloException, AccumuloSecurityException { + TableOperations tops = connector.tableOperations(); + + Assert.assertFalse(tops.exists(table)); + try { + tops.delete(table); + Assert.fail("Expected second table create to fail."); + } catch (TableNotFoundException tnfe) { + // expected + Assert.assertTrue(true); + } + } + +} diff --git a/proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/TableOpsTestIT.java b/proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/TableOpsTestIT.java new file mode 100644 index 0000000..30e4488 --- /dev/null +++ b/proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/TableOpsTestIT.java @@ -0,0 +1,18 @@ +/** + * Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + * + * 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 edu.jhuapl.accumulo.proxy; + +public class TableOpsTestIT extends TableOpsTest implements IntegrationTest {} diff --git a/proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/ThiftHelperTest.java b/proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/ThiftHelperTest.java new file mode 100644 index 0000000..83cac7c --- /dev/null +++ b/proxy-instance/src/test/java/edu/jhuapl/accumulo/proxy/ThiftHelperTest.java @@ -0,0 +1,129 @@ +/** + * Copyright 2014-2015 The Johns Hopkins University / Applied Physics Laboratory + * + * 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 edu.jhuapl.accumulo.proxy; + +import java.nio.ByteBuffer; + +import org.apache.accumulo.core.data.Key; +import org.apache.accumulo.core.data.Range; +import org.apache.hadoop.io.Text; +import org.junit.Assert; +import org.junit.Test; + +public class ThiftHelperTest { + + /** + * Used for enum mapping testing + */ + public enum FirstEnum { + val1, val2 + }; + + /** + * Used for enum mapping testing + */ + public enum SecondEnum { + val1 + }; + + @Test + public void testToText() { + String test = "test"; + + Text txt = ThriftHelper.toText(test); + Assert.assertEquals(test, txt.toString()); + + txt = ThriftHelper.toText(test.getBytes()); + Assert.assertEquals(test, txt.toString()); + + txt = ThriftHelper.toText((byte[]) null); + Assert.assertNull(txt); + } + + @Test + public void testEnum() { + SecondEnum se = ThriftHelper.convertEnum(null, SecondEnum.class); + Assert.assertNull(se); + + se = ThriftHelper.convertEnum(FirstEnum.val1, SecondEnum.class); + Assert.assertEquals(SecondEnum.val1, se); + } + + @Test(expected = ProxyInstanceError.class) + public void testEnumBadMapping() { + ThriftHelper.convertEnum(FirstEnum.val2, SecondEnum.class); + } + + @Test + public void testToByteBuffer() { + ByteBuffer buf = ThriftHelper.toByteBuffer(null); + Assert.assertNull(buf); + + Text text = new Text("test test"); + buf = ThriftHelper.toByteBuffer(text); + Assert.assertArrayEquals(text.getBytes(), buf.array()); + } + + @Test + public void testRanges() { + testConvertRange(new Range("a", "b")); + testConvertRange(new Range(null, "b")); + testConvertRange(new Range("a", null)); + testConvertRange(null); + testConvertRange(new Range("a", true, "b", false)); + testConvertRange(new Range("a", false, "b", false)); + testConvertRange(new Range("a", false, "b", true)); + } + + private void testConvertRange(Range range) { + org.apache.accumulo.proxy.thrift.Range r2 = ThriftHelper.toThrift(range); + checkRanges(range, r2); + + range = ThriftHelper.fromThrift(r2); + checkRanges(range, r2); + } + + private void checkRanges(Range r1, org.apache.accumulo.proxy.thrift.Range r2) { + if (r1 == null) { + Assert.assertNull("t1 was null, but r2 was not!", r2); + return; + } else if (r2 == null) { + Assert.fail("r1 was not null, but r2 was!"); + return; + } + + checkKeys(r1.getStartKey(), r2.getStart()); + checkKeys(r1.getEndKey(), r2.getStop()); + Assert.assertEquals(r1.isStartKeyInclusive(), r2.isStartInclusive()); + Assert.assertEquals(r1.isEndKeyInclusive(), r2.isStopInclusive()); + } + + private void checkKeys(Key k, org.apache.accumulo.proxy.thrift.Key k2) { + if (k == null) { + Assert.assertNull("k was null, but k2 was not!", k2); + return; + } else if (k2 == null) { + Assert.fail("k was not null, but k2 was!"); + return; + } + Assert.assertArrayEquals(k.getRow().getBytes(), k2.getRow()); + Assert.assertArrayEquals(k.getColumnFamily().getBytes(), k2.getColFamily()); + Assert.assertArrayEquals(k.getColumnQualifier().getBytes(), k2.getColQualifier()); + Assert.assertArrayEquals(k.getColumnVisibility().getBytes(), k2.getColVisibility()); + Assert.assertEquals(k.getTimestamp(), k2.getTimestamp()); + } + +} diff --git a/proxy-instance/src/test/resources/mock.props b/proxy-instance/src/test/resources/mock.props new file mode 100644 index 0000000..b0822f8 --- /dev/null +++ b/proxy-instance/src/test/resources/mock.props @@ -0,0 +1,3 @@ +useMockInstance=true +port=45678 +

2?hCR*_lW1IF7E5o{)_}! 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 z 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 literal 0 HcmV?d00001 diff --git a/proxy-instance-docs/src/main/asciidoc/proxy_instance_user_manual.asciidoc b/proxy-instance-docs/src/main/asciidoc/proxy_instance_user_manual.asciidoc new file mode 100644 index 0000000..a559bf7 --- /dev/null +++ b/proxy-instance-docs/src/main/asciidoc/proxy_instance_user_manual.asciidoc @@ -0,0 +1,21 @@ += Accumulo Proxy Instance User Manual +:author: JHUAPL +:toc2: +:toclevels: 2 +:toc-title: Accumulo Proxy Instance +:numbered: +:revnumber: {docVersion} + +include::chapters/introduction.txt[] + +include::chapters/building.txt[] + +include::chapters/usage.txt[] + +include::chapters/development.txt[] + +include::chapters/known_issues.txt[] + +image::apl_horizontal_blue.png["JHU/APL",width="450",align="center"] + +include::chapters/copyright.txt[] diff --git a/proxy-instance/pom.xml b/proxy-instance/pom.xml new file mode 100644 index 0000000..741a0c8 --- /dev/null +++ b/proxy-instance/pom.xml @@ -0,0 +1,95 @@ + + + 4.0.0 + + edu.jhuapl.accumulo + proxy-instance-project + 1.0.0-SNAPSHOT + + proxy-instance + proxy-instance + Implements Accumulo Java API through the Accumulo Thrift-based Proxy server. + + 1.7 + 1.7 + + + + org.apache.accumulo + accumulo-core + ${accumulo.version} + + + org.apache.accumulo + accumulo-proxy + ${accumulo.version} + + + junit + junit + test + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + maven-assembly-plugin + + + + stec.va.core.Main + + + + jar-with-dependencies + + + + + com.googlecode.maven-java-formatter-plugin + maven-java-formatter-plugin + 0.4 + + ${project.basedir}/src/main/resources/Eclipse-Codestyle.xml + LF + + + + + format + + + + + + com.mycila + license-maven-plugin + 2.11 + +