├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── DEVELOPING.md ├── LICENSE ├── README.md ├── checkstyle ├── custom-checks.xml └── java.header ├── pom.xml ├── scripts ├── build.sh ├── ci │ ├── build.sh │ ├── gcloud-init.sh │ ├── release-cloudbuild.yaml │ └── release.sh ├── integration_test.sh └── utils │ ├── gcloud.sh │ ├── maven.sh │ └── structure_test.sh ├── tests ├── test-distributed-war │ ├── .gitignore │ ├── pom.xml │ └── src │ │ └── main │ │ ├── appengine │ │ └── app.yaml │ │ ├── docker │ │ ├── Dockerfile │ │ └── credential.json │ │ ├── java │ │ └── com │ │ │ └── google │ │ │ └── cloud │ │ │ └── runtimes │ │ │ └── tomcat │ │ │ └── test │ │ │ └── distributed │ │ │ └── SessionServlet.java │ │ └── webapp │ │ └── WEB-INF │ │ └── web.xml ├── test-war-integration │ ├── pom.xml │ └── src │ │ └── main │ │ ├── cloudbuild │ │ └── integration.yaml │ │ ├── docker │ │ ├── Dockerfile │ │ ├── Dockerfile.in │ │ └── app.yaml │ │ ├── java │ │ └── com │ │ │ └── google │ │ │ └── cloud │ │ │ └── runtimes │ │ │ └── tomcat │ │ │ └── test │ │ │ └── integration │ │ │ ├── CustomLoggingServlet.java │ │ │ ├── CustomServlet.java │ │ │ ├── DumpServlet.java │ │ │ ├── ExceptionServlet.java │ │ │ ├── HelloServlet.java │ │ │ ├── MonitoringServlet.java │ │ │ ├── SecureTestServlet.java │ │ │ └── StandardLoggingServlet.java │ │ ├── resources │ │ ├── custom-test-specification.json │ │ └── logging.properties │ │ └── webapp │ │ └── WEB-INF │ │ └── web.xml ├── test-war-simple │ ├── pom.xml │ └── src │ │ └── main │ │ ├── docker │ │ └── Dockerfile │ │ ├── java │ │ └── com │ │ │ └── google │ │ │ └── cloud │ │ │ └── runtimes │ │ │ └── tomcat │ │ │ └── test │ │ │ └── integration │ │ │ └── HelloServlet.java │ │ └── webapp │ │ ├── WEB-INF │ │ └── web.xml │ │ └── index.html └── test-war-spring-boot │ ├── pom.xml │ └── src │ └── main │ ├── docker │ └── Dockerfile │ └── java │ └── com.google.cloud.runtimes │ └── spring │ ├── Application.java │ └── controllers │ └── HelloController.java ├── tomcat-gcp-lib ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── google │ │ └── cloud │ │ └── runtimes │ │ └── tomcat │ │ ├── session │ │ ├── DatastoreManager.java │ │ ├── DatastoreSession.java │ │ ├── DatastoreStore.java │ │ └── DatastoreValve.java │ │ └── trace │ │ ├── HttpLabels.java │ │ └── TraceValve.java │ └── test │ └── java │ └── com │ └── google │ └── cloud │ └── runtimes │ └── tomcat │ ├── session │ ├── DatastoreManagerTest.java │ ├── DatastoreSessionTest.java │ ├── DatastoreStoreTest.java │ ├── DatastoreValveTest.java │ └── integration │ │ └── DatastoreStoreIT.java │ └── trace │ └── TraceValveTest.java └── tomcat ├── pom.xml └── src ├── cloudbuild └── build.yaml ├── main ├── docker │ ├── 15-debug-env-tomcat.bash │ ├── 50-tomcat.bash │ └── Dockerfile ├── resources │ └── config │ │ ├── distributed-sessions.xml │ │ ├── gcp.xml │ │ └── stackdriver-trace.xml └── tomcat-base │ └── conf │ ├── catalina.properties │ ├── context.xml │ ├── distributed-sessions.xml │ ├── gcp.xml │ ├── logging.properties │ ├── server.xml │ └── stackdriver-trace.xml └── test ├── resources └── structure.yaml └── workspace ├── tomcat-gae.bash └── tomcat-unpack.bash /.gitignore: -------------------------------------------------------------------------------- 1 | # maven 2 | target 3 | 4 | # intellij / idea 5 | *.iml 6 | .idea 7 | 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | languages: java 4 | 5 | services: 6 | - docker 7 | 8 | jdk: 9 | - oraclejdk8 10 | 11 | branches: 12 | only: 13 | - master 14 | 15 | script: 16 | - mvn clean verify -B -V -q -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to become a contributor and submit your own code 2 | 3 | ## Contributor License Agreements 4 | 5 | We'd love to accept your patches! Before we can take them, we 6 | have to jump a couple of legal hurdles. 7 | 8 | Please fill out either the individual or corporate Contributor License Agreement 9 | (CLA). 10 | 11 | * If you are an individual writing original source code and you're sure you 12 | own the intellectual property, then you'll need to sign an [individual CLA](https://developers.google.com/open-source/cla/individual). 13 | * If you work for a company that wants to allow you to contribute your work, 14 | then you'll need to sign a [corporate CLA](https://developers.google.com/open-source/cla/corporate). 15 | 16 | Follow either of the two links above to access the appropriate CLA and 17 | instructions for how to sign and return it. Once we receive it, we'll be able to 18 | accept your pull requests. 19 | 20 | ## Contributing A Patch 21 | 22 | 1. Submit an issue describing your proposed change to the repo in question. 23 | 1. The repo owner will respond to your issue promptly. 24 | 1. If your proposed change is accepted, and you haven't already done so, sign a 25 | Contributor License Agreement (see details above). 26 | 1. Fork the desired repo, develop and test your code changes. 27 | 1. Ensure that your code adheres to the existing style in the sample to which 28 | you are contributing. Refer to the 29 | [Google Cloud Platform Samples Style Guide](https://github.com/GoogleCloudPlatform/Template/wiki/style.html) for the 30 | recommended coding standards for this organization. 31 | 1. Ensure that your code has an appropriate set of unit tests which all pass. 32 | 1. Submit a pull request. -------------------------------------------------------------------------------- /DEVELOPING.md: -------------------------------------------------------------------------------- 1 | # Development Guide 2 | 3 | ## Building the image 4 | 5 | Make sure you have Docker and Maven installed and that the Docker daemon is running. 6 | Then run the following command: 7 | 8 | ```bash 9 | mvn clean install 10 | ``` 11 | 12 | This will add the Tomcat runtime image to your local docker repository. You can now use the 13 | newly created image as a base in your Dockerfile. 14 | 15 | ## Testing the image 16 | 17 | ### JUnit Units tests 18 | 19 | You can quickly tests the project locally using the JUnit tests, to do so run: 20 | ```bash 21 | mvn clean test 22 | ``` 23 | 24 | ### JUnit GCP Integration tests 25 | 26 | This tests the interaction of the different components with the GCP Services. 27 | The tests are run outside of a Docker container. 28 | 29 | In order to run the tests you need to install the [Cloud SDK](https://cloud.google.com/sdk/docs/) 30 | and select an active project with `gcloud init`. 31 | 32 | You can run these tests with maven using the profile `gcp-integration-test`, for example: 33 | ```bash 34 | mvn clean verify -P gcp-integration-test 35 | ``` 36 | 37 | ### Runtimes common Structure tests 38 | 39 | Specification: [Runtime common - Structure tests](https://github.com/GoogleCloudPlatform/runtimes-common/tree/master/structure_tests) 40 | 41 | These tests inspect the content of the Docker image to ensure the presence of specific files and environment variables. 42 | 43 | They are automatically run when the image is built with Maven. 44 | You can find the details of the tests in [structure.yaml](tomcat/src/test/resources/structure.yaml) 45 | 46 | ### Runtimes common Integration tests 47 | 48 | Specification: [Runtimes common - Integration tests](https://github.com/GoogleCloudPlatform/runtimes-common/tree/master/integration_tests) 49 | 50 | The integration tests will deploy a sample application to App Engine Flex and run remote tests against this application to ensure 51 | that the standard requirements of the [language runtime image](https://github.com/GoogleCloudPlatform/runtimes-common/tree/master/integration_tests#tests) are respected. 52 | 53 | Before running these tests ensure that: 54 | * [Maven](https://maven.apache.org/download.cgi) is installed 55 | * [Google Cloud SDK](https://cloud.google.com/sdk) is installed 56 | 57 | When running those tests you need to indicate which image needs to be tested. 58 | As the tests will be executed remotely, the image needs to be pushed to a gcr.io repository. 59 | 60 | A script is available to run those tests: 61 | ```bash 62 | RUNTIME_IMAGE=gcr.io/my-project-id/tomcat:tag 63 | gcloud docker -- push $RUNTIME_IMAGE 64 | ./scripts/integration_test.sh $RUNTIME_IMAGE 65 | ``` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Google Cloud Platform Tomcat Runtime Image 2 | 3 | [![experimental](http://badges.github.io/stability-badges/dist/experimental.svg)](http://github.com/badges/stability-badges) 4 | 5 | **This Project is Archived** 6 | 7 | This repository contains the source for the Google-maintained Tomcat [docker](https://docker.com) image. 8 | 9 | ## Using the Tomcat image 10 | 11 | 12 | 13 | ### Running on App Engine Flexible 14 | 15 | #### Cloud SDK 16 | 17 | Simply choose the Java runtime with Tomcat as the server preference in `app.yaml`. 18 | 19 | ```yaml 20 | runtime: java 21 | env: flex 22 | runtime_config: 23 | server: tomcat8 24 | ``` 25 | 26 | Then you can deploy using the `gcloud beta app deploy` [command](https://cloud.google.com/sdk/gcloud/reference/beta/app/deploy). Note that the "beta" command is currently required. 27 | 28 | #### Using custom Dockerfile 29 | 30 | Specify a custom runtime in `app.yaml`. 31 | 32 | ```yaml 33 | runtime: custom 34 | env: flex 35 | ``` 36 | 37 | Then create a `Dockerfile` that uses the Tomcat runtime image as specified in [Other platforms](#other-platforms). 38 | 39 | ### Other platforms 40 | 41 | Create a `Dockerfile` based on the image and add your application WAR: 42 | 43 | ```dockerfile 44 | FROM gcr.io/google-appengine/tomcat 45 | COPY your-application.war $APP_DESTINATION_WAR 46 | ``` 47 | 48 | You can also use an exploded-war: 49 | 50 | ```dockerfile 51 | COPY your-application $APP_DESTINATION_EXPLODED_WAR 52 | ``` 53 | 54 | ## Configuring Tomcat 55 | 56 | ### Tomcat properties 57 | The Tomcat instance can be configured through the environment variable `TOMCAT_PROPERTIES` which is 58 | a comma-separated list of `name=value` pairs appended to `catalina.properties`. 59 | 60 | ### Security best practices 61 | 62 | #### Execute tomcat with a non-root user 63 | For security purposes it is recommended to start the Tomcat instance using the `tomcat` user. 64 | 65 | You can do so by adding `USER tomcat` at the end of your Dockerfile. 66 | 67 | ```dockerfile 68 | FROM gcr.io/google-appengine/tomcat 69 | COPY your-application.war $APP_DESTINATION_WAR 70 | 71 | RUN chown tomcat:tomcat $APP_DESTINATION_WAR 72 | USER tomcat 73 | ``` 74 | 75 | If you are using an exploded-war, then use the `$APP_DESTINATION_EXPLODED_WAR` environment variable instead. 76 | 77 | ## Optional Features 78 | ### Distributed sessions 79 | This image can be configured to store Tomcat sessions in the [Google Cloud Datastore](https://cloud.google.com/datastore/docs) which allows 80 | multiple instances of Tomcat to share sessions. 81 | 82 | You can enable this feature by adding `distributed-sessions` to the list of optional modules, which is specified in the `TOMCAT_MODULES_ENABLE` environment variable. 83 | 84 | The distributed sessions module can be configured through the environment variable `TOMCAT_PROPERTIES`. 85 | 86 | | Property | Description | Default | 87 | |---|---|---| 88 | | gcp.distributed-sessions.namespace | Namespace to use in the Datastore. | tomcat-gcp-persistent-session | 89 | | gcp.distributed-sessions.sessionKind | Name of the entity used to store sessions in the Datastore. | TomcatGCloudSession | 90 | | gcp.distributed-sessions.uriExcludePattern | [Pattern](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html) specifying which URI to ignore when persisting sessions. | null | 91 | | gcp.distributed-sessions.enableTrace | Register the operations of the module in Stackdriver Trace. (The [Trace module](#stackdriver-trace) must also be active) | false | 92 | 93 | For example on Google App Engine: 94 | 95 | ```yaml 96 | env_variables: 97 | TOMCAT_MODULES_ENABLE: distributed-sessions 98 | TOMCAT_PROPERTIES: gcp.distributed-sessions.uriExcludePattern=^/_ah/.* 99 | ``` 100 | 101 | #### Usage outside of Google Cloud Platform 102 | If you are using the runtime outside of GCP, you will want to make sure that your application has access to 103 | the Datastore. In this case, check out the [Google Cloud Authentication](https://developers.google.com/identity/protocols/application-default-credentials) guide. 104 | 105 | ### Stackdriver Trace 106 | The trace module sends information about requests (such as latency) to the [Stackdriver Trace service](https://cloud.google.com/trace/docs/). 107 | 108 | To enable this module add `stackdriver-trace` to the list of enabled modules. 109 | 110 | ```yaml 111 | env_variables: 112 | TOMCAT_MODULES_ENABLE: stackdriver-trace 113 | ``` 114 | 115 | The following configuration is available through the the environment variable `TOMCAT_PROPERTIES`. 116 | 117 | | Property | Description | Default | 118 | |---|---|---| 119 | | gcp.stackdriver-trace.scheduledDelay | The traces are grouped before being sent to the Stackdriver service, this is the maximum time in seconds a trace can be buffered| 15 | 120 | 121 | #### Usage outside of Google Cloud Platform 122 | When you are using this module outside of GCP you need to provide credentials through [Google Cloud Authentication](https://developers.google.com/identity/protocols/application-default-credentials). 123 | 124 | ### Stackdriver Logging 125 | When the Tomcat runtime is running on Google App Engine flexible environment all output to stdout/stderr is forwarded to Stackdriver Logging 126 | and available in the Cloud Console Log Viewer. 127 | 128 | However more detailed and integrated logs are available if the [Stackdriver Logging](https://cloud.google.com/logging/) mechanism is used directly. 129 | 130 | To take advantage of this integration, add the [Google Cloud Java Client for Logging](https://github.com/GoogleCloudPlatform/google-cloud-java/tree/master/google-cloud-logging) 131 | to your dependencies and provide a Java Util Logging configuration file (`logging.properties`) as part of the resources of the application (`classes/logging.properties`) with the following content: 132 | 133 | ```properties 134 | handlers=com.google.cloud.logging.LoggingHandler 135 | 136 | # Optional configuration 137 | .level=FINE 138 | com.google.cloud.logging.LoggingHandler.level=FINE 139 | com.google.cloud.logging.LoggingHandler.log=gae_app.log 140 | com.google.cloud.logging.LoggingHandler.formatter=java.util.logging.SimpleFormatter 141 | java.util.logging.SimpleFormatter.format=%3$s: %5$s%6$s 142 | ``` 143 | 144 | ### Enabling gzip compression 145 | Tomcat offers the possibility to use GZIP compression. 146 | 147 | To take advantage of this feature set the property `tomcat.server.connector.compression` to `on`: 148 | ```yaml 149 | env_variables: 150 | TOMCAT_PROPERTIES: tomcat.server.connector.compression=on 151 | ``` 152 | 153 | You can also specify a numerical value indicating the minimal amount of data (in bytes) before using compression. 154 | 155 | Detailed documentation can be found in the [Tomcat documentation](http://tomcat.apache.org/tomcat-8.5-doc/config/http.html), 156 | at the attribute `compression`. 157 | 158 | ## Development Guide 159 | 160 | * See [instructions](DEVELOPING.md) on how to build and test this image. 161 | 162 | ### Contributing changes 163 | 164 | * See [CONTRIBUTING.md](CONTRIBUTING.md) 165 | 166 | ## Licensing 167 | 168 | * See [LICENSE](LICENSE) 169 | -------------------------------------------------------------------------------- /checkstyle/custom-checks.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /checkstyle/java.header: -------------------------------------------------------------------------------- 1 | ^/\*$ 2 | ^ \* Copyright \(C\) \d\d\d\d Google Inc\.$ 3 | ^ \*$ 4 | ^ \* Licensed under the Apache License, Version 2\.0 \(the "License"\);$ 5 | ^ \* you may not use this file except in compliance with the License\.$ 6 | ^ \* You may obtain a copy of the License at$ 7 | ^ \*$ 8 | ^ \* http://www.apache.org/licenses/LICENSE-2.0$ 9 | ^ \*$ 10 | ^ \* Unless required by applicable law or agreed to in writing, software$ 11 | ^ \* distributed under the License is distributed on an "AS IS" BASIS,$ 12 | ^ \* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied\.$ 13 | ^ \* See the License for the specific language governing permissions and$ 14 | ^ \* limitations under the License\.$ 15 | ^ \*/$ -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 4.0.0 21 | 22 | com.google.cloud.runtimes 23 | tomcat-parent 24 | 0.1.0-SNAPSHOT 25 | pom 26 | Google Tomcat Runtime Parent 27 | 28 | 29 | UTF-8 30 | UTF-8 31 | yyyy-MM-dd-HH-mm 32 | 1.0.1 33 | 8 34 | 5 35 | 15 36 | ${tomcat.major.version}.${tomcat.minor.version}.${tomcat.dot.version} 37 | 38 | ${tomcat.major.version} 39 | ${tomcat.major.version}.${tomcat.minor.version}-${maven.build.timestamp} 40 | 41 | tomcat 42 | gcr.io/google-appengine/openjdk:8 43 | gcr.io/$PROJECT_ID 44 | ${docker.project.namespace}/${docker.image.name}:${docker.tag.long} 45 | 46 | 47 | 48 | tomcat-gcp-lib 49 | tomcat 50 | tests/test-war-simple 51 | tests/test-war-integration 52 | tests/test-war-spring-boot 53 | tests/test-distributed-war 54 | 55 | 56 | 57 | 58 | 59 | maven-checkstyle-plugin 60 | 2.17 61 | 62 | false 63 | true 64 | warning 65 | 66 | 67 | 68 | com.puppycrawl.tools 69 | checkstyle 70 | 8.1 71 | 72 | 73 | 74 | 75 | validate-google-style 76 | validate 77 | 78 | check 79 | 80 | 81 | google_checks.xml 82 | 83 | 84 | 85 | validate-file-header 86 | validate 87 | 88 | check 89 | 90 | 91 | checkstyle/java.header 92 | checkstyle/custom-checks.xml 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | org.apache.maven.plugins 102 | maven-enforcer-plugin 103 | 1.4.1 104 | 105 | 106 | enforce-maven 107 | 108 | enforce 109 | 110 | 111 | 112 | 113 | [3.0,) 114 | 115 | 116 | [1.8,) 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | org.apache.maven.plugins 125 | maven-dependency-plugin 126 | 3.0.1 127 | 128 | 129 | org.apache.maven.plugins 130 | maven-resources-plugin 131 | 3.0.1 132 | 133 | 134 | org.apache.maven.plugins 135 | maven-compiler-plugin 136 | 3.6.1 137 | 138 | 1.8 139 | 1.8 140 | 141 | 142 | 143 | io.fabric8 144 | docker-maven-plugin 145 | 0.21.0 146 | 147 | 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2017 Google Inc. All rights reserved. 4 | 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # 18 | # All the arguments passed to this script are transferred to the maven command 19 | # 20 | 21 | dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 22 | projectRoot=${dir}/.. 23 | 24 | source ${projectRoot}/scripts/utils/maven.sh 25 | 26 | pushd ${projectRoot} 27 | maven_utils::execute clean install 28 | popd 29 | 30 | gcloud builds submit --config ${projectRoot}/tomcat/target/cloudbuild/build.yaml ${projectRoot} 31 | -------------------------------------------------------------------------------- /scripts/ci/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2017 Google Inc. All rights reserved. 4 | 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | set -e 17 | 18 | readonly dir=$(dirname $0) 19 | readonly projectRoot=$dir/../.. 20 | 21 | # Load the library with Maven utilities 22 | source ${projectRoot}/scripts/utils/maven.sh 23 | source ${projectRoot}/scripts/utils/gcloud.sh 24 | 25 | # If no namespace is specified deduct it from the gcloud CLI 26 | if [ -z "$DOCKER_NAMESPACE" ]; then 27 | export DOCKER_NAMESPACE="gcr.io/$(gcloud_utils::get_project_name)" 28 | fi 29 | 30 | # If we are in a gcloud environment we want to initialize the gcloud CLI 31 | if [ -n "$GCLOUD_FILE" ]; then 32 | source ${dir}/gcloud-init.sh 33 | fi 34 | 35 | # Generate the Docker image tag from the git tag and generate the complete image name 36 | pushd ${projectRoot} 37 | export DOCKER_TAG_LONG=$(git rev-parse --short HEAD) 38 | readonly IMAGE=$(maven_utils::get_property cloudbuild.tomcat.image) 39 | popd 40 | 41 | echo "Building $IMAGE and running structure tests" 42 | ${projectRoot}/scripts/build.sh 43 | 44 | echo "Running integration tests on $IMAGE" 45 | ${projectRoot}/scripts/integration_test.sh ${IMAGE} 46 | -------------------------------------------------------------------------------- /scripts/ci/gcloud-init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2016 Google Inc. All rights reserved. 4 | 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | # Set up gcloud and auth 19 | set -ex 20 | 21 | DIR=$(pwd) 22 | 23 | if [ -z $GCLOUD_FILE ]; then 24 | echo '$GCLOUD_FILE environment variable must be set.' 25 | exit 1 26 | fi 27 | 28 | if [ -z $KEYFILE ]; then 29 | echo '$KEYFILE environment variable must be set.' 30 | exit 1 31 | fi 32 | 33 | if [ -z $GCP_PROJECT ]; then 34 | echo '$GCP_PROJECT environment variable must be set.' 35 | exit 1 36 | fi 37 | 38 | LOCAL_GCLOUD_FILE=gcloud.tar.gz 39 | cp $GCLOUD_FILE $LOCAL_GCLOUD_FILE 40 | 41 | # Hide the output here, it's long. 42 | tar -xzf $LOCAL_GCLOUD_FILE 43 | export PATH=$DIR/google-cloud-sdk/bin:$PATH 44 | 45 | gcloud auth activate-service-account --key-file=$KEYFILE 46 | gcloud config set project $GCP_PROJECT 47 | export GOOGLE_APPLICATION_CREDENTIALS=$KEYFILE 48 | 49 | gcloud components install beta -q -------------------------------------------------------------------------------- /scripts/ci/release-cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | steps: 2 | # Perform maven build, omitting local docker operations 3 | - name: 'gcr.io/cloud-builders/mvn:3.5.0-jdk-8' 4 | args: 5 | - '--batch-mode' 6 | - '-P-local-docker-build' 7 | - '-P-test.local' 8 | - '-Ddocker.tag.long=${_DOCKER_TAG}' 9 | - 'clean' 10 | - 'install' 11 | 12 | # Build the runtime container 13 | - name: 'gcr.io/cloud-builders/docker' 14 | args: ['build', '--tag=${_IMAGE}', '--no-cache', 'tomcat/target/docker-src'] 15 | 16 | # Runtimes-common structure tests 17 | # See https://github.com/GoogleCloudPlatform/runtimes-common/tree/master/structure_tests 18 | - name: 'gcr.io/gcp-runtimes/structure_test' 19 | args: ['--image', '${_IMAGE}', '-v', '--config', '/workspace/tomcat/target/test-classes/structure.yaml'] 20 | 21 | images: ['${_IMAGE}'] 22 | -------------------------------------------------------------------------------- /scripts/ci/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2017 Google Inc. All rights reserved. 4 | 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 18 | projectRoot=${dir}/../.. 19 | 20 | source ${projectRoot}/scripts/utils/gcloud.sh 21 | 22 | RUNTIME_NAME='tomcat' 23 | RUNTIME_VERSION='8.5' 24 | 25 | if [ -z "${DOCKER_TAG}" ]; then 26 | DOCKER_TAG="${RUNTIME_VERSION}-$(date -u +%Y-%m-%d_%H_%M)" 27 | fi 28 | 29 | if [ -z "$DOCKER_NAMESPACE" ]; then 30 | DOCKER_NAMESPACE="gcr.io/$(gcloud_utils::get_project_name)" 31 | fi 32 | 33 | IMAGE="${DOCKER_NAMESPACE}/${RUNTIME_NAME}:${DOCKER_TAG}" 34 | 35 | gcloud builds submit \ 36 | --config ${dir}/release-cloudbuild.yaml \ 37 | --substitutions="_IMAGE=$IMAGE,_DOCKER_TAG=$DOCKER_TAG" \ 38 | ${projectRoot} 39 | 40 | # Allow external script to reference the image tag 41 | export TAG=${DOCKER_TAG} -------------------------------------------------------------------------------- /scripts/integration_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2016 Google Inc. All rights reserved. 4 | 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -e 18 | 19 | # Runs integration tests on a given runtime image 20 | 21 | readonly dir=$(dirname $0) 22 | readonly projectRoot=${dir}/.. 23 | readonly testAppDir=${projectRoot}/tests/test-war-integration 24 | readonly deployDir=${testAppDir}/target/docker-src 25 | readonly service="tomcat-runtime-integration" 26 | 27 | readonly imageUnderTest=$1 28 | if [ -z "${imageUnderTest}" ]; then 29 | echo "Usage: ${0} " 30 | exit 1 31 | fi 32 | 33 | # build the test app 34 | pushd ${testAppDir} 35 | mvn -B -P-local-docker-build -P-local.test clean install 36 | popd 37 | 38 | # deploy to app engine 39 | pushd $deployDir 40 | export STAGING_IMAGE=$imageUnderTest 41 | envsubst '$STAGING_IMAGE' < Dockerfile.in > Dockerfile 42 | echo "Deploying to App Engine..." 43 | gcloud app deploy -q 44 | popd 45 | 46 | readonly DEPLOYED_APP_URL="https://${service}-dot-$(gcloud app describe | grep defaultHostname | awk '{print $2}')" 47 | echo "Running integration tests on application that is deployed at $DEPLOYED_APP_URL" 48 | 49 | # run in cloud container builder 50 | gcloud builds submit \ 51 | --config $testAppDir/target/cloudbuild/integration.yaml \ 52 | --substitutions "_DEPLOYED_APP_URL=$DEPLOYED_APP_URL" \ 53 | --timeout=25m \ 54 | ${dir} -------------------------------------------------------------------------------- /scripts/utils/gcloud.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2017 Google Inc. All rights reserved. 4 | 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | function gcloud_utils::get_project_name () { 18 | echo $(gcloud info \ 19 | | awk '/^Project: / { print $2 }' \ 20 | | sed 's/\[//' \ 21 | | sed 's/\]//') 22 | } 23 | -------------------------------------------------------------------------------- /scripts/utils/maven.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2017 Google Inc. All rights reserved. 4 | 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # Execute an arbitrary maven command 18 | # Arguments: 19 | # - All the arguments are passed to the maven command (Example: clean install) 20 | # 21 | function maven_utils::execute () { 22 | if [ -n "$DOCKER_NAMESPACE" ]; then 23 | set -- $@ "-Ddocker.project.namespace=$DOCKER_NAMESPACE" 24 | fi 25 | 26 | if [ -n "$DOCKER_TAG_LONG" ]; then 27 | set -- $@ "-Ddocker.tag.long=$DOCKER_TAG_LONG" 28 | fi 29 | 30 | mvn -P-local-docker-build -P-test.local "$@" 31 | } 32 | 33 | # Return a maven property 34 | # Arguments: 35 | # - The name of the property (Example: docker.tag.long) 36 | # 37 | function maven_utils::get_property () { 38 | echo $(maven_utils::execute org.codehaus.mojo:exec-maven-plugin:1.6.0:exec \ 39 | --non-recursive -q \ 40 | -Dexec.executable="echo" \ 41 | -Dexec.args="\${$1}" ) 42 | } -------------------------------------------------------------------------------- /scripts/utils/structure_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2017 Google Inc. All rights reserved. 4 | 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # 18 | # Fetch and execute the structure test framework run script. 19 | # 20 | dir=`dirname $0` 21 | scriptPath=https://raw.githubusercontent.com/GoogleCloudPlatform/runtimes-common/a5efef7f1f2cfd60814641fcff8239ea301e661d/structure_tests/ext_run.sh 22 | destDir=$dir/../target 23 | fileName=$destDir/run_structure_tests.sh 24 | 25 | if [ ! -d $destDir ] 26 | then 27 | mkdir -p $destDir 28 | fi 29 | 30 | curl $scriptPath > $fileName 31 | bash $fileName "$@" -------------------------------------------------------------------------------- /tests/test-distributed-war/.gitignore: -------------------------------------------------------------------------------- 1 | credential.json -------------------------------------------------------------------------------- /tests/test-distributed-war/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | tomcat-parent 7 | com.google.cloud.runtimes 8 | 0.1.0-SNAPSHOT 9 | ../../pom.xml 10 | 11 | 4.0.0 12 | 13 | test-distributed-war 14 | Tomcat-Runtime :: Tests :: Distributed Tests 15 | war 16 | 17 | 18 | ${docker.image.name}:${docker.tag.long} 19 | 20 | 21 | 22 | 23 | javax.servlet 24 | javax.servlet-api 25 | 3.1.0 26 | provided 27 | 28 | 29 | 30 | 31 | 32 | local-docker-build 33 | 34 | true 35 | 36 | 37 | 38 | 39 | io.fabric8 40 | docker-maven-plugin 41 | 42 | 43 | build 44 | package 45 | 46 | build 47 | 48 | 49 | 50 | 51 | ${project.artifactId} 52 | 53 | ${project.build.directory}/docker-src 54 | 55 | ${project.version} 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | maven-resources-plugin 73 | 74 | 75 | copy-dockerfile 76 | generate-resources 77 | 78 | copy-resources 79 | 80 | 81 | ${project.build.directory}/docker-src 82 | 83 | 84 | ${basedir}/src/main/docker 85 | true 86 | 87 | 88 | 89 | 90 | 91 | copy-appengine-file 92 | generate-resources 93 | 94 | copy-resources 95 | 96 | 97 | ${project.build.directory}/docker-src 98 | 99 | 100 | ${basedir}/src/main/appengine 101 | true 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | org.apache.maven.plugins 110 | maven-war-plugin 111 | 3.1.0 112 | 113 | ${project.build.directory}/docker-src 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /tests/test-distributed-war/src/main/appengine/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: custom 2 | env: flex 3 | service: tomcat-distributed-test 4 | 5 | manual_scaling: 6 | instances: 1 7 | 8 | handlers: 9 | - url: /.* 10 | script: ignored 11 | secure: optional -------------------------------------------------------------------------------- /tests/test-distributed-war/src/main/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | FROM ${test.docker.image} 15 | ADD ${project.build.finalName}.war $APP_DESTINATION 16 | 17 | COPY credential.json /credentials/key.json 18 | ENV GOOGLE_APPLICATION_CREDENTIALS /credentials/key.json 19 | 20 | ENV TOMCAT_MODULES_ENABLE distributed-sessions,stackdriver-trace 21 | ENV TOMCAT_LOGGING_PROPERTIES com.google.cloud.runtimes.tomcat.session.level=ALL,gcp.distributed-sessions.enableTrace=true -------------------------------------------------------------------------------- /tests/test-distributed-war/src/main/docker/credential.json: -------------------------------------------------------------------------------- 1 | /* If you are using the test-distributed-war outside of Google Cloud, you must replace this file 2 | * with your crendentials in order to access the datastore service. 3 | * You can find the procedure to generate credential.json at: 4 | * https://developers.google.com/identity/protocols/application-default-credentials 5 | */ -------------------------------------------------------------------------------- /tests/test-distributed-war/src/main/java/com/google/cloud/runtimes/tomcat/test/distributed/SessionServlet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.cloud.runtimes.tomcat.test.distributed; 18 | 19 | import java.io.IOException; 20 | import java.util.Arrays; 21 | import java.util.HashMap; 22 | import java.util.Map; 23 | import java.util.stream.DoubleStream; 24 | import javax.servlet.ServletException; 25 | import javax.servlet.annotation.WebServlet; 26 | import javax.servlet.http.HttpServlet; 27 | import javax.servlet.http.HttpServletRequest; 28 | import javax.servlet.http.HttpServletResponse; 29 | 30 | /** 31 | * This servlet demonstrates the usage of distributed sessions by adding session parameters and 32 | * modifying their values at each requests. 33 | */ 34 | @WebServlet(urlPatterns = {"/session"}) 35 | public class SessionServlet extends HttpServlet { 36 | 37 | @Override 38 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) 39 | throws ServletException, IOException { 40 | resp.setContentType("text/plain"); 41 | 42 | int count = 0; 43 | Object sessionValue = req.getSession().getAttribute("count"); 44 | resp.getWriter().println("Max inactive time: " + req.getSession().getMaxInactiveInterval()); 45 | 46 | if (sessionValue != null) { 47 | count = (int)sessionValue; 48 | count++; 49 | resp.getWriter().println("Session parameter: " + count); 50 | } else { 51 | resp.getWriter().println("No session parameter found, reload the page"); 52 | } 53 | 54 | Map map = new HashMap<>(); 55 | map.put("First entry", Arrays.asList(1,2,3,4,5)); 56 | map.put("Second entry", DoubleStream.generate(() -> Math.random() * 10000) 57 | .limit(5000) 58 | .toArray()); 59 | 60 | req.getSession().setAttribute("count", count); 61 | if (Math.random() > 0.7) { 62 | resp.getWriter().println("Modified map"); 63 | req.getSession().setAttribute("map", map); 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /tests/test-distributed-war/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | Web Application for Servlet 3.1 8 | 9 | 1 10 | 11 | -------------------------------------------------------------------------------- /tests/test-war-integration/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 21 | tomcat-parent 22 | com.google.cloud.runtimes 23 | 0.1.0-SNAPSHOT 24 | ../../pom.xml 25 | 26 | 4.0.0 27 | 28 | test-war-integration 29 | Tomcat-Runtime :: Tests :: Integration Tests 30 | war 31 | 32 | 33 | ${docker.image.name}:${docker.tag.long} 34 | 35 | 36 | 37 | 38 | javax.servlet 39 | javax.servlet-api 40 | 3.1.0 41 | provided 42 | 43 | 44 | com.fasterxml.jackson.core 45 | jackson-databind 46 | 2.8.7 47 | 48 | 49 | com.google.cloud 50 | google-cloud-logging 51 | 1.4.0 52 | 53 | 54 | com.google.cloud 55 | google-cloud-monitoring 56 | 0.22.0-alpha 57 | 58 | 59 | 60 | 61 | 62 | local-docker-build 63 | 64 | true 65 | 66 | 67 | 68 | 69 | io.fabric8 70 | docker-maven-plugin 71 | 72 | 73 | build 74 | package 75 | 76 | build 77 | 78 | 79 | 80 | 81 | ${project.artifactId} 82 | 83 | ${project.build.directory}/docker-src 84 | 85 | ${project.version} 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | maven-resources-plugin 102 | 103 | 104 | copy-dockerfile 105 | generate-resources 106 | 107 | copy-resources 108 | 109 | 110 | ${project.build.directory}/docker-src 111 | 112 | 113 | ${basedir}/src/main/docker 114 | true 115 | 116 | 117 | 118 | 119 | 120 | copy-cloudbuild-configuration 121 | generate-resources 122 | 123 | copy-resources 124 | 125 | 126 | ${project.build.directory}/cloudbuild 127 | 128 | 129 | ${basedir}/src/main/cloudbuild 130 | true 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | org.apache.maven.plugins 139 | maven-war-plugin 140 | 3.1.0 141 | 142 | ${project.build.directory}/docker-src 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /tests/test-war-integration/src/main/cloudbuild/integration.yaml: -------------------------------------------------------------------------------- 1 | # Cloud Builder pipeline for running integration tests 2 | # https://cloud.google.com/container-builder/docs/overview 3 | steps: 4 | # Runtimes-common integration tests 5 | # See https://github.com/GoogleCloudPlatform/runtimes-common/tree/master/integration_tests 6 | - name: 'gcr.io/gcp-runtimes/integration_test:2017-09-18-104536' 7 | args: 8 | - '--url=${_DEPLOYED_APP_URL}' 9 | - '--verbose' 10 | -------------------------------------------------------------------------------- /tests/test-war-integration/src/main/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | FROM ${test.docker.image} 15 | COPY ${project.build.finalName}.war $CATALINA_BASE/webapps/ROOT.war -------------------------------------------------------------------------------- /tests/test-war-integration/src/main/docker/Dockerfile.in: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | FROM ${STAGING_IMAGE} 15 | ADD ${project.build.finalName}.war $CATALINA_BASE/webapps/ROOT.war -------------------------------------------------------------------------------- /tests/test-war-integration/src/main/docker/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: custom 2 | env: flex 3 | service: tomcat-runtime-integration 4 | 5 | manual_scaling: 6 | instances: 1 7 | 8 | env_variables: 9 | TOMCAT_MODULES_ENABLE: stackdriver-trace 10 | 11 | handlers: 12 | - url: /.* 13 | script: ignored 14 | secure: optional -------------------------------------------------------------------------------- /tests/test-war-integration/src/main/java/com/google/cloud/runtimes/tomcat/test/integration/CustomLoggingServlet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.cloud.runtimes.tomcat.test.integration; 18 | 19 | import com.fasterxml.jackson.databind.JsonNode; 20 | import com.fasterxml.jackson.databind.ObjectMapper; 21 | import com.google.cloud.MonitoredResource; 22 | import com.google.cloud.logging.LogEntry; 23 | import com.google.cloud.logging.Logging; 24 | import com.google.cloud.logging.LoggingOptions; 25 | import com.google.cloud.logging.Payload; 26 | import com.google.cloud.logging.Severity; 27 | import java.io.IOException; 28 | import java.net.URLEncoder; 29 | import java.util.Collections; 30 | import javax.servlet.ServletException; 31 | import javax.servlet.annotation.WebServlet; 32 | import javax.servlet.http.HttpServlet; 33 | import javax.servlet.http.HttpServletRequest; 34 | import javax.servlet.http.HttpServletResponse; 35 | 36 | @WebServlet(urlPatterns = "/logging_custom") 37 | public class CustomLoggingServlet extends HttpServlet { 38 | 39 | private static final ObjectMapper objectMapper = new ObjectMapper(); 40 | 41 | private final Logging logging = LoggingOptions.getDefaultInstance().getService(); 42 | 43 | @Override 44 | protected void doPost(HttpServletRequest req, HttpServletResponse resp) 45 | throws ServletException, IOException { 46 | 47 | JsonNode body = objectMapper.readTree(req.getReader()); 48 | String logName = body.path("log_name").asText(); 49 | String token = body.path("token").asText(); 50 | Severity severity = Severity.valueOf(body.path("level").asText()); 51 | 52 | LogEntry logEntry = LogEntry.newBuilder(Payload.StringPayload.of(token)) 53 | .setLogName(logName) 54 | .setSeverity(severity) 55 | .setResource(MonitoredResource.newBuilder("global").build()) 56 | .build(); 57 | 58 | logging.write(Collections.singletonList(logEntry)); 59 | 60 | resp.setContentType("text/plain"); 61 | resp.getWriter().println(URLEncoder.encode(logName, "UTF-8")); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/test-war-integration/src/main/java/com/google/cloud/runtimes/tomcat/test/integration/CustomServlet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.cloud.runtimes.tomcat.test.integration; 18 | 19 | import com.google.common.io.CharStreams; 20 | import java.io.IOException; 21 | import java.io.InputStreamReader; 22 | import javax.servlet.ServletException; 23 | import javax.servlet.annotation.WebServlet; 24 | import javax.servlet.http.HttpServlet; 25 | import javax.servlet.http.HttpServletRequest; 26 | import javax.servlet.http.HttpServletResponse; 27 | 28 | /** 29 | * Reference the custom tests for the integration framework 30 | * (https://github.com/GoogleCloudPlatform/runtimes-common/tree/master/integration_tests) 31 | */ 32 | @WebServlet(urlPatterns = {"/custom"}) 33 | public class CustomServlet extends HttpServlet { 34 | 35 | @Override 36 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) 37 | throws ServletException, IOException { 38 | 39 | String configuration = CharStreams.toString( 40 | new InputStreamReader(getClass().getResourceAsStream("/custom-test-specification.json"))); 41 | 42 | resp.setContentType("application/json"); 43 | resp.getWriter().print(configuration); 44 | } 45 | } -------------------------------------------------------------------------------- /tests/test-war-integration/src/main/java/com/google/cloud/runtimes/tomcat/test/integration/DumpServlet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.cloud.runtimes.tomcat.test.integration; 18 | 19 | import java.io.IOException; 20 | import java.io.PrintWriter; 21 | import java.util.Collections; 22 | import java.util.logging.Logger; 23 | import javax.servlet.ServletException; 24 | import javax.servlet.annotation.WebServlet; 25 | import javax.servlet.http.HttpServlet; 26 | import javax.servlet.http.HttpServletRequest; 27 | import javax.servlet.http.HttpServletResponse; 28 | 29 | @WebServlet(urlPatterns = {"/dump/*"}) 30 | public class DumpServlet extends HttpServlet { 31 | 32 | private static final Logger log = Logger.getLogger(DumpServlet.class.getName()); 33 | 34 | @Override 35 | protected void doGet(HttpServletRequest request, HttpServletResponse response) 36 | throws ServletException, IOException { 37 | log.info("JUL.info:" + request.getRequestURI()); 38 | getServletContext().log("ServletContext.log:" + request.getRequestURI()); 39 | if (Boolean.parseBoolean(request.getParameter("throw"))) { 40 | throw new ServletException("Test Exception"); 41 | } 42 | 43 | response.setContentType("text/html"); 44 | response.setStatus(HttpServletResponse.SC_OK); 45 | 46 | PrintWriter out = response.getWriter(); 47 | 48 | out.println("

DumpServlet

"); 49 | out.println("

Context Fields:

"); 50 | out.println("
");
 51 |     out.printf("serverInfo=%s%n", getServletContext().getServerInfo());
 52 |     out.printf("getServletContextName=%s%n", getServletContext().getServletContextName());
 53 |     out.printf("virtualServerName=%s%n", getServletContext().getVirtualServerName());
 54 |     out.printf("contextPath=%s%n", getServletContext().getContextPath());
 55 |     out.printf(
 56 |         "version=%d.%d%n",
 57 |         getServletContext().getMajorVersion(),
 58 |         getServletContext().getMinorVersion());
 59 |     out.printf(
 60 |         "effectiveVersion=%d.%d%n",
 61 |         getServletContext().getEffectiveMajorVersion(),
 62 |         getServletContext().getEffectiveMinorVersion());
 63 |     out.println("
"); 64 | out.println("

Request Fields:

"); 65 | out.println("
");
 66 |     out.printf(
 67 |         "remoteHost/Addr:port=%s/%s:%d%n",
 68 |         request.getRemoteHost(),
 69 |         request.getRemoteAddr(),
 70 |         request.getRemotePort());
 71 |     out.printf(
 72 |         "localName/Addr:port=%s/%s:%d%n",
 73 |         request.getLocalName(),
 74 |         request.getLocalAddr(),
 75 |         request.getLocalPort());
 76 |     out.printf(
 77 |         "scheme=%s(secure=%b) method=%s protocol=%s%n",
 78 |         request.getScheme(),
 79 |         request.isSecure(),
 80 |         request.getMethod(),
 81 |         request.getProtocol());
 82 |     out.printf("serverName:serverPort=%s:%d%n", request.getServerName(), request.getServerPort());
 83 |     out.printf("requestURI=%s%n", request.getRequestURI());
 84 |     out.printf("requestURL=%s%n", request.getRequestURL().toString());
 85 |     out.printf(
 86 |         "contextPath|servletPath|pathInfo=%s|%s|%s%n",
 87 |         request.getContextPath(),
 88 |         request.getServletPath(),
 89 |         request.getPathInfo());
 90 |     out.printf(
 91 |         "session/new=%s/%b%n", request.getSession(true).getId(), request.getSession().isNew());
 92 |     out.println("
"); 93 | out.println("

Request Headers:

"); 94 | out.println("
");
 95 |     for (String n : Collections.list(request.getHeaderNames())) {
 96 |       for (String v : Collections.list(request.getHeaders(n))) {
 97 |         out.printf("%s: %s%n", n, v);
 98 |       }
 99 |     }
100 |     out.println("
"); 101 | out.println("

Response Fields:

"); 102 | out.println("
");
103 |     out.printf("bufferSize=%d%n", response.getBufferSize());
104 |     out.printf("encodedURL(\"/foo/bar\")=%s%n", response.encodeURL("/foo/bar"));
105 |     out.printf("encodedRedirectURL(\"/foo/bar\")=%s%n", response.encodeRedirectURL("/foo/bar"));
106 |     out.println("
"); 107 | 108 | out.println("

Environment:

"); 109 | out.println("
");
110 |     for (String n : System.getenv().keySet()) {
111 |       out.printf("%s=%s%n", n, System.getenv(n));
112 |     }
113 |     out.println("
"); 114 | 115 | out.println("

System Properties:

"); 116 | out.println("
");
117 |     for (Object n : System.getProperties().keySet()) {
118 |       out.printf("%s=%s%n", n, System.getProperty(String.valueOf(n)));
119 |     }
120 |     out.println("
"); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /tests/test-war-integration/src/main/java/com/google/cloud/runtimes/tomcat/test/integration/ExceptionServlet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.cloud.runtimes.tomcat.test.integration; 18 | 19 | import com.fasterxml.jackson.databind.ObjectMapper; 20 | 21 | import java.io.IOException; 22 | import javax.servlet.ServletException; 23 | import javax.servlet.annotation.WebServlet; 24 | import javax.servlet.http.HttpServlet; 25 | import javax.servlet.http.HttpServletRequest; 26 | import javax.servlet.http.HttpServletResponse; 27 | 28 | @WebServlet(urlPatterns = {"/exception"}) 29 | public class ExceptionServlet extends HttpServlet { 30 | 31 | private static final ObjectMapper objectMapper = new ObjectMapper(); 32 | 33 | static class ExceptionRequest { 34 | public String token; 35 | } 36 | 37 | @Override 38 | protected void doPost(HttpServletRequest req, HttpServletResponse resp) 39 | throws ServletException, IOException { 40 | ExceptionRequest exceptionRequest 41 | = objectMapper.readValue(req.getReader(), ExceptionRequest.class); 42 | 43 | // Print an exception stack trace containing the provided token. This should be picked up by 44 | // Stackdriver exception monitoring. 45 | new RuntimeException(String.format( 46 | "Sample runtime exception for integration test. Token is %s", exceptionRequest.token)) 47 | .printStackTrace(); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /tests/test-war-integration/src/main/java/com/google/cloud/runtimes/tomcat/test/integration/HelloServlet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.cloud.runtimes.tomcat.test.integration; 18 | 19 | import java.io.IOException; 20 | import javax.servlet.ServletException; 21 | import javax.servlet.annotation.WebServlet; 22 | import javax.servlet.http.HttpServlet; 23 | import javax.servlet.http.HttpServletRequest; 24 | import javax.servlet.http.HttpServletResponse; 25 | 26 | @WebServlet(urlPatterns = {"/"}) 27 | public class HelloServlet extends HttpServlet { 28 | 29 | @Override 30 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) 31 | throws ServletException, IOException { 32 | resp.setContentType("text.plain"); 33 | resp.getWriter().print("Hello World!"); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /tests/test-war-integration/src/main/java/com/google/cloud/runtimes/tomcat/test/integration/MonitoringServlet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.cloud.runtimes.tomcat.test.integration; 18 | 19 | import com.fasterxml.jackson.databind.JsonNode; 20 | import com.fasterxml.jackson.databind.ObjectMapper; 21 | import com.google.api.Metric; 22 | import com.google.cloud.ServiceOptions; 23 | import com.google.cloud.Timestamp; 24 | import com.google.cloud.monitoring.v3.MetricServiceClient; 25 | import com.google.monitoring.v3.Point; 26 | import com.google.monitoring.v3.ProjectName; 27 | import com.google.monitoring.v3.TimeInterval; 28 | import com.google.monitoring.v3.TimeSeries; 29 | import com.google.monitoring.v3.TypedValue; 30 | import java.io.IOException; 31 | import java.util.Collections; 32 | import java.util.logging.Logger; 33 | import javax.servlet.ServletException; 34 | import javax.servlet.annotation.WebServlet; 35 | import javax.servlet.http.HttpServlet; 36 | import javax.servlet.http.HttpServletRequest; 37 | import javax.servlet.http.HttpServletResponse; 38 | 39 | @WebServlet(urlPatterns = "/monitoring") 40 | public class MonitoringServlet extends HttpServlet { 41 | 42 | private static final ObjectMapper objectMapper = new ObjectMapper(); 43 | 44 | private static final Logger logger = Logger.getLogger(MonitoringServlet.class.getName()); 45 | 46 | @Override 47 | protected void doPost(HttpServletRequest req, HttpServletResponse resp) 48 | throws ServletException, IOException { 49 | JsonNode body = objectMapper.readTree(req.getReader()); 50 | String name = body.path("name").asText(); 51 | long token = body.path("token").asLong(); 52 | 53 | logger.info("Creating Time series with name " + name + " and token " + token); 54 | 55 | MetricServiceClient serviceClient = MetricServiceClient.create(); 56 | 57 | TimeSeries timeSeries = 58 | TimeSeries.newBuilder() 59 | .addPoints(Point.newBuilder() 60 | .setValue(TypedValue.newBuilder().setInt64Value(token)) 61 | .setInterval(TimeInterval.newBuilder() 62 | .setEndTime(Timestamp.now().toProto()))) 63 | .setMetric(Metric.newBuilder().setType(name)) 64 | .build(); 65 | 66 | serviceClient.createTimeSeries( 67 | ProjectName.create(ServiceOptions.getDefaultProjectId()), 68 | Collections.singletonList(timeSeries)); 69 | 70 | resp.setContentType("text/plain"); 71 | resp.getWriter().println("OK"); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /tests/test-war-integration/src/main/java/com/google/cloud/runtimes/tomcat/test/integration/SecureTestServlet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.cloud.runtimes.tomcat.test.integration; 18 | 19 | import java.io.IOException; 20 | import javax.servlet.ServletException; 21 | import javax.servlet.annotation.WebServlet; 22 | import javax.servlet.http.HttpServlet; 23 | import javax.servlet.http.HttpServletRequest; 24 | import javax.servlet.http.HttpServletResponse; 25 | 26 | /** 27 | * This class is used by the the runtime common testing framework as a custom test to ensure 28 | * that when x-forwarded-proto is specified and equals to https 29 | * (e.g the application is served by a load balancer) 30 | * the connection is considered as secure. 31 | * 32 | *

33 | * Note: 34 | *

    35 | *
  • If the request with the header x-forwarded-proto does not come from a local ip the 36 | * connection will not be considered as secure.
  • 37 | * 38 | *
  • In Tomcat this header is interpreted by the valve: 39 | * https://tomcat.apache.org/tomcat-9.0-doc/config/valve.html#Remote_IP_Valve 40 | * See tomcat-base/server.xml for the configuration.
  • 41 | *
42 | *

43 | */ 44 | @WebServlet(urlPatterns = "/custom/tests/secure") 45 | public class SecureTestServlet extends HttpServlet { 46 | 47 | @Override 48 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) 49 | throws ServletException, IOException { 50 | 51 | resp.setContentType("plain/text"); 52 | 53 | if (req.getHeader("x-forwarded-proto") != null) { 54 | 55 | if (req.getHeader("x-forwarded-proto").equals("http") && req.isSecure()) { 56 | resp.setStatus(500); 57 | resp.getWriter().println( 58 | "Error: x-forwarded-proto is set to http and the connection is considered secure"); 59 | } else if (req.getHeader("x-forwarded-proto").equals("https") && !req.isSecure()) { 60 | resp.setStatus(500); 61 | resp.getWriter().println( 62 | "Error: x-forwarded-proto is set to https but the connection is not considered secure"); 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/test-war-integration/src/main/java/com/google/cloud/runtimes/tomcat/test/integration/StandardLoggingServlet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.cloud.runtimes.tomcat.test.integration; 18 | 19 | import com.fasterxml.jackson.databind.JsonNode; 20 | import com.fasterxml.jackson.databind.ObjectMapper; 21 | import java.io.IOException; 22 | import java.net.URLEncoder; 23 | import java.util.logging.Level; 24 | import java.util.logging.Logger; 25 | import javax.servlet.ServletException; 26 | import javax.servlet.annotation.WebServlet; 27 | import javax.servlet.http.HttpServlet; 28 | import javax.servlet.http.HttpServletRequest; 29 | import javax.servlet.http.HttpServletResponse; 30 | 31 | @WebServlet(urlPatterns = "/logging_standard") 32 | public class StandardLoggingServlet extends HttpServlet { 33 | 34 | private static final Logger logger = Logger.getLogger(StandardLoggingServlet.class.getName()); 35 | 36 | private static final ObjectMapper objectMapper = new ObjectMapper(); 37 | 38 | @Override 39 | protected void doPost(HttpServletRequest req, HttpServletResponse resp) 40 | throws ServletException, IOException { 41 | JsonNode body = objectMapper.readTree(req.getReader()); 42 | String token = body.path("token").asText(); 43 | String level = convertStackdriverSeverityToLoggingLevel(body.path("level").asText()); 44 | 45 | logger.log(Level.parse(level), token); 46 | 47 | resp.setContentType("text/plain"); 48 | resp.getWriter().println(URLEncoder.encode("appengine.googleapis.com/stdout", "UTF-8")); 49 | } 50 | 51 | private String convertStackdriverSeverityToLoggingLevel(String severity) { 52 | String level; 53 | switch (severity) { 54 | case "DEBUG": 55 | level = "FINE"; 56 | break; 57 | case "ERROR": 58 | case "CRITICAL": 59 | case "ALERT": 60 | level = "SEVERE"; 61 | break; 62 | default: 63 | level = severity; 64 | break; 65 | } 66 | 67 | return level; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/test-war-integration/src/main/resources/custom-test-specification.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "name": "test-gzip", 3 | "steps": [{ 4 | "name": "test-gzip-header", 5 | "configuration": { 6 | "headers": { "Accept-Encoding": "gzip" }, 7 | "method": "GET" 8 | }, 9 | "path": "/dump/all" 10 | }], 11 | "validation": { 12 | "match": [{ 13 | "key": "test-gzip-header.response.headers.Content-Encoding", 14 | "pattern": "gzip" 15 | }] 16 | } 17 | },{ 18 | "name": "test-x-forwarded-proto-header", 19 | "path": "custom/tests/secure" 20 | }] 21 | -------------------------------------------------------------------------------- /tests/test-war-integration/src/main/resources/logging.properties: -------------------------------------------------------------------------------- 1 | com.google.cloud.runtimes.level = FINE 2 | # Ideally we would prefer to set the handler for a specific class however this is not functional yet. 3 | # com.google.cloud.runtimes.tomcat.test.handlers = com.google.cloud.logging.LoggingHandler 4 | handlers = com.google.cloud.logging.LoggingHandler 5 | 6 | com.google.cloud.logging.LoggingHandler.level = FINE 7 | com.google.cloud.logging.LoggingHandler.log = appengine.googleapis.com%2Fstdout 8 | -------------------------------------------------------------------------------- /tests/test-war-integration/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | Web Application for Servlet 3.1 8 | 9 | -------------------------------------------------------------------------------- /tests/test-war-simple/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 21 | tomcat-parent 22 | com.google.cloud.runtimes 23 | 0.1.0-SNAPSHOT 24 | ../../pom.xml 25 | 26 | 4.0.0 27 | 28 | test-war-simple 29 | Tomcat-Runtime :: Tests :: Simple Tests 30 | war 31 | 32 | 33 | ${docker.image.name}:${docker.tag.long} 34 | 35 | 36 | 37 | 38 | javax.servlet 39 | javax.servlet-api 40 | 3.1.0 41 | provided 42 | 43 | 44 | 45 | 46 | 47 | local-docker-build 48 | 49 | true 50 | 51 | 52 | 53 | 54 | io.fabric8 55 | docker-maven-plugin 56 | 57 | 58 | build 59 | package 60 | 61 | build 62 | 63 | 64 | 65 | 66 | ${project.artifactId} 67 | 68 | ${project.build.directory}/docker-src 69 | 70 | ${project.version} 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | maven-resources-plugin 87 | 88 | 89 | copy-dockerfile 90 | generate-resources 91 | 92 | copy-resources 93 | 94 | 95 | ${project.build.directory}/docker-src 96 | 97 | 98 | ${basedir}/src/main/docker 99 | true 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | org.apache.maven.plugins 108 | maven-war-plugin 109 | 3.1.0 110 | 111 | ${project.build.directory}/docker-src 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /tests/test-war-simple/src/main/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | FROM ${test.docker.image} 15 | ADD ${project.build.finalName}.war $CATALINA_BASE/webapps/ROOT.war -------------------------------------------------------------------------------- /tests/test-war-simple/src/main/java/com/google/cloud/runtimes/tomcat/test/integration/HelloServlet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.cloud.runtimes.tomcat.test.integration; 18 | 19 | import java.io.IOException; 20 | import javax.servlet.ServletException; 21 | import javax.servlet.annotation.WebServlet; 22 | import javax.servlet.http.HttpServlet; 23 | import javax.servlet.http.HttpServletRequest; 24 | import javax.servlet.http.HttpServletResponse; 25 | 26 | @WebServlet(urlPatterns = {"/hello/*"}) 27 | public class HelloServlet extends HttpServlet { 28 | 29 | @Override 30 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) 31 | throws ServletException, IOException { 32 | resp.setContentType("text.plain"); 33 | resp.getWriter().println("Hello from the simple war application"); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /tests/test-war-simple/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | Web Application for Servlet 3.1 8 | 9 | -------------------------------------------------------------------------------- /tests/test-war-simple/src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | Welcome to the Simple Test.
-------------------------------------------------------------------------------- /tests/test-war-spring-boot/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 4.0.0 21 | 22 | 23 | tomcat-parent 24 | com.google.cloud.runtimes 25 | 0.1.0-SNAPSHOT 26 | ../../pom.xml 27 | 28 | 29 | test-war-spring-boot 30 | Tomcat-Runtime :: Tests :: Spring boot tests 31 | war 32 | 33 | 34 | ${docker.image.name}:${docker.tag.long} 35 | 36 | 37 | 38 | 39 | local-docker-build 40 | 41 | true 42 | 43 | 44 | 45 | 46 | io.fabric8 47 | docker-maven-plugin 48 | 49 | 50 | build 51 | package 52 | 53 | build 54 | 55 | 56 | 57 | 58 | ${project.artifactId} 59 | 60 | ${project.build.directory}/docker-src 61 | 62 | ${project.version} 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | org.springframework.boot 80 | spring-boot-dependencies 81 | 1.5.3.RELEASE 82 | pom 83 | import 84 | 85 | 86 | 87 | 88 | 89 | 90 | org.springframework.boot 91 | spring-boot-starter-web 92 | 93 | 94 | org.springframework.boot 95 | spring-boot-starter-tomcat 96 | provided 97 | 98 | 99 | 100 | 101 | 102 | 103 | org.springframework.boot 104 | spring-boot-maven-plugin 105 | 106 | 107 | org.apache.maven.plugins 108 | maven-war-plugin 109 | 3.1.0 110 | 111 | ${project.build.directory}/docker-src 112 | 113 | 114 | 115 | maven-resources-plugin 116 | 117 | 118 | copy-dockerfile 119 | generate-resources 120 | 121 | copy-resources 122 | 123 | 124 | ${project.build.directory}/docker-src 125 | 126 | 127 | ${basedir}/src/main/docker 128 | true 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /tests/test-war-spring-boot/src/main/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | FROM ${test.docker.image} 15 | ADD ${project.build.finalName}.war $CATALINA_BASE/webapps/ROOT.war -------------------------------------------------------------------------------- /tests/test-war-spring-boot/src/main/java/com.google.cloud.runtimes/spring/Application.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.cloud.runtimes.spring; 18 | 19 | import org.springframework.boot.SpringApplication; 20 | import org.springframework.boot.autoconfigure.SpringBootApplication; 21 | import org.springframework.boot.builder.SpringApplicationBuilder; 22 | import org.springframework.boot.web.support.SpringBootServletInitializer; 23 | 24 | @SpringBootApplication 25 | public class Application extends SpringBootServletInitializer { 26 | 27 | @Override 28 | protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { 29 | return builder.sources(Application.class); 30 | } 31 | 32 | public static void main(String[] args) throws Exception { 33 | SpringApplication.run(Application.class, args); 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /tests/test-war-spring-boot/src/main/java/com.google.cloud.runtimes/spring/controllers/HelloController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.cloud.runtimes.spring.controllers; 18 | 19 | import org.springframework.web.bind.annotation.RequestMapping; 20 | import org.springframework.web.bind.annotation.RestController; 21 | 22 | @RestController 23 | public class HelloController { 24 | 25 | @RequestMapping("/") 26 | public String hello() { 27 | return "Hello World!"; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /tomcat-gcp-lib/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | tomcat-parent 7 | com.google.cloud.runtimes 8 | 0.1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | tomcat-gcp-lib 13 | 14 | 15 | 16 | org.apache.tomcat 17 | tomcat-catalina 18 | 8.5.15 19 | provided 20 | 21 | 22 | com.google.cloud 23 | google-cloud-datastore 24 | 1.2.1 25 | 26 | 27 | com.google.guava 28 | guava 29 | 23.0 30 | 31 | 32 | 34 | 35 | io.grpc 36 | grpc-all 37 | 1.4.0 38 | 39 | 41 | 42 | io.netty 43 | netty-tcnative-boringssl-static 44 | 2.0.3.Final 45 | 46 | 47 | 48 | com.google.cloud.trace 49 | core 50 | 0.4.0 51 | 52 | 53 | com.google.cloud.trace 54 | trace-grpc-api-service 55 | 0.4.0 56 | 57 | 58 | io.grpc 59 | grpc-all 60 | 61 | 62 | com.google.auth 63 | google-auth-library-oauth2-http 64 | 65 | 66 | 67 | 68 | 69 | junit 70 | junit 71 | 4.12 72 | test 73 | 74 | 75 | org.mockito 76 | mockito-inline 77 | 2.8.47 78 | test 79 | 80 | 81 | 82 | 83 | 84 | 85 | maven-assembly-plugin 86 | 3.0.0 87 | 88 | 89 | jar-with-dependencies 90 | 91 | 92 | 93 | 94 | make-assembly 95 | package 96 | 97 | single 98 | 99 | 100 | 101 | 102 | 103 | org.apache.maven.plugins 104 | maven-surefire-plugin 105 | 2.20 106 | 107 | 108 | org.apache.maven.plugins 109 | maven-compiler-plugin 110 | 3.6.1 111 | 112 | javac-with-errorprone 113 | true 114 | 1.8 115 | 1.8 116 | 117 | 118 | 119 | org.codehaus.plexus 120 | plexus-compiler-javac-errorprone 121 | 2.8.2 122 | 123 | 124 | com.google.errorprone 125 | error_prone_core 126 | 2.0.21 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | gcp-integration-test 136 | 137 | 138 | 139 | org.apache.maven.plugins 140 | maven-failsafe-plugin 141 | 2.20 142 | 143 | 144 | 145 | integration-test 146 | verify 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /tomcat-gcp-lib/src/main/java/com/google/cloud/runtimes/tomcat/session/DatastoreManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.cloud.runtimes.tomcat.session; 18 | 19 | import java.io.IOException; 20 | import java.util.Arrays; 21 | import java.util.HashSet; 22 | import java.util.Set; 23 | 24 | import org.apache.catalina.Lifecycle; 25 | import org.apache.catalina.LifecycleException; 26 | import org.apache.catalina.LifecycleState; 27 | import org.apache.catalina.Session; 28 | import org.apache.catalina.Store; 29 | import org.apache.catalina.StoreManager; 30 | import org.apache.catalina.session.ManagerBase; 31 | import org.apache.catalina.session.StandardSession; 32 | import org.apache.catalina.session.StoreBase; 33 | import org.apache.juli.logging.Log; 34 | import org.apache.juli.logging.LogFactory; 35 | 36 | 37 | /** 38 | * Implementation of the {@code org.apache.catalina.Manager} interface which uses 39 | * Google Datastore to share sessions across nodes. 40 | * 41 | *

This manager should be used in conjunction with {@link DatastoreValve} and can be used 42 | * with {@link DatastoreStore}.
43 | * Example configuration:

44 | * 45 | *
 46 |  *   {@code
 47 |  *   
 48 |  *   
 49 |  *     
 50 |  *   
 51 |  *   }
 52 |  * 
53 | * 54 | *

The sessions is never stored locally and is always fetched from the Datastore.

55 | */ 56 | public class DatastoreManager extends ManagerBase implements StoreManager { 57 | 58 | private static final Log log = LogFactory.getLog(DatastoreManager.class); 59 | 60 | /** 61 | * The store will be in charge of all the interactions with the Datastore. 62 | */ 63 | protected Store store = null; 64 | 65 | /** 66 | * {@inheritDoc} 67 | * 68 | *

Ensure that a store is present and initialized.

69 | * 70 | * @throws LifecycleException If an error occurs during the store initialization 71 | */ 72 | @Override 73 | protected synchronized void startInternal() throws LifecycleException { 74 | super.startInternal(); 75 | 76 | if (store == null) { 77 | throw new LifecycleException("No Store configured, persistence disabled"); 78 | } else if (store instanceof Lifecycle) { 79 | ((Lifecycle) store).start(); 80 | } 81 | 82 | setState(LifecycleState.STARTING); 83 | } 84 | 85 | /** 86 | * Search in the store for an existing session with the specified id. 87 | * 88 | * @param id The session id for the session to be returned 89 | * @return The request session or null if a session with the requested ID could not be found 90 | * @throws IOException If an input/output error occurs while processing this request 91 | */ 92 | @Override 93 | public Session findSession(String id) throws IOException { 94 | log.debug("Datastore manager is loading session: " + id); 95 | Session session = null; 96 | 97 | try { 98 | session = this.getStore().load(id); 99 | } catch (ClassNotFoundException ex) { 100 | log.warn("An error occurred during session deserialization", ex); 101 | } 102 | 103 | return session; 104 | } 105 | 106 | /** 107 | * {@inheritDoc} 108 | * 109 | *

Note: Sessions are loaded at each request therefore no session is loaded at 110 | * initialization.

111 | * 112 | * @throws ClassNotFoundException Cannot occurs 113 | * @throws IOException Cannot occurs 114 | */ 115 | @Override 116 | public void load() throws ClassNotFoundException, IOException {} 117 | 118 | /** 119 | * {@inheritDoc} 120 | * 121 | *

Note: Sessions are persisted after each requests but never saved into the local manager, 122 | * therefore no operation is needed during unload.

123 | * 124 | * @throws IOException Cannot occurs 125 | */ 126 | @Override 127 | public void unload() throws IOException {} 128 | 129 | /** 130 | * Remove the Session from the manager but not from the Datastore. 131 | * 132 | * @param session The session to remove. 133 | */ 134 | @Override 135 | public void removeSuper(Session session) { 136 | super.remove(session); 137 | } 138 | 139 | /** 140 | * Remove this Session from the active Sessions and the Datastore. 141 | * 142 | * @param session The session to remove. 143 | */ 144 | @Override 145 | public void remove(Session session) { 146 | this.removeSuper(session); 147 | 148 | try { 149 | store.remove(session.getId()); 150 | } catch (IOException e) { 151 | log.error("An error occurred while removing session with id: " + session.getId(), e); 152 | } 153 | } 154 | 155 | /** 156 | * Returns the number of sessions present in the Store. 157 | * 158 | *

Note: Aggregation can be slow on the Datastore, cache the result if possible

159 | * 160 | * @return the session count. 161 | */ 162 | @Override 163 | public int getActiveSessionsFull() { 164 | int sessionCount = 0; 165 | try { 166 | sessionCount = store.getSize(); 167 | } catch (IOException e) { 168 | log.error("An error occurred while counting sessions: ", e); 169 | } 170 | 171 | return sessionCount; 172 | } 173 | 174 | /** 175 | * Returns a set of all sessions IDs or null if an error occurs. 176 | * 177 | *

Note: Listing all the keys can be slow on the Datastore.

178 | * 179 | * @return The complete set of sessions IDs across the cluster. 180 | */ 181 | @Override 182 | public Set getSessionIdsFull() { 183 | Set sessionsId = null; 184 | try { 185 | String[] keys = this.store.keys(); 186 | sessionsId = new HashSet<>(Arrays.asList(keys)); 187 | } catch (IOException e) { 188 | log.error("An error occurred while listing active sessions: ", e); 189 | } 190 | 191 | return sessionsId; 192 | } 193 | 194 | @Override 195 | protected void stopInternal() throws LifecycleException { 196 | super.stopInternal(); 197 | 198 | if (store instanceof Lifecycle) { 199 | ((Lifecycle) store).stop(); 200 | } 201 | 202 | setState(LifecycleState.STOPPING); 203 | } 204 | 205 | @Override 206 | public void processExpires() { 207 | log.debug("Processing expired sessions"); 208 | if (store instanceof StoreBase) { 209 | ((StoreBase) store).processExpires(); 210 | } 211 | } 212 | 213 | @Override 214 | protected StandardSession getNewSession() { 215 | return new DatastoreSession(this); 216 | } 217 | 218 | public Store getStore() { 219 | return this.store; 220 | } 221 | 222 | /** 223 | * The store will be injected by Tomcat on startup. 224 | * 225 | *

See distributed-sessions.xml for the configuration.

226 | */ 227 | public void setStore(Store store) { 228 | this.store = store; 229 | store.setManager(this); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /tomcat-gcp-lib/src/main/java/com/google/cloud/runtimes/tomcat/session/DatastoreSession.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.cloud.runtimes.tomcat.session; 18 | 19 | import com.google.cloud.datastore.Blob; 20 | import com.google.cloud.datastore.BlobValue; 21 | import com.google.cloud.datastore.Entity; 22 | import com.google.cloud.datastore.Key; 23 | import com.google.cloud.datastore.KeyFactory; 24 | import com.google.common.annotations.VisibleForTesting; 25 | import java.io.ByteArrayOutputStream; 26 | import java.io.IOException; 27 | import java.io.InputStream; 28 | import java.io.ObjectInputStream; 29 | import java.io.ObjectOutputStream; 30 | import java.io.UncheckedIOException; 31 | import java.util.Collections; 32 | import java.util.HashSet; 33 | import java.util.LinkedList; 34 | import java.util.List; 35 | import java.util.Set; 36 | import java.util.stream.Collectors; 37 | import java.util.stream.Stream; 38 | import org.apache.catalina.Manager; 39 | import org.apache.catalina.session.StandardSession; 40 | 41 | /** 42 | * A DatastoreSession have the same behavior as a standard session but provide utilities to interact 43 | * with the Datastore, such as helper for attributes and metadata serialization. 44 | */ 45 | public class DatastoreSession extends StandardSession { 46 | 47 | protected Set accessedAttributes; 48 | protected Set initialAttributes; 49 | 50 | @VisibleForTesting 51 | class SessionMetadata { 52 | public static final String CREATION_TIME = "creationTime"; 53 | public static final String LAST_ACCESSED_TIME = "lastAccessedTime"; 54 | public static final String MAX_INACTIVE_INTERVAL = "maxInactiveInterval"; 55 | public static final String IS_NEW = "isNew"; 56 | public static final String IS_VALID = "isValid"; 57 | public static final String THIS_ACCESSED_TIME = "thisAccessedTime"; 58 | public static final String EXPIRATION_TIME = "expirationTime"; 59 | public static final String ATTRIBUTE_VALUE_NAME = "value"; 60 | } 61 | 62 | /** 63 | * Create a new session which can be stored in the Datastore. 64 | * @param manager The session manager which manage this session. 65 | */ 66 | public DatastoreSession(Manager manager) { 67 | super(manager); 68 | this.accessedAttributes = new HashSet<>(); 69 | this.initialAttributes = new HashSet<>(); 70 | } 71 | 72 | /** 73 | * Restore the attributes and metadata of the session from Datastore Entities. 74 | * 75 | * @param entities An iterator of entity, containing the metadata and attributes of the session. 76 | * @throws ClassNotFoundException The class in attempt to be deserialized is not present in the 77 | * application. 78 | * @throws IOException Error during the deserialization of the object. 79 | */ 80 | public void restoreFromEntities(Key sessionKey, Iterable entities) throws 81 | ClassNotFoundException, IOException { 82 | Entity metadataEntity = null; 83 | List attributeEntities = new LinkedList<>(); 84 | for (Entity entity : entities) { 85 | if (entity.getKey().equals(sessionKey)) { 86 | metadataEntity = entity; 87 | } else { 88 | attributeEntities.add(entity); 89 | } 90 | } 91 | 92 | if (metadataEntity == null) { 93 | throw new IOException("The serialized session is missing the metadata entity"); 94 | } 95 | 96 | restoreMetadataFromEntity(metadataEntity); 97 | restoreAttributesFromEntity(attributeEntities); 98 | setId(sessionKey.getName()); 99 | initialAttributes.addAll(Collections.list(getAttributeNames())); 100 | } 101 | 102 | /** 103 | * Restore the metadata of a session with the values contains in the entity. 104 | * @param metadata An entity containing the metadata to restore 105 | */ 106 | private void restoreMetadataFromEntity(Entity metadata) { 107 | creationTime = metadata.getLong(SessionMetadata.CREATION_TIME); 108 | lastAccessedTime = metadata.getLong(SessionMetadata.LAST_ACCESSED_TIME); 109 | maxInactiveInterval = (int) metadata.getLong(SessionMetadata.MAX_INACTIVE_INTERVAL); 110 | isNew = metadata.getBoolean(SessionMetadata.IS_NEW); 111 | isValid = metadata.getBoolean(SessionMetadata.IS_VALID); 112 | thisAccessedTime = metadata.getLong(SessionMetadata.THIS_ACCESSED_TIME); 113 | } 114 | 115 | /** 116 | * Deserialize the content of each entity and add them as attribute of the session. 117 | * @param entities The entities containing the serialized attributes. 118 | * @throws IOException If an error occur during the deserialization 119 | * @throws ClassNotFoundException If the class being deserialized is not present in this program. 120 | */ 121 | private void restoreAttributesFromEntity(Iterable entities) throws IOException, 122 | ClassNotFoundException { 123 | for (Entity entity : entities) { 124 | String name = entity.getKey().getName(); 125 | Blob value = entity.getBlob(SessionMetadata.ATTRIBUTE_VALUE_NAME); 126 | try (InputStream fis = value.asInputStream(); 127 | ObjectInputStream ois = new ObjectInputStream(fis)) { 128 | Object attribute = ois.readObject(); 129 | setAttribute(name, attribute, false); 130 | } 131 | } 132 | } 133 | 134 | /** 135 | * Serialize the session metadata and attributes into entities storable in the datastore. 136 | * @param sessionKey The key of the serialized session 137 | * @param attributeKeyFactory A key factory containing sessionKey in its ancestors, used to 138 | * generate the key for the attributes. 139 | * @return A list of entities containing the metadata and each attribute. 140 | * @throws IOException If an error occur during the serialization. 141 | */ 142 | public List saveToEntities(Key sessionKey, KeyFactory attributeKeyFactory) throws 143 | IOException { 144 | List entities = saveAttributesToEntity(attributeKeyFactory); 145 | entities.add(saveMetadataToEntity(sessionKey)); 146 | return entities; 147 | } 148 | 149 | /** 150 | * Store the metadata of the session in an entity. 151 | * @param sessionKey Identifier of the session on the Datastore 152 | * @return An entity containing the metadata. 153 | */ 154 | @VisibleForTesting 155 | Entity saveMetadataToEntity(Key sessionKey) { 156 | Entity.Builder sessionEntity = Entity.newBuilder(sessionKey) 157 | .set(SessionMetadata.CREATION_TIME, getCreationTime()) 158 | .set(SessionMetadata.LAST_ACCESSED_TIME, getLastAccessedTime()) 159 | .set(SessionMetadata.MAX_INACTIVE_INTERVAL, getMaxInactiveInterval()) 160 | .set(SessionMetadata.IS_NEW, isNew()) 161 | .set(SessionMetadata.IS_VALID, isValid()) 162 | .set(SessionMetadata.THIS_ACCESSED_TIME, getThisAccessedTime()); 163 | 164 | // A negative time indicates that the session should never time out 165 | if (getMaxInactiveInterval() >= 0) { 166 | sessionEntity.set(SessionMetadata.EXPIRATION_TIME, 167 | getLastAccessedTime() + getMaxInactiveInterval() * 1000); 168 | } 169 | 170 | return sessionEntity.build(); 171 | } 172 | 173 | /** 174 | * Serialize the session attributes into entities. 175 | * @param attributeKeyFactory The key builder for the entities. 176 | * @return A list of entities where the key correspond to the name of the attribute 177 | and the property `value` to the serialized attribute. 178 | * @throws IOException If an error occur during the serialization. 179 | */ 180 | @VisibleForTesting 181 | List saveAttributesToEntity(KeyFactory attributeKeyFactory) throws 182 | IOException { 183 | Stream entities = Collections.list(getAttributeNames()).stream() 184 | .filter(name -> accessedAttributes.contains(name)) 185 | .filter(name -> isAttributeDistributable(name, getAttribute(name))) 186 | .map(name -> serializeAttribute(attributeKeyFactory, name)); 187 | 188 | try { 189 | return entities.collect(Collectors.toList()); 190 | } catch (UncheckedIOException e) { 191 | throw e.getCause(); 192 | } 193 | } 194 | 195 | /** 196 | * Serialize an attribute an embed it into an entity whose key is generated by the provided 197 | * KeyFactory. 198 | * @param attributeKeyFactory The KeyFactory to use to create the key for the entity. 199 | * @param name The name of the attribute to serialize. 200 | * @return An Entity containing the serialized attribute. 201 | */ 202 | private Entity serializeAttribute(KeyFactory attributeKeyFactory, String name) { 203 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 204 | try (ObjectOutputStream oos = new ObjectOutputStream(bos)) { 205 | oos.writeObject(getAttribute(name)); 206 | } catch (IOException e) { 207 | throw new UncheckedIOException(e); 208 | } 209 | 210 | return Entity.newBuilder(attributeKeyFactory.newKey(name)) 211 | .set(SessionMetadata.ATTRIBUTE_VALUE_NAME, 212 | BlobValue.newBuilder(Blob.copyFrom(bos.toByteArray())) 213 | .setExcludeFromIndexes(true) 214 | .build()) 215 | .build(); 216 | } 217 | 218 | /** 219 | * List the attributes that were present at the beginning of the request and suppressed during 220 | * its execution. This is used to reflect the suppression of attributes in the Datastore (The 221 | * suppressed attributes would be left unchanged in the Datastore otherwise). 222 | * @return A set of the suppressed attributes. 223 | */ 224 | public Set getSuppressedAttributes() { 225 | Set suppressedAttribute = new HashSet<>(initialAttributes); 226 | suppressedAttribute.removeAll(Collections.list(getAttributeNames())); 227 | return suppressedAttribute; 228 | } 229 | 230 | @Override 231 | public Object getAttribute(String name) { 232 | accessedAttributes.add(name); 233 | return super.getAttribute(name); 234 | } 235 | 236 | @Override 237 | public void setAttribute(String name, Object value, boolean notify) { 238 | super.setAttribute(name, value, notify); 239 | if (notify) { 240 | accessedAttributes.add(name); 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /tomcat-gcp-lib/src/main/java/com/google/cloud/runtimes/tomcat/session/DatastoreStore.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.cloud.runtimes.tomcat.session; 18 | 19 | import com.google.cloud.datastore.Datastore; 20 | import com.google.cloud.datastore.DatastoreOptions; 21 | import com.google.cloud.datastore.Entity; 22 | import com.google.cloud.datastore.FullEntity; 23 | import com.google.cloud.datastore.Key; 24 | import com.google.cloud.datastore.KeyFactory; 25 | import com.google.cloud.datastore.PathElement; 26 | import com.google.cloud.datastore.Query; 27 | import com.google.cloud.datastore.QueryResults; 28 | import com.google.cloud.datastore.StructuredQuery.PropertyFilter; 29 | import com.google.cloud.runtimes.tomcat.session.DatastoreSession.SessionMetadata; 30 | import com.google.cloud.trace.Trace; 31 | import com.google.cloud.trace.Tracer; 32 | import com.google.cloud.trace.core.TraceContext; 33 | import com.google.common.annotations.VisibleForTesting; 34 | import com.google.common.collect.Lists; 35 | import com.google.common.collect.Streams; 36 | 37 | import java.io.IOException; 38 | import java.time.Clock; 39 | import java.util.Arrays; 40 | 41 | import java.util.Iterator; 42 | import java.util.List; 43 | import java.util.stream.Stream; 44 | import org.apache.catalina.LifecycleException; 45 | import org.apache.catalina.Session; 46 | import org.apache.catalina.session.StoreBase; 47 | import org.apache.juli.logging.Log; 48 | import org.apache.juli.logging.LogFactory; 49 | 50 | 51 | /** 52 | * This store interacts with the Datastore service to persist sessions. 53 | * 54 | *

It does not make any assumptions about the manager, so it could be used 55 | * by all manager implementations.

56 | * 57 | *

However, aggregations can be slow on the Datastore. So, if performance is a concern, prefer 58 | * using a manager implementation which is not using aggregations such as 59 | * {@link DatastoreManager}

60 | */ 61 | public class DatastoreStore extends StoreBase { 62 | 63 | private static final Log log = LogFactory.getLog(DatastoreStore.class); 64 | 65 | private Datastore datastore = null; 66 | 67 | /** 68 | * Name of the kind used in The Datastore for the session. 69 | */ 70 | private String sessionKind; 71 | 72 | /** 73 | * Namespace to use in the Datastore. 74 | */ 75 | private String namespace; 76 | 77 | /** 78 | * Whether or not to send traces to Stackdriver for the operations related to session persistence. 79 | */ 80 | private boolean traceRequest = false; 81 | 82 | private Clock clock; 83 | 84 | /** 85 | * {@inheritDoc} 86 | * 87 | *

Initiate a connection to the Datastore.

88 | * 89 | */ 90 | @Override 91 | protected synchronized void startInternal() throws LifecycleException { 92 | log.debug("Initialization of the Datastore Store"); 93 | 94 | this.clock = Clock.systemUTC(); 95 | this.datastore = DatastoreOptions.newBuilder().setNamespace(namespace).build().getService(); 96 | 97 | super.startInternal(); 98 | } 99 | 100 | private Key newKey(String name) { 101 | return datastore.newKeyFactory().setKind(sessionKind).newKey(name); 102 | } 103 | 104 | /** 105 | * Return the number of Sessions present in this Store. 106 | * 107 | *

The Datastore does not support counting elements in a collection. 108 | * So, all keys are fetched and the counted locally.

109 | * 110 | *

This method may be slow if a large number of sessions are persisted, 111 | * prefer operations on individual entities rather than aggregations.

112 | * 113 | * @return The number of sessions stored into the Datastore 114 | */ 115 | @Override 116 | public int getSize() throws IOException { 117 | log.debug("Accessing sessions count, be cautious this operation can cause performance issues"); 118 | Query query = Query.newKeyQueryBuilder().build(); 119 | QueryResults results = datastore.run(query); 120 | long count = Streams.stream(results).count(); 121 | return Math.toIntExact(count); 122 | } 123 | 124 | /** 125 | * Returns an array containing the session identifiers of all Sessions currently saved in this 126 | * Store. If there are no such Sessions, a zero-length array is returned. 127 | * 128 | *

This operation may be slow if a large number of sessions is persisted. 129 | * Note that the number of keys returned may be bounded by the Datastore configuration.

130 | * 131 | * @return The ids of all persisted sessions 132 | */ 133 | @Override 134 | public String[] keys() throws IOException { 135 | String[] keys; 136 | 137 | Query query = Query.newKeyQueryBuilder().build(); 138 | QueryResults results = datastore.run(query); 139 | keys = Streams.stream(results) 140 | .map(key -> key.getNameOrId().toString()) 141 | .toArray(String[]::new); 142 | 143 | if (keys == null) { 144 | keys = new String[0]; 145 | } 146 | 147 | return keys; 148 | } 149 | 150 | /** 151 | * Load and return the Session associated with the specified session identifier from this Store, 152 | * without removing it. If there is no such stored Session, return null. 153 | * 154 | *

Look in the Datastore for a serialized session and attempt to deserialize it.

155 | * 156 | *

If the session is successfully deserialized, it is added to the current manager and is 157 | * returned by this method. Otherwise null is returned.

158 | * 159 | * @param id Session identifier of the session to load 160 | * @return The loaded session instance 161 | * @throws ClassNotFoundException If a deserialization error occurs 162 | */ 163 | @Override 164 | public Session load(String id) throws ClassNotFoundException, IOException { 165 | log.debug("Session " + id + " requested"); 166 | TraceContext context = startSpan("Loading session"); 167 | Key sessionKey = newKey(id); 168 | 169 | DatastoreSession session = deserializeSession(sessionKey); 170 | 171 | endSpan(context); 172 | log.debug("Session " + id + " loaded"); 173 | return session; 174 | } 175 | 176 | /** 177 | * Create a new session usable by Tomcat, from a serialized session in a Datastore Entity. 178 | * @param sessionKey The key associated with the session metadata and attributes. 179 | * @return A new session containing the metadata and attributes stored in the entity. 180 | * @throws ClassNotFoundException Thrown if a class serialized in the entity is not available in 181 | * this context. 182 | * @throws IOException Thrown when an error occur during the deserialization. 183 | */ 184 | private DatastoreSession deserializeSession(Key sessionKey) 185 | throws ClassNotFoundException, IOException { 186 | TraceContext loadingSessionContext = startSpan("Fetching the session from Datastore"); 187 | Iterator entities = datastore.run(Query.newEntityQueryBuilder() 188 | .setKind(sessionKind) 189 | .setFilter(PropertyFilter.hasAncestor(sessionKey)) 190 | .build()); 191 | endSpan(loadingSessionContext); 192 | 193 | DatastoreSession session = null; 194 | if (entities.hasNext()) { 195 | session = (DatastoreSession) manager.createEmptySession(); 196 | TraceContext deserializationContext = startSpan("Deserialization of the session"); 197 | session.restoreFromEntities(sessionKey, Lists.newArrayList(entities)); 198 | endSpan(deserializationContext); 199 | } 200 | return session; 201 | } 202 | 203 | /** 204 | * Remove the Session with the specified session identifier from this Store. 205 | * If no such Session is present, this method takes no action. 206 | * 207 | * @param id Session identifier of the session to remove 208 | */ 209 | @Override 210 | public void remove(String id) { 211 | log.debug("Removing session: " + id); 212 | datastore.delete(newKey(id)); 213 | } 214 | 215 | /** 216 | * Remove all Sessions from this Store. 217 | */ 218 | @Override 219 | public void clear() throws IOException { 220 | log.debug("Deleting all sessions"); 221 | datastore.delete(Arrays.stream(keys()) 222 | .map(this::newKey) 223 | .toArray(Key[]::new)); 224 | } 225 | 226 | /** 227 | * Save the specified Session into this Store. Any previously saved information for 228 | * the associated session identifier is replaced. 229 | * 230 | *

Attempt to serialize the session and send it to the datastore.

231 | * 232 | * @throws IOException If an error occurs during the serialization of the session. 233 | * 234 | * @param session Session to be saved 235 | */ 236 | @Override 237 | public void save(Session session) throws IOException { 238 | log.debug("Persisting session: " + session.getId()); 239 | 240 | if (!(session instanceof DatastoreSession)) { 241 | throw new IOException( 242 | "The session must be an instance of DatastoreSession to be serialized"); 243 | } 244 | DatastoreSession datastoreSession = (DatastoreSession) session; 245 | Key sessionKey = newKey(session.getId()); 246 | KeyFactory attributeKeyFactory = datastore.newKeyFactory() 247 | .setKind(sessionKind) 248 | .addAncestor(PathElement.of(sessionKind, sessionKey.getName())); 249 | 250 | List entities = serializeSession(datastoreSession, sessionKey, attributeKeyFactory); 251 | 252 | TraceContext datastoreSaveContext = startSpan("Storing the session in the Datastore"); 253 | datastore.put(entities.toArray(new FullEntity[0])); 254 | datastore.delete(datastoreSession.getSuppressedAttributes().stream() 255 | .map(attributeKeyFactory::newKey) 256 | .toArray(Key[]::new)); 257 | endSpan(datastoreSaveContext); 258 | } 259 | 260 | /** 261 | * Serialize a session to a list of Entities that can be stored to the Datastore. 262 | * @param session The session to serialize. 263 | * @return A list of one or more entities containing the session and its attributes. 264 | * @throws IOException If the session cannot be serialized. 265 | */ 266 | @VisibleForTesting 267 | List serializeSession(DatastoreSession session, Key sessionKey, 268 | KeyFactory attributeKeyFactory) throws IOException { 269 | TraceContext serializationContext = startSpan("Serialization of the session"); 270 | List entities = session.saveToEntities(sessionKey, attributeKeyFactory); 271 | endSpan(serializationContext); 272 | return entities; 273 | } 274 | 275 | /** 276 | * Remove expired sessions from the datastore. 277 | */ 278 | @Override 279 | public void processExpires() { 280 | log.debug("Processing expired sessions"); 281 | 282 | Query query = Query.newKeyQueryBuilder().setKind(sessionKind) 283 | .setFilter(PropertyFilter.le(SessionMetadata.EXPIRATION_TIME, 284 | clock.millis())) 285 | .build(); 286 | 287 | QueryResults keys = datastore.run(query); 288 | 289 | Stream toDelete = Streams.stream(keys) 290 | .parallel() 291 | .flatMap(key -> Streams.stream(datastore.run(Query.newKeyQueryBuilder() 292 | .setKind(sessionKind) 293 | .setFilter(PropertyFilter.hasAncestor(newKey(key.getName()))) 294 | .build()))); 295 | datastore.delete(toDelete.toArray(Key[]::new)); 296 | } 297 | 298 | @VisibleForTesting 299 | TraceContext startSpan(String spanName) { 300 | TraceContext context = null; 301 | if (traceRequest) { 302 | context = Trace.getTracer().startSpan(spanName); 303 | } 304 | return context; 305 | } 306 | 307 | @VisibleForTesting 308 | private void endSpan(TraceContext context) { 309 | if (context != null) { 310 | Tracer tracer = Trace.getTracer(); 311 | tracer.endSpan(context); 312 | } 313 | } 314 | 315 | /** 316 | * This property will be injected by Tomcat on startup. 317 | * 318 | *

See context.xml and catalina.properties for the default values

319 | */ 320 | public void setNamespace(String namespace) { 321 | this.namespace = namespace; 322 | } 323 | 324 | /** 325 | * This property will be injected by Tomcat on startup. 326 | */ 327 | public void setSessionKind(String sessionKind) { 328 | this.sessionKind = sessionKind; 329 | } 330 | 331 | /** 332 | * This property will be injected by Tomcat on startup. 333 | */ 334 | public void setTraceRequest(boolean traceRequest) { 335 | this.traceRequest = traceRequest; 336 | } 337 | 338 | @VisibleForTesting 339 | void setDatastore(Datastore datastore) { 340 | this.datastore = datastore; 341 | } 342 | 343 | @VisibleForTesting 344 | void setClock(Clock clock) { 345 | this.clock = clock; 346 | } 347 | 348 | } 349 | -------------------------------------------------------------------------------- /tomcat-gcp-lib/src/main/java/com/google/cloud/runtimes/tomcat/session/DatastoreValve.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.cloud.runtimes.tomcat.session; 18 | 19 | import java.io.IOException; 20 | import java.util.regex.Pattern; 21 | import javax.servlet.ServletException; 22 | 23 | import org.apache.catalina.Context; 24 | import org.apache.catalina.Manager; 25 | import org.apache.catalina.Session; 26 | import org.apache.catalina.StoreManager; 27 | import org.apache.catalina.connector.Request; 28 | import org.apache.catalina.connector.Response; 29 | import org.apache.catalina.valves.ValveBase; 30 | import org.apache.juli.logging.Log; 31 | import org.apache.juli.logging.LogFactory; 32 | 33 | 34 | /** 35 | * This valve uses the Store Manager to persist the session after each request. 36 | */ 37 | public class DatastoreValve extends ValveBase { 38 | 39 | private static final Log log = LogFactory.getLog(DatastoreValve.class); 40 | 41 | private String uriExcludePattern; 42 | 43 | /** 44 | * {@inheritDoc} 45 | * 46 | *

If the manager contain a store, use it to persist the session at the end of the request.

47 | */ 48 | @Override 49 | public void invoke(Request request, Response response) throws IOException, ServletException { 50 | 51 | log.debug("Processing request with session:" + request.getRequestedSessionId()); 52 | 53 | getNext().invoke(request, response); 54 | 55 | Context context = request.getContext(); 56 | Manager manager = context.getManager(); 57 | 58 | Session session = request.getSessionInternal(false); 59 | if (session != null && !isUriExcluded(request.getRequestURI())) { 60 | log.debug("Persisting session with id: " + session.getId()); 61 | session.access(); 62 | session.endAccess(); 63 | 64 | if (manager instanceof StoreManager) { 65 | StoreManager storeManager = (StoreManager) manager; 66 | storeManager.getStore().save(session); 67 | storeManager.removeSuper(session); 68 | } else { 69 | log.error("In order to persist the session the manager must implement StoreManager"); 70 | } 71 | } else { 72 | log.debug("Session not persisted (Non existent or the URI is ignored)"); 73 | } 74 | 75 | } 76 | 77 | /** 78 | * Verify if the specified URI should be ignored for session persistence. 79 | * @param uri The URI of the request 80 | * @return Whether the URI should be ignored or not 81 | */ 82 | private boolean isUriExcluded(String uri) { 83 | return uriExcludePattern != null && Pattern.matches(uriExcludePattern, uri); 84 | } 85 | 86 | /** 87 | * This property will be injected by Tomcat on startup. 88 | */ 89 | public void setUriExcludePattern(String uriExcludePattern) { 90 | this.uriExcludePattern = uriExcludePattern; 91 | } 92 | } -------------------------------------------------------------------------------- /tomcat-gcp-lib/src/main/java/com/google/cloud/runtimes/tomcat/trace/HttpLabels.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.cloud.runtimes.tomcat.trace; 18 | 19 | public enum HttpLabels { 20 | HTTP_REQUEST_SIZE("/http/request/size"), 21 | HTTP_RESPONSE_SIZE("/http/response/size"), 22 | HTTP_METHOD("/http/method"), 23 | HTTP_STATUS_CODE("/http/status_code"), 24 | HTTP_URL("/http/url"), 25 | HTTP_USER_AGENT("/http/user_agent"), 26 | HTTP_CLIENT_PROTOCOL("/http/client_protocol"); 27 | 28 | private final String value; 29 | 30 | HttpLabels(String value) { 31 | this.value = value; 32 | } 33 | 34 | public String getValue() { 35 | return this.value; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tomcat-gcp-lib/src/main/java/com/google/cloud/runtimes/tomcat/trace/TraceValve.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.cloud.runtimes.tomcat.trace; 18 | 19 | import com.google.cloud.ServiceOptions; 20 | import com.google.cloud.trace.Trace; 21 | import com.google.cloud.trace.Tracer; 22 | import com.google.cloud.trace.core.Labels; 23 | import com.google.cloud.trace.core.SpanContext; 24 | import com.google.cloud.trace.core.SpanContextFactory; 25 | import com.google.cloud.trace.core.SpanContextHandle; 26 | import com.google.cloud.trace.core.TraceContext; 27 | import com.google.cloud.trace.service.TraceGrpcApiService; 28 | import com.google.cloud.trace.service.TraceService; 29 | import com.google.common.annotations.VisibleForTesting; 30 | import com.google.common.net.HttpHeaders; 31 | 32 | import java.io.IOException; 33 | import javax.servlet.ServletException; 34 | 35 | import org.apache.catalina.LifecycleException; 36 | import org.apache.catalina.connector.Request; 37 | import org.apache.catalina.connector.Response; 38 | import org.apache.catalina.valves.ValveBase; 39 | import org.apache.juli.logging.Log; 40 | import org.apache.juli.logging.LogFactory; 41 | 42 | 43 | /** 44 | * This valve sends information about the requests to the Stackdriver Trace service. 45 | */ 46 | public class TraceValve extends ValveBase { 47 | 48 | /** 49 | * Header used by GCP to stores the trace id and the span id. 50 | */ 51 | private static final String X_CLOUD_TRACE_HEADER = SpanContextFactory.headerKey(); 52 | 53 | private static final Log log = LogFactory.getLog(TraceValve.class); 54 | 55 | private TraceService traceService; 56 | 57 | /** 58 | * Delay in second before the trace scheduler send the traces (allow buffering of traces). 59 | */ 60 | private Integer traceScheduledDelay; 61 | 62 | /** 63 | * {@inheritDoc} 64 | * 65 | *

Initialize the Trace service.

66 | */ 67 | @Override 68 | protected void initInternal() throws LifecycleException { 69 | super.initInternal(); 70 | initTraceService(); 71 | } 72 | 73 | @VisibleForTesting 74 | TraceGrpcApiService.Builder getTraceService() { 75 | return TraceGrpcApiService.builder(); 76 | } 77 | 78 | @VisibleForTesting 79 | void initTraceService() throws LifecycleException { 80 | 81 | if (traceScheduledDelay != null && traceScheduledDelay <= 0) { 82 | throw new LifecycleException("The delay for trace must be greater than 0"); 83 | } 84 | 85 | try { 86 | String projectId = ServiceOptions.getDefaultProjectId(); 87 | TraceGrpcApiService.Builder traceServiceBuilder = getTraceService() 88 | .setProjectId(projectId); 89 | 90 | if (traceScheduledDelay != null) { 91 | traceServiceBuilder.setScheduledDelay(traceScheduledDelay); 92 | } 93 | 94 | traceService = traceServiceBuilder.build(); 95 | Trace.init(traceService); 96 | log.info("Trace service initialized for project: " + projectId); 97 | } catch (IOException e) { 98 | throw new LifecycleException(e); 99 | } 100 | } 101 | 102 | /** 103 | * {@inheritDoc} 104 | * 105 | *

Create a new trace containing information about the requests. 106 | * The traces are buffered by the TraceService and regularly sent to Stackdriver

107 | */ 108 | @Override 109 | public void invoke(Request request, Response response) throws IOException, ServletException { 110 | Tracer tracer = traceService.getTracer(); 111 | SpanContextHandle contextHandle = null; 112 | 113 | String traceHeader = request.getHeader(X_CLOUD_TRACE_HEADER); 114 | if (traceHeader != null) { 115 | SpanContext spanContext = traceService 116 | .getSpanContextFactory() 117 | .fromHeader(traceHeader); 118 | contextHandle = traceService.getSpanContextHandler().attach(spanContext); 119 | log.debug("Tracing request with header: " + request.getHeader(X_CLOUD_TRACE_HEADER)); 120 | } 121 | 122 | TraceContext context = tracer.startSpan(request.getRequestURI()); 123 | 124 | getNext().invoke(request, response); 125 | 126 | tracer.annotateSpan(context, createLabels(request, response)); 127 | 128 | tracer.endSpan(context); 129 | if (contextHandle != null) { 130 | contextHandle.detach(); 131 | } 132 | 133 | } 134 | 135 | /** 136 | * Create labels for Stackdriver trace with basic response and request info. 137 | */ 138 | private Labels createLabels(Request request, Response response) { 139 | Labels.Builder labels = Labels.builder(); 140 | this.annotateIfNotEmpty(labels, HttpLabels.HTTP_METHOD.getValue(), request.getMethod()); 141 | this.annotateIfNotEmpty(labels, HttpLabels.HTTP_URL.getValue(), request.getRequestURI()); 142 | this.annotateIfNotEmpty(labels, HttpLabels.HTTP_CLIENT_PROTOCOL.getValue(), 143 | request.getProtocol()); 144 | this.annotateIfNotEmpty(labels, HttpLabels.HTTP_USER_AGENT.getValue(), 145 | request.getHeader(HttpHeaders.USER_AGENT)); 146 | this.annotateIfNotEmpty(labels, HttpLabels.HTTP_REQUEST_SIZE.getValue(), 147 | request.getHeader(HttpHeaders.CONTENT_LENGTH)); 148 | this.annotateIfNotEmpty(labels, HttpLabels.HTTP_RESPONSE_SIZE.getValue(), 149 | response.getHeader(HttpHeaders.CONTENT_LENGTH)); 150 | labels.add(HttpLabels.HTTP_STATUS_CODE.getValue(), Integer.toString(response.getStatus())); 151 | return labels.build(); 152 | } 153 | 154 | /** 155 | * Make sure that only labels with actual values are added. 156 | */ 157 | private void annotateIfNotEmpty(Labels.Builder labels, String key, String value) { 158 | if (value != null && value.length() > 0) { 159 | labels.add(key, value); 160 | } 161 | } 162 | 163 | /** 164 | * This property will be injected by Tomcat on startup. 165 | */ 166 | public void setTraceScheduledDelay(Integer traceScheduledDelay) { 167 | this.traceScheduledDelay = traceScheduledDelay; 168 | } 169 | 170 | public void setTraceService(TraceService traceService) { 171 | this.traceService = traceService; 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /tomcat-gcp-lib/src/test/java/com/google/cloud/runtimes/tomcat/session/DatastoreManagerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.cloud.runtimes.tomcat.session; 18 | 19 | import static org.junit.Assert.assertEquals; 20 | import static org.junit.Assert.assertFalse; 21 | import static org.junit.Assert.assertNull; 22 | import static org.junit.Assert.assertTrue; 23 | import static org.mockito.ArgumentMatchers.anyString; 24 | import static org.mockito.Mockito.never; 25 | import static org.mockito.Mockito.verify; 26 | import static org.mockito.Mockito.when; 27 | 28 | import java.io.IOException; 29 | import java.util.Arrays; 30 | import org.apache.catalina.Context; 31 | import org.apache.catalina.Session; 32 | import org.apache.catalina.Store; 33 | import org.junit.Before; 34 | import org.junit.Test; 35 | import org.mockito.Mock; 36 | import org.mockito.MockitoAnnotations; 37 | 38 | /** 39 | * 40 | * Ensures that the operations of {@link DatastoreManager} are correctly delegated to the 41 | * corresponding {@link org.apache.catalina.Store} instance. 42 | * 43 | * Also tests operations on local sessions. 44 | */ 45 | public class DatastoreManagerTest { 46 | 47 | @Mock 48 | private Store store; 49 | 50 | @Mock 51 | private Session session; 52 | 53 | @Mock 54 | private Context context; 55 | 56 | private DatastoreManager manager; 57 | 58 | @Before 59 | public void setUp() throws Exception { 60 | MockitoAnnotations.initMocks(this); 61 | 62 | when(session.getId()).thenReturn("123"); 63 | when(session.getIdInternal()).thenReturn("123"); 64 | 65 | manager = new DatastoreManager(); 66 | manager.setStore(store); 67 | manager.setContext(context); 68 | } 69 | 70 | 71 | @Test 72 | public void testFindSession() throws IOException, ClassNotFoundException { 73 | when(store.load(anyString())).thenReturn(session); 74 | 75 | Session loaded = manager.findSession("123"); 76 | assertEquals(session, loaded); 77 | verify(store).load("123"); 78 | } 79 | 80 | @Test 81 | public void testFindNonExistingSession() throws IOException { 82 | Session loaded = manager.findSession("123"); 83 | assertNull(loaded); 84 | } 85 | 86 | @Test 87 | public void testLocalSessionRemoval() throws IOException { 88 | manager.add(session); 89 | assertTrue(Arrays.asList(manager.findSessions()).contains(session)); 90 | 91 | manager.removeSuper(session); 92 | assertFalse(Arrays.asList(manager.findSessions()).contains(session)); 93 | verify(store, never()).remove(anyString()); 94 | } 95 | 96 | @Test 97 | public void testRemoteSessionRemoval() throws IOException { 98 | manager.remove(session); 99 | verify(store).remove("123"); 100 | } 101 | 102 | @Test 103 | public void testCountOfActiveSession() throws IOException { 104 | manager.getActiveSessionsFull(); 105 | verify(store).getSize(); 106 | } 107 | 108 | @Test 109 | public void testListOfActiveSessions() throws IOException { 110 | when(store.keys()).thenReturn(new String[0]); 111 | manager.getSessionIdsFull(); 112 | verify(store).keys(); 113 | } 114 | 115 | } -------------------------------------------------------------------------------- /tomcat-gcp-lib/src/test/java/com/google/cloud/runtimes/tomcat/session/DatastoreSessionTest.java: -------------------------------------------------------------------------------- 1 | package com.google.cloud.runtimes.tomcat.session; 2 | 3 | import static org.junit.Assert.*; 4 | import static org.mockito.ArgumentMatchers.any; 5 | import static org.mockito.Mockito.mock; 6 | import static org.mockito.Mockito.spy; 7 | import static org.mockito.Mockito.when; 8 | 9 | import com.google.cloud.datastore.BaseEntity; 10 | import com.google.cloud.datastore.Blob; 11 | import com.google.cloud.datastore.Entity; 12 | import com.google.cloud.datastore.Key; 13 | import com.google.cloud.datastore.KeyFactory; 14 | import com.google.cloud.runtimes.tomcat.session.DatastoreSession.SessionMetadata; 15 | import java.io.ByteArrayOutputStream; 16 | import java.io.IOException; 17 | import java.io.NotSerializableException; 18 | import java.io.ObjectOutputStream; 19 | import java.util.Arrays; 20 | import java.util.Collections; 21 | import java.util.HashMap; 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.stream.Collectors; 25 | import org.apache.catalina.Context; 26 | import org.apache.catalina.Manager; 27 | import org.junit.Before; 28 | import org.junit.Test; 29 | import org.mockito.Mock; 30 | import org.mockito.MockitoAnnotations; 31 | 32 | public class DatastoreSessionTest { 33 | 34 | @Mock 35 | private Manager sessionManager; 36 | 37 | @Mock 38 | private Context managerContext; 39 | 40 | @Mock 41 | private Key sessionKey; 42 | 43 | @Before 44 | public void setUp() throws Exception { 45 | MockitoAnnotations.initMocks(this); 46 | when(sessionManager.getContext()).thenReturn(managerContext); 47 | } 48 | 49 | @Test 50 | public void testMetadataDeserialization() throws Exception { 51 | Entity metadata = Entity.newBuilder(sessionKey) 52 | .set(SessionMetadata.MAX_INACTIVE_INTERVAL, 0) 53 | .set(SessionMetadata.CREATION_TIME, 1) 54 | .set(SessionMetadata.LAST_ACCESSED_TIME, 2) 55 | .set(SessionMetadata.IS_NEW, true) 56 | .set(SessionMetadata.IS_VALID, true) 57 | .set(SessionMetadata.THIS_ACCESSED_TIME, 3) 58 | .build(); 59 | 60 | DatastoreSession session = new DatastoreSession(sessionManager); 61 | session.restoreFromEntities(sessionKey, Collections.singleton(metadata)); 62 | 63 | assertEquals(session.getMaxInactiveInterval(), 0); 64 | assertEquals(session.getCreationTime(), 1); 65 | assertEquals(session.getLastAccessedTime(), 2); 66 | assertEquals(session.isNew(), true); 67 | assertEquals(session.isValid(), true); 68 | assertEquals(session.getThisAccessedTime(), 3); 69 | } 70 | 71 | @Test 72 | public void testAttributesDeserialization() throws Exception { 73 | Entity metadata = mock(Entity.class); 74 | when(metadata.getBoolean(any())).thenReturn(true); 75 | when(sessionKey.getName()).thenReturn("count"); 76 | when(metadata.getKey()).thenReturn(sessionKey); 77 | int count = 5; 78 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 79 | ObjectOutputStream oos = new ObjectOutputStream(bos); 80 | oos.writeObject(count); 81 | 82 | Entity valueEntity = Entity.newBuilder( 83 | (Key)when(mock(Key.class).getName()).thenReturn("count").getMock()) 84 | .set("value", Blob.copyFrom(bos.toByteArray())) 85 | .build(); 86 | 87 | DatastoreSession session = new DatastoreSession(sessionManager); 88 | session.restoreFromEntities(sessionKey, Arrays.asList(metadata, valueEntity)); 89 | 90 | assertEquals(count, session.getAttribute("count")); 91 | } 92 | 93 | @Test 94 | public void testAttributesSerializationKey() throws Exception { 95 | DatastoreSession session = new DatastoreSession(sessionManager); 96 | session.setValid(true); 97 | session.setAttribute("count", 2); 98 | session.setAttribute("map", new HashMap<>()); 99 | 100 | KeyFactory factory = new KeyFactory("project").setKind("kind"); 101 | List entities = session.saveAttributesToEntity(factory); 102 | 103 | assertTrue(entities.stream() 104 | .map(BaseEntity::getKey) 105 | .map(Key::getName) 106 | .collect(Collectors.toSet()) 107 | .containsAll(Arrays.asList("count", "map"))); 108 | } 109 | 110 | @Test 111 | public void testSerializationCycle() throws Exception { 112 | DatastoreSession initialSession = new DatastoreSession(sessionManager); 113 | initialSession.setValid(true); 114 | initialSession.setAttribute("count", 5); 115 | initialSession.setAttribute("map", Collections.singletonMap("key", "value")); 116 | 117 | KeyFactory keyFactory = new KeyFactory("project").setKind("kind"); 118 | List attributes = initialSession.saveToEntities(sessionKey, keyFactory); 119 | 120 | DatastoreSession restoredSession = new DatastoreSession(sessionManager); 121 | restoredSession.restoreFromEntities(sessionKey, attributes); 122 | 123 | assertTrue(restoredSession.getAttribute("count") != null); 124 | assertTrue(restoredSession.getAttribute("map") != null); 125 | 126 | assertEquals(5, restoredSession.getAttribute("count")); 127 | assertEquals("value", ((Map)restoredSession.getAttribute("map")).get("key")); 128 | } 129 | 130 | @Test(expected = NotSerializableException.class) 131 | public void testSerializationError() throws Exception { 132 | DatastoreSession session = spy(new DatastoreSession(sessionManager)); 133 | session.setValid(true); 134 | session.setAttribute("count", 5); 135 | when(session.isAttributeDistributable(any(), any())).thenReturn(true); 136 | when(session.getAttribute("count")).thenReturn(sessionManager); 137 | 138 | session.saveAttributesToEntity(new KeyFactory("project").setKind("kind")); 139 | } 140 | 141 | @Test(expected = IOException.class) 142 | public void testSerializationWithoutMetadata() throws Exception { 143 | DatastoreSession session = new DatastoreSession(sessionManager); 144 | Entity attribute = Entity.newBuilder(new KeyFactory("project") 145 | .setKind("kind") 146 | .newKey("456")) 147 | .build(); 148 | session.restoreFromEntities(sessionKey, Collections.singleton(attribute)); 149 | } 150 | 151 | } -------------------------------------------------------------------------------- /tomcat-gcp-lib/src/test/java/com/google/cloud/runtimes/tomcat/session/DatastoreStoreTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.cloud.runtimes.tomcat.session; 18 | 19 | import static org.junit.Assert.*; 20 | import static org.mockito.ArgumentMatchers.any; 21 | import static org.mockito.ArgumentMatchers.anyString; 22 | import static org.mockito.Mockito.never; 23 | import static org.mockito.Mockito.spy; 24 | import static org.mockito.Mockito.verify; 25 | import static org.mockito.Mockito.when; 26 | 27 | import com.google.cloud.datastore.Cursor; 28 | import com.google.cloud.datastore.Datastore; 29 | import com.google.cloud.datastore.Entity; 30 | import com.google.cloud.datastore.FullEntity; 31 | import com.google.cloud.datastore.Key; 32 | import com.google.cloud.datastore.KeyFactory; 33 | import com.google.cloud.datastore.KeyQuery; 34 | import com.google.cloud.datastore.PathElement; 35 | import com.google.cloud.datastore.QueryResults; 36 | import com.google.common.collect.ImmutableList; 37 | import java.time.Clock; 38 | import java.util.Arrays; 39 | import java.util.Collections; 40 | import java.util.Iterator; 41 | import java.util.List; 42 | import java.util.Map; 43 | import java.util.stream.Collectors; 44 | import org.apache.catalina.Manager; 45 | import org.apache.catalina.Session; 46 | import org.apache.catalina.core.StandardContext; 47 | import org.junit.Before; 48 | import org.junit.Test; 49 | import org.mockito.ArgumentCaptor; 50 | import org.mockito.Mock; 51 | import org.mockito.MockitoAnnotations; 52 | 53 | /** 54 | * Ensures that {@code DatastoreStore} correctly uses the Datastore. 55 | */ 56 | public class DatastoreStoreTest { 57 | 58 | @Mock 59 | private Datastore datastore; 60 | 61 | @Mock 62 | private Manager manager; 63 | 64 | @Mock 65 | private Clock clock; 66 | 67 | private Key key; 68 | 69 | private Key attributeKey; 70 | 71 | private DatastoreStore store; 72 | 73 | private static final String keyId = "123"; 74 | 75 | @Before 76 | public void setUp() throws Exception { 77 | MockitoAnnotations.initMocks(this); 78 | store = new DatastoreStore(); 79 | KeyFactory keyFactory = new KeyFactory("project").setKind("kind"); 80 | key = keyFactory.newKey(keyId); 81 | attributeKey = keyFactory.newKey("attribute"); 82 | QueryResults keyQueryResults = new IteratorQueryResults<>(ImmutableList.of(key).iterator()); 83 | 84 | when(datastore.newKeyFactory()).thenAnswer((invocation) -> new KeyFactory("project")); 85 | when(datastore.run(any(KeyQuery.class))).thenReturn(keyQueryResults); 86 | 87 | when(manager.getContext()).thenReturn(new StandardContext()); 88 | when(manager.willAttributeDistribute(anyString(), any())).thenReturn(true); 89 | when(manager.createEmptySession()).thenReturn(new DatastoreSession(manager)); 90 | 91 | store.setDatastore(datastore); 92 | store.setClock(clock); 93 | store.setSessionKind("kind"); 94 | store.setManager(manager); 95 | } 96 | 97 | @Test 98 | public void testGetStoreSize() throws Exception { 99 | int size = store.getSize(); 100 | verify(datastore).run(any(KeyQuery.class)); 101 | assertEquals(1, size); 102 | } 103 | 104 | @Test 105 | public void testClearStore() throws Exception { 106 | store.clear(); 107 | verify(datastore).delete(any(Key.class)); 108 | } 109 | 110 | @Test 111 | public void testEnumerateKeys() throws Exception { 112 | String[] keys = store.keys(); 113 | verify(datastore).run(any(KeyQuery.class)); 114 | assertEquals(1, keys.length); 115 | assertEquals(keyId, keys[0]); 116 | } 117 | 118 | @Test 119 | public void testEmptySessionLoading() throws Exception { 120 | DatastoreSession session = new DatastoreSession(manager); 121 | session.setValid(true); 122 | session.setId(keyId); 123 | 124 | Entity sessionEntity = session.saveMetadataToEntity(key); 125 | 126 | when(datastore.run(any())).thenReturn( 127 | new IteratorQueryResults<>(Collections.singleton(sessionEntity).iterator())); 128 | 129 | Session loadedSession = store.load(keyId); 130 | verify(datastore).run(any()); 131 | verify(manager).createEmptySession(); 132 | 133 | assertEquals(loadedSession.getId(), keyId); 134 | } 135 | 136 | @Test 137 | public void testLoadNonExistentSession() throws Exception { 138 | when(datastore.run(any())).thenReturn(new IteratorQueryResults<>(Collections.emptyIterator())); 139 | 140 | Session session = store.load("456"); 141 | verify(manager, never()).createEmptySession(); 142 | assertNull(session); 143 | } 144 | 145 | @Test 146 | public void testSessionRemoval() throws Exception { 147 | store.remove(keyId); 148 | verify(datastore).delete(any(Key.class)); 149 | } 150 | 151 | @Test 152 | public void testSessionExpiration() throws Exception { 153 | when(datastore.run(any(KeyQuery.class))).thenReturn( 154 | new IteratorQueryResults<>(Collections.singletonList(key).iterator()), 155 | new IteratorQueryResults<>(Arrays.asList(key, attributeKey).iterator()) 156 | ); 157 | 158 | store.processExpires(); 159 | verify(datastore).delete(Arrays.asList(key, attributeKey).toArray(new Key[0])); 160 | } 161 | 162 | @Test 163 | public void testSessionSave() throws Exception { 164 | DatastoreSession session = spy(new DatastoreSession(manager)); 165 | session.setValid(true); 166 | session.setId(keyId); 167 | session.setAttribute("count", 5); 168 | 169 | store.save(session); 170 | ArgumentCaptor captor = ArgumentCaptor.forClass(Entity.class); 171 | verify(datastore).put((FullEntity[]) captor.capture()); 172 | verify(session).saveAttributesToEntity(any()); 173 | 174 | List entities = captor.getAllValues(); 175 | assertEquals(2, entities.size()); 176 | 177 | assertTrue(entities.stream() 178 | .map(e -> e.getKey().getName()) 179 | .collect(Collectors.toList()) 180 | .containsAll(Arrays.asList("count", keyId))); 181 | 182 | } 183 | 184 | @Test 185 | public void testDecomposedSessionLoad() throws Exception { 186 | DatastoreSession session = new DatastoreSession(manager); 187 | session.setValid(true); 188 | session.setId(keyId); 189 | session.setAttribute("count", 2); 190 | session.setAttribute("map", Collections.singletonMap("key", "value")); 191 | 192 | KeyFactory attributeKeyFactory = datastore.newKeyFactory() 193 | .setKind("kind") 194 | .addAncestor(PathElement.of("kind", key.getName())); 195 | List entities = session.saveToEntities(key, attributeKeyFactory); 196 | 197 | QueryResults queryResults = new IteratorQueryResults<>(entities.iterator()); 198 | when(datastore.run(any())).thenReturn(queryResults); 199 | 200 | Session restored = store.load(keyId); 201 | 202 | assertEquals(keyId, restored.getId()); 203 | assertEquals(2, restored.getSession().getAttribute("count")); 204 | assertEquals("value", 205 | ((Map)session.getSession().getAttribute("map")).get("key")); 206 | } 207 | 208 | @Test 209 | public void testSerializationCycleWithAttributeRemoval() throws Exception { 210 | DatastoreSession initialSession = new DatastoreSession(manager); 211 | initialSession.setValid(true); 212 | initialSession.setId(keyId); 213 | initialSession.setAttribute("count", 5); 214 | initialSession.setAttribute("map", Collections.singletonMap("key", "value")); 215 | KeyFactory attributeKeyFactory = datastore.newKeyFactory() 216 | .setKind("kind") 217 | .addAncestor(PathElement.of("kind", key.getName())); 218 | 219 | List initialSessionEntities = store.serializeSession(initialSession, key, 220 | attributeKeyFactory); 221 | 222 | // Load the session and remove the map attribute 223 | when(datastore.run(any())).thenReturn( 224 | new IteratorQueryResults<>(initialSessionEntities.iterator())); 225 | DatastoreSession session = (DatastoreSession)store.load(keyId); 226 | session.getSession().setAttribute("map", null); 227 | 228 | // Save and reload the session to ensure that the attribute map is not serialized 229 | store.save(session); 230 | 231 | ArgumentCaptor keyCaptors = ArgumentCaptor.forClass(Key.class); 232 | verify(datastore).delete(keyCaptors.capture()); 233 | 234 | assertNotNull(keyCaptors.getValue()); 235 | assertEquals("map", keyCaptors.getValue().getName()); 236 | } 237 | 238 | 239 | @Test 240 | public void testTracerActivation() throws Exception { 241 | store.setTraceRequest(false); 242 | assertNull(store.startSpan("span")); 243 | 244 | store.setTraceRequest(true); 245 | assertNotNull(store.startSpan("span")); 246 | } 247 | 248 | /** 249 | * This is an helper class to mock the return of Datastore queries. 250 | */ 251 | private class IteratorQueryResults implements QueryResults { 252 | 253 | private Iterator iterator; 254 | 255 | IteratorQueryResults(Iterator iterator) { 256 | this.iterator = iterator; 257 | } 258 | 259 | @Override 260 | public Class getResultClass() { 261 | return null; 262 | } 263 | 264 | @Override 265 | public Cursor getCursorAfter() { 266 | return null; 267 | } 268 | 269 | @Override 270 | public boolean hasNext() { 271 | return iterator.hasNext(); 272 | } 273 | 274 | @Override 275 | public T next() { 276 | return iterator.next(); 277 | } 278 | } 279 | 280 | } -------------------------------------------------------------------------------- /tomcat-gcp-lib/src/test/java/com/google/cloud/runtimes/tomcat/session/DatastoreValveTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.cloud.runtimes.tomcat.session; 18 | 19 | import static org.mockito.ArgumentMatchers.anyBoolean; 20 | import static org.mockito.Mockito.mock; 21 | import static org.mockito.Mockito.never; 22 | import static org.mockito.Mockito.verify; 23 | import static org.mockito.Mockito.when; 24 | 25 | import org.apache.catalina.Context; 26 | import org.apache.catalina.Session; 27 | import org.apache.catalina.Store; 28 | import org.apache.catalina.Valve; 29 | import org.apache.catalina.connector.Request; 30 | import org.apache.catalina.connector.Response; 31 | import org.junit.Before; 32 | import org.junit.Test; 33 | import org.mockito.Mock; 34 | import org.mockito.MockitoAnnotations; 35 | 36 | /** 37 | * Ensures that {@code DatastoreValve} is persisting the session in the store at the end 38 | * of each request. 39 | */ 40 | public class DatastoreValveTest { 41 | 42 | @Mock 43 | private Context context; 44 | 45 | @Mock 46 | private DatastoreManager manager; 47 | 48 | @Mock 49 | private Store store; 50 | 51 | @Mock 52 | private Request request; 53 | 54 | @Mock 55 | private Response response; 56 | 57 | @Mock 58 | private Session session; 59 | 60 | @Mock 61 | private Valve nextValve = mock(Valve.class); 62 | 63 | private DatastoreValve valve; 64 | 65 | @Before 66 | public void setUp() throws Exception { 67 | MockitoAnnotations.initMocks(this); 68 | 69 | when(context.getManager()).thenReturn(manager); 70 | when(manager.getStore()).thenReturn(store); 71 | when(request.getSessionInternal(anyBoolean())).thenReturn(session); 72 | when(request.getContext()).thenReturn(context); 73 | valve = new DatastoreValve(); 74 | } 75 | 76 | @Test 77 | public void testSessionPersistence() throws Exception { 78 | valve.setNext(nextValve); 79 | valve.invoke(request, response); 80 | 81 | verify(store).save(session); 82 | verify(manager).removeSuper(session); 83 | } 84 | 85 | @Test 86 | public void testIgnoredHealthCheck() throws Exception { 87 | when(request.getRequestURI()).thenReturn("/_ah/health"); 88 | 89 | valve.setNext(nextValve); 90 | valve.setUriExcludePattern("^/_ah/.*"); 91 | valve.invoke(request, response); 92 | 93 | verify(session, never()).access(); 94 | } 95 | 96 | @Test 97 | public void testIgnoredExtension() throws Exception { 98 | when(request.getRequestURI()).thenReturn("/img/foo.png"); 99 | 100 | valve.setNext(nextValve); 101 | valve.setUriExcludePattern(".*\\.(ico|png|gif|jpg|css|js|)$"); 102 | valve.invoke(request, response); 103 | 104 | verify(session, never()).access(); 105 | } 106 | 107 | @Test 108 | public void testNonIgnoredHealthCheck() throws Exception { 109 | when(request.getRequestURI()).thenReturn("/_ah/health"); 110 | 111 | valve.setNext(nextValve); 112 | valve.setUriExcludePattern(".*\\.(ico|png|gif|jpg|css|js|)$"); 113 | valve.invoke(request, response); 114 | 115 | verify(session).access(); 116 | } 117 | 118 | } -------------------------------------------------------------------------------- /tomcat-gcp-lib/src/test/java/com/google/cloud/runtimes/tomcat/session/integration/DatastoreStoreIT.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.cloud.runtimes.tomcat.session.integration; 18 | 19 | import static org.junit.Assert.assertEquals; 20 | import static org.junit.Assert.assertNotNull; 21 | import static org.junit.Assert.assertNull; 22 | import static org.junit.Assert.assertTrue; 23 | import static org.junit.Assume.assumeNoException; 24 | 25 | import com.google.cloud.datastore.Datastore; 26 | import com.google.cloud.datastore.DatastoreException; 27 | import com.google.cloud.datastore.DatastoreOptions; 28 | import com.google.cloud.datastore.Entity; 29 | import com.google.cloud.datastore.Key; 30 | import com.google.cloud.datastore.KeyFactory; 31 | import com.google.cloud.datastore.Query; 32 | import com.google.cloud.datastore.QueryResults; 33 | import com.google.cloud.runtimes.tomcat.session.DatastoreManager; 34 | import com.google.cloud.runtimes.tomcat.session.DatastoreStore; 35 | import com.google.common.collect.Streams; 36 | import java.io.IOException; 37 | import java.util.Arrays; 38 | import javax.servlet.http.HttpSession; 39 | import org.apache.catalina.Context; 40 | import org.apache.catalina.LifecycleException; 41 | import org.apache.catalina.Manager; 42 | import org.apache.catalina.Session; 43 | import org.apache.catalina.core.StandardContext; 44 | import org.junit.AfterClass; 45 | import org.junit.Before; 46 | import org.junit.BeforeClass; 47 | import org.junit.Test; 48 | 49 | /** 50 | * Ensure that the {@link DatastoreStore} respects the contract set by the 51 | * {@link org.apache.catalina.Store} interface and verify the behavior of 52 | * the serialization mechanisms. 53 | * 54 | * If no credentials can be found for the Datastore, those tests are ignored. 55 | */ 56 | public class DatastoreStoreIT { 57 | 58 | private static DatastoreStore store; 59 | private static Datastore datastore; 60 | private static Manager manager; 61 | private static KeyFactory keyFactory; 62 | private static final String namespace = "tomcat-gcp-persistent-session-test"; 63 | private static final String kind = "TomcatGCloudSession"; 64 | 65 | private Session session; 66 | 67 | private static void setUpDatastore() { 68 | datastore = DatastoreOptions.newBuilder().setNamespace(namespace).build().getService(); 69 | keyFactory = datastore.newKeyFactory().setKind(kind); 70 | } 71 | 72 | private static void setUpStore() { 73 | store = new DatastoreStore(); 74 | Context context = new StandardContext(); 75 | manager = new DatastoreManager(); 76 | manager.setContext(context); 77 | store.setManager(manager); 78 | store.setNamespace(namespace); 79 | store.setSessionKind(kind); 80 | } 81 | 82 | private void clearNamespace() { 83 | QueryResults keys = datastore.run(Query.newKeyQueryBuilder().build()); 84 | datastore.delete(Streams.stream(keys).toArray(Key[]::new)); 85 | } 86 | 87 | @BeforeClass 88 | public static void setUpClass() throws Exception { 89 | setUpDatastore(); 90 | setUpStore(); 91 | 92 | // Run the tests only if the datastore is accessible 93 | try { 94 | datastore.get(keyFactory.newKey("123")); 95 | } catch (DatastoreException e) { 96 | assumeNoException(e); 97 | } 98 | 99 | store.start(); 100 | } 101 | 102 | @Before 103 | public void setUp() throws LifecycleException { 104 | this.clearNamespace(); 105 | this.session = manager.createSession("123"); 106 | } 107 | 108 | @AfterClass 109 | public static void tearDown() throws LifecycleException { 110 | store.stop(); 111 | } 112 | 113 | @Test 114 | public void testSessionSave() throws IOException { 115 | store.save(session); 116 | 117 | Entity serializedSession = datastore.get(keyFactory.newKey(session.getId())); 118 | assertNotNull(serializedSession); 119 | } 120 | 121 | @Test 122 | public void testSessionCount() throws IOException { 123 | Session session2 = manager.createSession("456"); 124 | store.save(session); 125 | store.save(session2); 126 | 127 | int size = store.getSize(); 128 | assertEquals(2, size); 129 | } 130 | 131 | @Test 132 | public void testSessionEnumeration() throws IOException { 133 | store.save(session); 134 | 135 | String[] keys = store.keys(); 136 | assertTrue(Arrays.stream(keys).anyMatch(id -> session.getId().equals(id))); 137 | assertEquals(1, keys.length); 138 | } 139 | 140 | /** 141 | * The Store contract specifies that if no entity is found an empty array must be return. 142 | */ 143 | @Test 144 | public void testSessionEnumerationForEmptyStore() throws IOException { 145 | String[] keys = store.keys(); 146 | assertNotNull(keys); 147 | } 148 | 149 | @Test 150 | public void testSessionLoad() throws IOException, ClassNotFoundException { 151 | session.getSession().setAttribute("attribute", "test-value"); 152 | store.save(session); 153 | 154 | Session loadedSession = store.load(session.getId()); 155 | assertEquals("test-value", loadedSession.getSession().getAttribute("attribute")); 156 | } 157 | 158 | @Test 159 | public void testSessionRemoval() throws IOException { 160 | store.save(session); 161 | 162 | store.remove(session.getId()); 163 | Entity serializedSession = datastore.get(keyFactory.newKey(session.getId())); 164 | assertNull(serializedSession); 165 | } 166 | 167 | @Test 168 | public void testStoreReset() throws IOException { 169 | store.save(session); 170 | 171 | store.clear(); 172 | Entity serializedSession = datastore.get(keyFactory.newKey(session.getId())); 173 | assertNull(serializedSession); 174 | assertEquals(0, store.getSize()); 175 | } 176 | 177 | /** 178 | * Non serializable attributes should be ignored but should not prevent the serialization 179 | * of the session. 180 | */ 181 | @Test 182 | public void testSerializationOfNonSerializableAttribute() 183 | throws IOException, ClassNotFoundException { 184 | HttpSession innerSession = session.getSession(); 185 | innerSession.setAttribute("serializable", "test"); 186 | innerSession.setAttribute("non-serializable", manager); 187 | store.save(session); 188 | 189 | Session loadedSession = store.load(session.getId()); 190 | HttpSession innerLoadedSession = loadedSession.getSession(); 191 | assertNull(innerLoadedSession.getAttribute("non-serializable")); 192 | assertNotNull(innerLoadedSession.getAttribute("serializable")); 193 | } 194 | 195 | } -------------------------------------------------------------------------------- /tomcat-gcp-lib/src/test/java/com/google/cloud/runtimes/tomcat/trace/TraceValveTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.cloud.runtimes.tomcat.trace; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | import static org.junit.Assert.assertTrue; 20 | import static org.mockito.ArgumentMatchers.any; 21 | import static org.mockito.ArgumentMatchers.anyString; 22 | import static org.mockito.ArgumentMatchers.matches; 23 | import static org.mockito.Mockito.mock; 24 | import static org.mockito.Mockito.spy; 25 | import static org.mockito.Mockito.verify; 26 | import static org.mockito.Mockito.when; 27 | 28 | import com.google.cloud.trace.SpanContextHandler; 29 | import com.google.cloud.trace.Trace; 30 | import com.google.cloud.trace.Tracer; 31 | import com.google.cloud.trace.core.Label; 32 | import com.google.cloud.trace.core.Labels; 33 | import com.google.cloud.trace.core.SpanContext; 34 | import com.google.cloud.trace.core.SpanContextFactory; 35 | import com.google.cloud.trace.core.SpanContextHandle; 36 | import com.google.cloud.trace.core.TraceContext; 37 | import com.google.cloud.trace.service.TraceGrpcApiService; 38 | import com.google.cloud.trace.service.TraceGrpcApiService.Builder; 39 | import java.io.IOException; 40 | import org.apache.catalina.LifecycleException; 41 | import org.apache.catalina.Valve; 42 | import org.apache.catalina.connector.Request; 43 | import org.apache.catalina.connector.Response; 44 | import org.junit.Before; 45 | import org.junit.Test; 46 | import org.mockito.ArgumentCaptor; 47 | import org.mockito.Mock; 48 | import org.mockito.MockitoAnnotations; 49 | 50 | public class TraceValveTest { 51 | 52 | @Mock 53 | private TraceGrpcApiService traceService; 54 | 55 | @Mock 56 | private Tracer tracer; 57 | 58 | @Mock 59 | private Request request; 60 | 61 | @Mock 62 | private Response response; 63 | 64 | @Mock 65 | private Valve nextValve; 66 | 67 | @Mock 68 | private TraceContext traceContext; 69 | 70 | @Mock 71 | private SpanContextHandler spanContextHandler; 72 | 73 | @Mock 74 | private SpanContextFactory spanContextFactory; 75 | 76 | private TraceValve valve; 77 | 78 | @Before 79 | public void setUp() throws Exception { 80 | MockitoAnnotations.initMocks(this); 81 | 82 | valve = new TraceValve(); 83 | valve.setNext(nextValve); 84 | valve.setTraceService(traceService); 85 | 86 | when(traceService.getTracer()).thenReturn(tracer); 87 | when(traceService.getSpanContextFactory()).thenReturn(spanContextFactory); 88 | when(traceService.getSpanContextHandler()).thenReturn(spanContextHandler); 89 | when(tracer.startSpan(anyString())).thenReturn(traceContext); 90 | } 91 | 92 | @Test 93 | public void testRequestForwarding() throws Exception { 94 | valve.invoke(request, response); 95 | 96 | verify(nextValve).invoke(any(), any()); 97 | } 98 | 99 | @Test 100 | public void testSpanCreation() throws Exception { 101 | when(request.getRequestURI()).thenReturn("/index"); 102 | when(response.getStatus()).thenReturn(200); 103 | ArgumentCaptor labelsArgument = ArgumentCaptor.forClass(Labels.class); 104 | ArgumentCaptor traceArgument = ArgumentCaptor.forClass(TraceContext.class); 105 | Label indexLabel = new Label(HttpLabels.HTTP_URL.getValue(), "/index"); 106 | Label statusCodeLabel = new Label(HttpLabels.HTTP_STATUS_CODE.getValue(), "200"); 107 | 108 | valve.invoke(request, response); 109 | 110 | verify(tracer).startSpan(matches("/index")); 111 | verify(tracer).annotateSpan(traceArgument.capture(), labelsArgument.capture()); 112 | verify(tracer).endSpan(traceContext); 113 | assertEquals(labelsArgument.getValue().getLabels().size(), 2); 114 | assertTrue(labelsArgument.getValue().getLabels().contains(indexLabel)); 115 | assertTrue(labelsArgument.getValue().getLabels().contains(statusCodeLabel)); 116 | } 117 | 118 | /** 119 | * If x-cloud-trace-context header is present a new context must created. 120 | */ 121 | @Test 122 | public void testTraceHeader() throws Exception { 123 | SpanContext contextFromHeader = mock(SpanContext.class); 124 | when(request.getHeader(SpanContextFactory.headerKey())).thenReturn("traceid/spanid"); 125 | when(spanContextFactory.fromHeader("traceid/spanid")).thenReturn(contextFromHeader); 126 | 127 | valve.invoke(request, response); 128 | 129 | verify(spanContextHandler).attach(contextFromHeader); 130 | } 131 | 132 | @Test(expected = LifecycleException.class) 133 | public void testInvalidTraceDelay() throws Exception { 134 | valve.setTraceScheduledDelay(0); 135 | valve.initTraceService(); 136 | } 137 | 138 | @Test 139 | public void testServiceInitialization() throws Exception { 140 | valve.setTraceScheduledDelay(60); 141 | TraceValve spiedValve = spy(valve); 142 | Builder builder = spy(TraceGrpcApiService.builder()); 143 | when(builder.build()).thenReturn(traceService); 144 | when(spiedValve.getTraceService()).thenReturn(builder); 145 | spiedValve.initTraceService(); 146 | assertEquals(Trace.getTracer(), traceService.getTracer()); 147 | } 148 | 149 | @Test(expected = LifecycleException.class) 150 | public void testServiceInitializationError() throws Exception { 151 | valve.setTraceScheduledDelay(60); 152 | TraceValve spiedValve = spy(valve); 153 | Builder builder = spy(TraceGrpcApiService.builder()); 154 | when(builder.build()).thenThrow(new IOException()); 155 | when(spiedValve.getTraceService()).thenReturn(builder); 156 | spiedValve.initTraceService(); 157 | } 158 | 159 | @Test 160 | public void testThatSpanHandleIsDetached() throws Exception { 161 | when(request.getHeader(anyString())).thenReturn(""); 162 | SpanContextHandle contextHandle = mock(SpanContextHandle.class); 163 | when(traceService.getSpanContextHandler().attach(any())).thenReturn(contextHandle); 164 | valve.invoke(request, response); 165 | verify(contextHandle).detach(); 166 | } 167 | 168 | } -------------------------------------------------------------------------------- /tomcat/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 21 | tomcat-parent 22 | com.google.cloud.runtimes 23 | 0.1.0-SNAPSHOT 24 | 25 | 4.0.0 26 | Google Tomcat Runtime Docker Image 27 | tomcat 28 | pom 29 | 30 | 31 | /usr/local/tomcat 32 | /var/lib/tomcat 33 | 34 | 35 | 36 | 37 | local-docker-build 38 | 39 | true 40 | 41 | 42 | 43 | 44 | io.fabric8 45 | docker-maven-plugin 46 | 47 | 48 | build 49 | package 50 | 51 | build 52 | 53 | 54 | 55 | 56 | ${docker.image.name} 57 | 58 | ${project.build.directory}/docker-src 59 | 60 | ${docker.tag.short} 61 | ${docker.tag.long} 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | org.codehaus.mojo 72 | exec-maven-plugin 73 | 1.6.0 74 | 75 | 76 | structure-test 77 | integration-test 78 | 79 | exec 80 | 81 | 82 | ../../scripts/utils/structure_test.sh 83 | ${project.build.directory} 84 | 85 | --image 86 | ${docker.image.name}:${docker.tag.long} 87 | --workspace 88 | ${project.basedir}/.. 89 | --config 90 | ${project.build.testOutputDirectory}/structure.yaml 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | maven-dependency-plugin 106 | 107 | 108 | tomcat-home 109 | generate-resources 110 | 111 | unpack 112 | 113 | 114 | 115 | 116 | org.apache.tomcat 117 | tomcat 118 | ${tomcat.version} 119 | tar.gz 120 | true 121 | ** 122 | ${project.build.directory} 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | maven-resources-plugin 132 | 133 | 134 | copy-resources 135 | generate-resources 136 | 137 | copy-resources 138 | 139 | 140 | ${project.build.directory}/docker-src 141 | 142 | 143 | ${basedir}/src/main/docker 144 | true 145 | 146 | 147 | 148 | 149 | 150 | copy-tomcat-home 151 | process-resources 152 | 153 | copy-resources 154 | 155 | 156 | ${project.build.directory}/docker-src/tomcat-home 157 | 158 | 159 | ${project.build.directory}/apache-tomcat-${tomcat.version} 160 | false 161 | 162 | ** 163 | 164 | 165 | 166 | 167 | 168 | 169 | copy-tomcat-base 170 | process-resources 171 | 172 | copy-resources 173 | 174 | 175 | ${project.build.directory}/docker-src/tomcat-base 176 | 177 | 178 | ${project.basedir}/src/main/tomcat-base 179 | true 180 | 181 | ** 182 | 183 | 184 | 185 | 186 | 187 | 188 | copy-tomcat-optional-configuration 189 | process-resources 190 | 191 | copy-resources 192 | 193 | 194 | ${project.build.directory}/docker-src/config 195 | 196 | 197 | ${project.basedir}/src/main/resources/config 198 | true 199 | 200 | ** 201 | 202 | 203 | 204 | 205 | 206 | 207 | copy-structure-test 208 | generate-test-resources 209 | 210 | copy-resources 211 | 212 | 213 | ${project.build.testOutputDirectory} 214 | 215 | 216 | ${basedir}/src/test/resources 217 | true 218 | 219 | 220 | 221 | 222 | 223 | copy-cloudbuild-configuration 224 | generate-resources 225 | 226 | copy-resources 227 | 228 | 229 | ${project.build.directory}/cloudbuild 230 | 231 | 232 | ${basedir}/src/cloudbuild 233 | true 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | org.apache.maven.plugins 244 | maven-dependency-plugin 245 | 3.0.1 246 | 247 | 248 | copy 249 | package 250 | 251 | copy 252 | 253 | 254 | ${project.build.directory}/docker-src/tomcat-base/lib-gcp/ 255 | 256 | 257 | ${project.groupId} 258 | tomcat-gcp-lib 259 | ${project.version} 260 | jar-with-dependencies 261 | jar 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | -------------------------------------------------------------------------------- /tomcat/src/cloudbuild/build.yaml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: 'gcr.io/cloud-builders/docker' 3 | args: ['build', '-t', '${docker.project.namespace}/${docker.image.name}:${docker.tag.long}', 'tomcat/target/docker-src'] 4 | 5 | - name: 'gcr.io/gcp-runtimes/structure_test' 6 | args: [ 7 | '--image', 8 | '${cloudbuild.tomcat.image}', 9 | '-v', 10 | '--config', 11 | '/workspace/tomcat/target/test-classes/structure.yaml'] 12 | 13 | images: 14 | - '${cloudbuild.tomcat.image}' 15 | -------------------------------------------------------------------------------- /tomcat/src/main/docker/15-debug-env-tomcat.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Adds bits of configuration necessary for the Cloud Debugger to work with the Tomcat image. 3 | # It needs to run before 20-debug-env.bash from the openjdk image. 4 | 5 | # Set CDBG_APP_WEB_INF_DIR, used by CDBG in format-env-appengine-vm.sh 6 | export CDBG_APP_WEB_INF_DIR="${CATALINA_BASE}/webapps/ROOT/WEB-INF" -------------------------------------------------------------------------------- /tomcat/src/main/docker/50-tomcat.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Unpack a WAR app (if present) beforehand so that Stackdriver Debugger 4 | # can load it. This should be done before the JVM for Tomcat starts up. 5 | export ROOT_WAR=$CATALINA_BASE/webapps/ROOT.war 6 | export ROOT_DIR=$CATALINA_BASE/webapps/ROOT 7 | if [ -e "$ROOT_WAR" ]; then 8 | # Unpack it only if $ROOT_DIR doesn't exist or the root is older than the war. 9 | if [ -e "$ROOT_WAR" -a \( \( ! -e "$ROOT_DIR" \) -o \( "$ROOT_DIR" -ot "$ROOT_WAR" \) \) ]; then 10 | rm -fr $ROOT_DIR 11 | unzip $ROOT_WAR -d $ROOT_DIR 12 | chown -R tomcat:tomcat $ROOT_DIR 13 | fi 14 | fi 15 | 16 | # If we are deploying on a GAE platform, enable the GCP module 17 | if [ "$PLATFORM" == "gae" ]; then 18 | TOMCAT_MODULES_ENABLE="$TOMCAT_MODULES_ENABLE,gcp" 19 | fi 20 | 21 | if [ -n "$TOMCAT_MODULES_ENABLE" ]; then 22 | echo "$TOMCAT_MODULES_ENABLE" | tr ',' '\n' | while read module; do 23 | if [ -r "/config/${module}.xml" ]; then 24 | cp "/config/${module}.xml" "${CATALINA_BASE}/conf/${module}.xml" 25 | else 26 | echo "The configuration for the module ${module} does not exist" >&2 27 | fi 28 | done 29 | fi 30 | 31 | # Add all the user defined properties to catalina.properties 32 | if [ -n "$TOMCAT_PROPERTIES" ]; then 33 | echo >> ${CATALINA_BASE}/conf/catalina.properties 34 | echo "$TOMCAT_PROPERTIES" | tr ',' '\n' | while read property; do 35 | echo ${property} >> ${CATALINA_BASE}/conf/catalina.properties 36 | done 37 | fi 38 | 39 | if [ -n "$TOMCAT_LOGGING_PROPERTIES" ]; then 40 | echo "$TOMCAT_LOGGING_PROPERTIES" | tr ',' '\n' | while read property; do 41 | echo ${property} >> ${CATALINA_BASE}/conf/logging.properties 42 | done 43 | fi -------------------------------------------------------------------------------- /tomcat/src/main/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | FROM ${docker.openjdk.image} 15 | 16 | RUN groupadd -r tomcat \ 17 | && useradd -r -g tomcat tomcat 18 | 19 | # Environment variable for tomcat 20 | ENV CATALINA_HOME ${tomcat.home} 21 | 22 | # Create Tomcat home 23 | COPY tomcat-home $CATALINA_HOME 24 | RUN chmod +x ${CATALINA_HOME}/bin/catalina.sh \ 25 | && chown -R tomcat:tomcat ${CATALINA_HOME} 26 | 27 | # Create Tomcat base 28 | ENV CATALINA_BASE ${tomcat.base} 29 | COPY tomcat-base $CATALINA_BASE 30 | COPY 15-debug-env-tomcat.bash 50-tomcat.bash /setup-env.d/ 31 | COPY config /config 32 | 33 | RUN mkdir -p $CATALINA_BASE/webapps \ 34 | && mkdir -p $CATALINA_BASE/temp \ 35 | && cp $CATALINA_HOME/conf/web.xml $CATALINA_BASE/conf/web.xml \ 36 | && chown -R tomcat:tomcat ${CATALINA_BASE} \ 37 | && chmod -R 400 $CATALINA_BASE/conf 38 | 39 | # Set path where apps should be added to the container 40 | ENV APP_WAR ROOT.war 41 | ENV APP_EXPLODED_WAR ROOT/ 42 | ENV APP_DESTINATION_PATH $CATALINA_BASE/webapps/ 43 | ENV APP_DESTINATION_WAR $APP_DESTINATION_PATH$APP_WAR 44 | ENV APP_DESTINATION_EXPLODED_WAR $APP_DESTINATION_PATH$APP_EXPLODED_WAR 45 | 46 | # This env var is here to not break users of previous versions where only a .war was expected 47 | ENV APP_DESTINATION $APP_DESTINATION_WAR 48 | 49 | WORKDIR $CATALINA_BASE/webapps 50 | 51 | EXPOSE 8080 52 | CMD ["/bin/bash", "${tomcat.home}/bin/catalina.sh", "run"] 53 | -------------------------------------------------------------------------------- /tomcat/src/main/resources/config/distributed-sessions.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /tomcat/src/main/resources/config/gcp.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /tomcat/src/main/resources/config/stackdriver-trace.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tomcat/src/main/tomcat-base/conf/catalina.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # 17 | # List of comma-separated packages that start with or equal this string 18 | # will cause a security exception to be thrown when 19 | # passed to checkPackageAccess unless the 20 | # corresponding RuntimePermission ("accessClassInPackage."+package) has 21 | # been granted. 22 | package.access=sun.,org.apache.catalina.,org.apache.coyote.,org.apache.jasper.,org.apache.tomcat. 23 | # 24 | # List of comma-separated packages that start with or equal this string 25 | # will cause a security exception to be thrown when 26 | # passed to checkPackageDefinition unless the 27 | # corresponding RuntimePermission ("defineClassInPackage."+package) has 28 | # been granted. 29 | # 30 | # by default, no packages are restricted for definition, and none of 31 | # the class loaders supplied with the JDK call checkPackageDefinition. 32 | # 33 | package.definition=sun.,java.,org.apache.catalina.,org.apache.coyote.,\ 34 | org.apache.jasper.,org.apache.naming.,org.apache.tomcat. 35 | 36 | # 37 | # 38 | # List of comma-separated paths defining the contents of the "common" 39 | # classloader. Prefixes should be used to define what is the repository type. 40 | # Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute. 41 | # If left as blank,the JVM system loader will be used as Catalina's "common" 42 | # loader. 43 | # Examples: 44 | # "foo": Add this folder as a class repository 45 | # "foo/*.jar": Add all the JARs of the specified folder as class 46 | # repositories 47 | # "foo/bar.jar": Add bar.jar as a class repository 48 | # 49 | # Note: Values are enclosed in double quotes ("...") in case either the 50 | # ${catalina.base} path or the ${catalina.home} path contains a comma. 51 | # Because double quotes are used for quoting, the double quote character 52 | # may not appear in a path. 53 | common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar","${catalina.base}/lib-gcp/*.jar" 54 | 55 | # 56 | # List of comma-separated paths defining the contents of the "server" 57 | # classloader. Prefixes should be used to define what is the repository type. 58 | # Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute. 59 | # If left as blank, the "common" loader will be used as Catalina's "server" 60 | # loader. 61 | # Examples: 62 | # "foo": Add this folder as a class repository 63 | # "foo/*.jar": Add all the JARs of the specified folder as class 64 | # repositories 65 | # "foo/bar.jar": Add bar.jar as a class repository 66 | # 67 | # Note: Values may be enclosed in double quotes ("...") in case either the 68 | # ${catalina.base} path or the ${catalina.home} path contains a comma. 69 | # Because double quotes are used for quoting, the double quote character 70 | # may not appear in a path. 71 | server.loader= 72 | 73 | # 74 | # List of comma-separated paths defining the contents of the "shared" 75 | # classloader. Prefixes should be used to define what is the repository type. 76 | # Path may be relative to the CATALINA_BASE path or absolute. If left as blank, 77 | # the "common" loader will be used as Catalina's "shared" loader. 78 | # Examples: 79 | # "foo": Add this folder as a class repository 80 | # "foo/*.jar": Add all the JARs of the specified folder as class 81 | # repositories 82 | # "foo/bar.jar": Add bar.jar as a class repository 83 | # Please note that for single jars, e.g. bar.jar, you need the URL form 84 | # starting with file:. 85 | # 86 | # Note: Values may be enclosed in double quotes ("...") in case either the 87 | # ${catalina.base} path or the ${catalina.home} path contains a comma. 88 | # Because double quotes are used for quoting, the double quote character 89 | # may not appear in a path. 90 | shared.loader= 91 | 92 | # Default list of JAR files that should not be scanned using the JarScanner 93 | # functionality. This is typically used to scan JARs for configuration 94 | # information. JARs that do not contain such information may be excluded from 95 | # the scan to speed up the scanning process. This is the default list. JARs on 96 | # this list are excluded from all scans. The list must be a comma separated list 97 | # of JAR file names. 98 | # The list of JARs to skip may be over-ridden at a Context level for individual 99 | # scan types by configuring a JarScanner with a nested JarScanFilter. 100 | # The JARs listed below include: 101 | # - Tomcat Bootstrap JARs 102 | # - Tomcat API JARs 103 | # - Catalina JARs 104 | # - Jasper JARs 105 | # - Tomcat JARs 106 | # - Common non-Tomcat JARs 107 | # - Test JARs (JUnit, Cobertura and dependencies) 108 | tomcat.util.scan.StandardJarScanFilter.jarsToSkip=\ 109 | bootstrap.jar,commons-daemon.jar,tomcat-juli.jar,\ 110 | annotations-api.jar,el-api.jar,jsp-api.jar,servlet-api.jar,websocket-api.jar,\ 111 | jaspic-api.jar,\ 112 | catalina.jar,catalina-ant.jar,catalina-ha.jar,catalina-storeconfig.jar,\ 113 | catalina-tribes.jar,\ 114 | jasper.jar,jasper-el.jar,ecj-*.jar,\ 115 | tomcat-api.jar,tomcat-util.jar,tomcat-util-scan.jar,tomcat-coyote.jar,\ 116 | tomcat-dbcp.jar,tomcat-jni.jar,tomcat-websocket.jar,\ 117 | tomcat-i18n-en.jar,tomcat-i18n-es.jar,tomcat-i18n-fr.jar,tomcat-i18n-ja.jar,\ 118 | tomcat-juli-adapters.jar,catalina-jmx-remote.jar,catalina-ws.jar,\ 119 | tomcat-jdbc.jar,\ 120 | tools.jar,\ 121 | commons-beanutils*.jar,commons-codec*.jar,commons-collections*.jar,\ 122 | commons-dbcp*.jar,commons-digester*.jar,commons-fileupload*.jar,\ 123 | commons-httpclient*.jar,commons-io*.jar,commons-lang*.jar,commons-logging*.jar,\ 124 | commons-math*.jar,commons-pool*.jar,\ 125 | jstl.jar,taglibs-standard-spec-*.jar,\ 126 | geronimo-spec-jaxrpc*.jar,wsdl4j*.jar,\ 127 | ant.jar,ant-junit*.jar,aspectj*.jar,jmx.jar,h2*.jar,hibernate*.jar,httpclient*.jar,\ 128 | jmx-tools.jar,jta*.jar,log4j*.jar,mail*.jar,slf4j*.jar,\ 129 | xercesImpl.jar,xmlParserAPIs.jar,xml-apis.jar,\ 130 | junit.jar,junit-*.jar,hamcrest-*.jar,easymock-*.jar,cglib-*.jar,\ 131 | objenesis-*.jar,ant-launcher.jar,\ 132 | cobertura-*.jar,asm-*.jar,dom4j-*.jar,icu4j-*.jar,jaxen-*.jar,jdom-*.jar,\ 133 | jetty-*.jar,oro-*.jar,servlet-api-*.jar,tagsoup-*.jar,xmlParserAPIs-*.jar,\ 134 | xom-*.jar,\ 135 | tomcat-gcp-lib*.jar 136 | 137 | # Default list of JAR files that should be scanned that overrides the default 138 | # jarsToSkip list above. This is typically used to include a specific JAR that 139 | # has been excluded by a broad file name pattern in the jarsToSkip list. 140 | # The list of JARs to scan may be over-ridden at a Context level for individual 141 | # scan types by configuring a JarScanner with a nested JarScanFilter. 142 | tomcat.util.scan.StandardJarScanFilter.jarsToScan=\ 143 | log4j-web*.jar,log4j-taglib*.jar,log4javascript*.jar,slf4j-taglib*.jar 144 | 145 | # String cache configuration. 146 | tomcat.util.buf.StringCache.byte.enabled=true 147 | #tomcat.util.buf.StringCache.char.enabled=true 148 | #tomcat.util.buf.StringCache.trainThreshold=500000 149 | #tomcat.util.buf.StringCache.cacheSize=5000 150 | 151 | # Regular expression to indicate witch proxy should be trusted in x-forwarded-for header (e.g load balancer) 152 | gae.proxy.trusted=".*" 153 | 154 | # Datastore Session Manager configuration 155 | gcp.distributed-sessions.namespace=tomcat-gcp-persistent-session 156 | gcp.distributed-sessions.sessionKind=TomcatGCloudSession 157 | gcp.distributed-sessions.enableTrace=false 158 | 159 | # Specify which Uri to ignore when persisting sessions. 160 | gcp.distributed-sessions.uriExcludePattern= 161 | 162 | # Set the attribute compression of the HTTP Connector 163 | tomcat.server.connector.compression=off -------------------------------------------------------------------------------- /tomcat/src/main/tomcat-base/conf/context.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 23 | 24 | ]> 25 | 26 | 27 | &trace-requests; 28 | 29 | &gcp-configuration; 30 | 31 | &distributed-session-configuration; 32 | 33 | -------------------------------------------------------------------------------- /tomcat/src/main/tomcat-base/conf/distributed-sessions.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tomcat/src/main/tomcat-base/conf/gcp.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tomcat/src/main/tomcat-base/conf/logging.properties: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed to the Apache Software Foundation (ASF) under one or more 4 | # contributor license agreements. See the NOTICE file distributed with 5 | # this work for additional information regarding copyright ownership. 6 | # The ASF licenses this file to You under the Apache License, Version 2.0 7 | # (the "License"); you may not use this file except in compliance with 8 | # the License. You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | handlers = java.util.logging.ConsoleHandler 19 | 20 | .handlers = java.util.logging.ConsoleHandler 21 | 22 | ############################################################ 23 | # Handler specific properties. 24 | # Describes specific configuration info for Handlers. 25 | ############################################################ 26 | 27 | java.util.logging.ConsoleHandler.level = FINE 28 | java.util.logging.ConsoleHandler.formatter = org.apache.juli.OneLineFormatter 29 | 30 | ############################################################ 31 | # Facility specific properties. 32 | # Provides extra control for each logger. 33 | ############################################################ 34 | 35 | org.apache.catalina.core.ContainerBase.[Catalina].[localhost].level = INFO 36 | org.apache.catalina.core.ContainerBase.[Catalina].[localhost].handlers = java.util.logging.ConsoleHandler 37 | 38 | org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].level = INFO 39 | org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].handlers = java.util.logging.ConsoleHandler 40 | 41 | org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager].level = INFO 42 | org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager].handlers = java.util.logging.ConsoleHandler 43 | -------------------------------------------------------------------------------- /tomcat/src/main/tomcat-base/conf/server.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 40 | 41 | 46 | 47 | 48 | 49 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /tomcat/src/main/tomcat-base/conf/stackdriver-trace.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tomcat/src/test/resources/structure.yaml: -------------------------------------------------------------------------------- 1 | schemaVersion: 1.0.0 2 | 3 | commandTests: 4 | - name: 'CATALINA_HOME is set' 5 | command: ['env'] 6 | expectedOutput: ['CATALINA_HOME=${tomcat.home}'] 7 | - name: 'CATALINA_BASE is set' 8 | command: ['env'] 9 | expectedOutput: ['CATALINA_BASE=${tomcat.base}'] 10 | - name: 'Catalina script is executable' 11 | command: ['find', '${tomcat.home}/bin/catalina.sh', '-executable'] 12 | expectedOutput: ['catalina.sh'] 13 | - name: 'APP_DESTINATION_PATH is set' 14 | command: ['env'] 15 | expectedOutput: ['APP_DESTINATION_PATH=${tomcat.base}/webapps/'] 16 | - name: 'APP_WAR is set' 17 | command: ['env'] 18 | expectedOutput: ['APP_WAR=ROOT.war'] 19 | - name: 'APP_EXPLODED_WAR is set' 20 | command: ['env'] 21 | expectedOutput: ['APP_EXPLODED_WAR=ROOT/'] 22 | - name: 'APP_DESTINATION_WAR is set' 23 | command: ['env'] 24 | expectedOutput: ['APP_DESTINATION_WAR=${tomcat.base}/webapps/ROOT.war'] 25 | - name: 'APP_DESTINATION is set' 26 | command: ['env'] 27 | expectedOutput: ['APP_DESTINATION=${tomcat.base}/webapps/ROOT.war'] 28 | - name: 'APP_DESTINATION_EXPLODED_WAR is set' 29 | command: ['env'] 30 | expectedOutput: ['APP_DESTINATION_EXPLODED_WAR=${tomcat.base}/webapps/ROOT/'] 31 | - name: 'Check the configuration for the GAE environment' 32 | setup: [[ 'chmod', '+x', '/workspace/tomcat/src/test/workspace/tomcat-gae.bash' ]] 33 | command: [ '/workspace/tomcat/src/test/workspace/tomcat-gae.bash' ] 34 | expectedOutput: ['OK'] 35 | exitCode: 0 36 | - name: 'Check tomcat ROOT.war unpack' 37 | setup: [[ 'chmod', '+x', '/workspace/tomcat/src/test/workspace/tomcat-unpack.bash' ]] 38 | command: [ '/workspace/tomcat/src/test/workspace/tomcat-unpack.bash' ] 39 | expectedOutput: ['OK'] 40 | exitCode: 0 41 | 42 | fileExistenceTests: 43 | - name: 'Tomcat home exists' 44 | path: '${tomcat.home}' 45 | isDirectory: true 46 | shouldExist: true 47 | - name: 'Catalina script exists' 48 | path: '${tomcat.home}/bin/catalina.sh' 49 | isDirectory: false 50 | shouldExist: true 51 | - name: 'Tomcat base exists' 52 | path: '${tomcat.base}' 53 | isDirectory: true 54 | shouldExist: true 55 | - name: 'Webapps directory exists' 56 | path: '${tomcat.base}/webapps' 57 | isDirectory: true 58 | shouldExist: true 59 | - name: 'Catalina temp directory exists' 60 | path: '${tomcat.base}/temp' 61 | isDirectory: true 62 | shouldExist: true 63 | - name: 'Catalina properties exists' 64 | path: '${tomcat.base}/conf/catalina.properties' 65 | isDirectory: false 66 | shouldExist: true 67 | - name: 'Server configuration file exists in Catalina base' 68 | path: '${tomcat.base}/conf/server.xml' 69 | isDirectory: false 70 | shouldExist: true 71 | - name: 'Gcp configuration file should exists' 72 | path: '/config/gcp.xml' 73 | isDirectory: false 74 | shouldExist: true 75 | - name: 'Gcp configuration placeholder should exists' 76 | path: '${tomcat.base}/conf/gcp.xml' 77 | isDirectory: false 78 | shouldExist: true 79 | 80 | licenseTests: 81 | - debian: true 82 | files: [] 83 | -------------------------------------------------------------------------------- /tomcat/src/test/workspace/tomcat-gae.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PLATFORM="gae" 4 | 5 | # Test that the configuration for gcp is added into tomcat config 6 | source /setup-env.d/50-tomcat.bash 7 | if [ "$(cat /config/gcp.xml)" != "$(cat ${CATALINA_BASE}/conf/gcp.xml)" ]; then 8 | echo "gcp.xml should be copied from /config to catalina base" 9 | exit 1 10 | fi 11 | 12 | # Test the JAVA_OPTS for cloud debugging 13 | EXPECTED_DBG_AGENT="-agentpath:/opt/cdbg/cdbg_java_agent.so=--log_dir=/var/log/app_engine,--alsologtostderr=true,--cdbg_extra_class_path=${CATALINA_BASE}/webapps/ROOT/WEB-INF/classes:${CATALINA_BASE}/webapps/ROOT/WEB-INF/lib" 14 | ACTUAL_DBG_AGENT="$(export GAE_INSTANCE=instance; /docker-entrypoint.bash env | grep DBG_AGENT | cut -d '=' -f 1 --complement)" 15 | 16 | if [ "$ACTUAL_DBG_AGENT" != "$EXPECTED_DBG_AGENT" ]; then 17 | echo "DBG_AGENT='$(echo ${ACTUAL_DBG_AGENT})'" 18 | exit 1 19 | fi 20 | 21 | echo "OK" -------------------------------------------------------------------------------- /tomcat/src/test/workspace/tomcat-unpack.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Test that ROOT.war is unpacked so cloud debugging can use the class files within 4 | rm -fr $CATALINA_BASE/webapps/ROOT $CATALINA_BASE/webapps/ROOT.war 5 | trap "rm -rf $CATALINA_BASE/webapps/ROOT $CATALINA_BASE/webapps/ROOT.war" EXIT 6 | mkdir $CATALINA_BASE/webapps/ROOT/WEB-INF -p 7 | echo original > $CATALINA_BASE/webapps/ROOT/WEB-INF/web.xml 8 | cd $CATALINA_BASE/webapps/ROOT 9 | jar cf ../ROOT.war * 10 | cd .. 11 | 12 | rm -fr $CATALINA_BASE/webapps/ROOT 13 | source /setup-env.d/50-tomcat.bash 14 | 15 | if [ "$(cat $CATALINA_BASE/webapps/ROOT/WEB-INF/web.xml)" != "original" ]; then 16 | echo FAILED not unpacked when no ROOT 17 | exit 1 18 | fi 19 | 20 | echo updated > $CATALINA_BASE/webapps/ROOT/WEB-INF/web.xml 21 | source /setup-env.d/50-tomcat.bash 22 | if [ "$(cat $CATALINA_BASE/webapps/ROOT/WEB-INF/web.xml)" != "updated" ]; then 23 | echo FAILED unpacked when war older 24 | exit 1 25 | fi 26 | 27 | sleep 1 28 | 29 | touch $CATALINA_BASE/webapps/ROOT.war 30 | source /setup-env.d/50-tomcat.bash 31 | if [ "$(cat $CATALINA_BASE/webapps/ROOT/WEB-INF/web.xml)" != "original" ]; then 32 | echo FAILED not unpacked when war newer 33 | exit 1 34 | fi 35 | 36 | echo "OK" --------------------------------------------------------------------------------