Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
bd79d41
update mysql sample to include new region tags
shubha-rajan Apr 22, 2022
65b4ef7
add license header
shubha-rajan Apr 22, 2022
aa506d7
move connection code for TCP and connector to separate files
shubha-rajan Apr 26, 2022
d8f9198
add functions sample
shubha-rajan Apr 26, 2022
6e1ead5
add missing license headers
shubha-rajan Apr 26, 2022
09b8580
Update region tag
shubha-rajan Apr 26, 2022
b1915ee
reformat code
shubha-rajan Apr 26, 2022
a111d15
fix function sample
shubha-rajan Apr 26, 2022
34b7b4d
get function working
shubha-rajan Apr 26, 2022
0c2ef0f
update instructions for deploying to Functions
shubha-rajan Apr 26, 2022
dbf82e3
Update cloud-sql/mysql/servlet/.env.yaml
shubha-rajan Apr 27, 2022
2367b8e
add imports and environment variables to sample
shubha-rajan Apr 28, 2022
b51e07f
Update README.md
shubha-rajan Apr 28, 2022
0134a35
Update ConnectorConnectionPoolFactory.java
shubha-rajan Apr 28, 2022
9d992b3
formatting
shubha-rajan Apr 29, 2022
90016c2
Update cloud-sql/mysql/servlet/src/main/java/com/example/cloudsql/Con…
shubha-rajan Apr 29, 2022
8fc722f
Update TcpConnectionPoolFactory.java
shubha-rajan Apr 29, 2022
d78947c
Update cloud-sql/mysql/servlet/src/main/java/com/example/cloudsql/Tem…
shubha-rajan Apr 29, 2022
dc809d5
Factor out common methods in servlet and cloud function
shubha-rajan Apr 29, 2022
ff96d05
combine DatabaseSetup and InputValidator classes
shubha-rajan Apr 29, 2022
77b0a55
refactor cloud function to use initialization on demand
shubha-rajan Apr 29, 2022
e2e3183
formatting
shubha-rajan Apr 29, 2022
eeebe2e
Update ConnectorConnectionPoolFactory.java
shubha-rajan May 2, 2022
ba8e5c7
Update ConnectorConnectionPoolFactory.java
shubha-rajan May 2, 2022
b4b461c
Update TcpConnectionPoolFactory.java
shubha-rajan May 2, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions cloud-sql/mysql/servlet/.env.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
INSTANCE_CONNECTION_NAME: <PROJECT-ID>:<INSTANCE-REGION>:INSTANCE-NAME>
INSTANCE_UNIX_SOCKET: /cloudsql/<PROJECT-ID>:<INSTANCE-REGION>:INSTANCE-NAME>
INSTANCE_HOST: '127.0.0.1'
DB_PORT: 3306
DB_USER: <YOUR_DB_USER_NAME>
DB_PASS: <YOUR_DB_PASSWORD>
DB_NAME: <YOUR_DB_NAME>
69 changes: 68 additions & 1 deletion cloud-sql/mysql/servlet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,56 @@ export DB_NAME='my_db'
Note: Saving credentials in environment variables is convenient, but not secure - consider a more
secure solution such as [Cloud KMS](https://cloud.google.com/kms/) or [Secret Manager](https://cloud.google.com/secret-manager/) to help keep secrets safe.

## Configure SSL Certificates
For deployments that connect directly to a Cloud SQL instance with TCP,
without using the Cloud SQL Proxy,
configuring SSL certificates will ensure the connection is encrypted.
1. Use the gcloud CLI to [download the server certificate](https://cloud.google.com/sql/docs/mysql/configure-ssl-instance#server-certs) for your Cloud SQL instance.
- Get information about the service certificate:
```
gcloud beta sql ssl server-ca-certs list --instance=INSTANCE_NAME
```
- Create a server certificate:
```
gcloud beta sql ssl server-ca-certs create --instance=INSTANCE_NAME
```
- Download the certificate information to a local PEM file
```
gcloud beta sql ssl server-ca-certs list \
--format="value(cert)" \
--instance=INSTANCE_NAME > \
server-ca.pem
```

1. Use the gcloud CLI to [create and download a client public key certificate and client private key](https://cloud.google.com/sql/docs/mysql/configure-ssl-instance#client-certs)
- Create a client certificate using the ssl client-certs create command:
```
gcloud sql ssl client-certs create CERT_NAME client-key.pem --instance=INSTANCE_NAME
```
- Retrieve the public key for the certificate you just created and copy it into the client-cert.pem file with the ssl client-certs describe command:
Comment thread
enocom marked this conversation as resolved.
```
gcloud sql ssl client-certs describe CERT_NAME \
--instance=INSTANCE_NAME \
--format="value(cert)" > client-cert.pem
```
1. [Import the server certificate into a custom Java truststore](https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-using-ssl.html) using `keytool`:
```
keytool -importcert -alias MySQLCACert -file server-ca.pem \
-keystore <truststore-filename> -storepass <password>
```
1. Set the `TRUST_CERT_KEYSTORE_PATH` and `TRUST_CERT_KEYSTORE_PASSWD` environment variables to the values used in the previous step.
1. [Import the client certificate and key into a custom Java keystore](https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-using-ssl.html) using `openssl` and `keytool`:
- Convert the client key and certificate files to a PKCS #12 archive:
```
openssl pkcs12 -export -in client-cert.pem -inkey client-key.pem \
-name "mysqlclient" -passout pass:mypassword -out client-keystore.p12
```
- Import the client key and certificate into a Java keystore:
```
keytool -importkeystore -srckeystore client-keystore.p12 -srcstoretype pkcs12 \
-srcstorepass <password> -destkeystore <keystore-filename> -deststoretype JKS -deststorepass <password>
```
1. Set the `CLIENT_CERT_KEYSTORE_PATH` and `CLIENT_CERT_KEYSTORE_PASSWD` environment variables to the values used in the previous step.
## Deploying locally

To run this application locally, run the following command inside the project folder:
Expand All @@ -48,13 +98,19 @@ and verify that
has been added in your build section as a plugin.


### Development Server
### App Engine Development Server

The following command will run the application locally in the the GAE-development server:
```bash
mvn appengine:run
```

### Cloud Functions Development Server
To run the application locally as a Cloud Function, run the following command:
```
mvn function:run -Drun.functionTarget=com.example.cloudsql.functions.Main
```

### Deploy to Google App Engine

First, update `src/main/webapp/WEB-INF/appengine-web.xml` with the correct values to pass the
Expand Down Expand Up @@ -120,3 +176,14 @@ mvn clean package com.google.cloud.tools:jib-maven-plugin:2.8.0:build \

For more details about using Cloud Run see http://cloud.run.
Review other [Java on Cloud Run samples](../../../run/).

### Deploy to Google Cloud Functions

To deploy the application to Cloud Functions, first fill in the values for required environment variables in `.env.yaml`. Then run the following command
```
gcloud functions deploy mysql-sample \
--trigger-http \
--entry-point com.example.cloudsql.functions.Main \
--runtime java11 \
--env-vars-file .env.yaml
```
28 changes: 28 additions & 0 deletions cloud-sql/mysql/servlet/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,17 @@
<version>1.1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.cloud.functions.invoker</groupId>
<artifactId>java-function-invoker</artifactId>
<version>1.0.1</version>
</dependency>
<dependency>
<groupId>com.google.cloud.functions</groupId>
<artifactId>functions-framework-api</artifactId>
<version>1.0.4</version>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
Expand All @@ -114,6 +125,23 @@
<deploy.version>GCLOUD_CONFIG</deploy.version>
</configuration>
</plugin>
<plugin>
<!--
Google Cloud Functions Framework Maven plugin

This plugin allows you to run Cloud Functions Java code
locally. Use the following terminal command to run a
given function locally:

mvn function:run -Drun.functionTarget=your.package.yourFunction
-->
<groupId>com.google.cloud.functions</groupId>
<artifactId>function-maven-plugin</artifactId>
<version>0.10.0</version>
<configuration>
<functionTarget>com.example.cloudsql.functions.Main</functionTarget>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,8 @@

package com.example.cloudsql;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
Expand All @@ -34,101 +31,6 @@
@WebListener("Creates a connection pool that is stored in the Servlet's context for later use.")
public class ConnectionPoolContextListener implements ServletContextListener {

// Saving credentials in environment variables is convenient, but not secure - consider a more
// secure solution such as https://cloud.google.com/kms/ to help keep secrets safe.
private static final String INSTANCE_CONNECTION_NAME =
System.getenv("INSTANCE_CONNECTION_NAME");
private static final String DB_USER = System.getenv("DB_USER");
private static final String DB_PASS = System.getenv("DB_PASS");
private static final String DB_NAME = System.getenv("DB_NAME");

@SuppressFBWarnings(
value = "USBR_UNNECESSARY_STORE_BEFORE_RETURN",
justification = "Necessary for sample region tag.")
private DataSource createConnectionPool() {
// [START cloud_sql_mysql_servlet_create]
// Note: For Java users, the Cloud SQL JDBC Socket Factory can provide authenticated connections
// which is preferred to using the Cloud SQL Proxy with Unix sockets.
// See https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory for details.

// The configuration object specifies behaviors for the connection pool.
HikariConfig config = new HikariConfig();

// The following URL is equivalent to setting the config options below:
// jdbc:mysql:///<DB_NAME>?cloudSqlInstance=<INSTANCE_CONNECTION_NAME>&
// socketFactory=com.google.cloud.sql.mysql.SocketFactory&user=<DB_USER>&password=<DB_PASS>
// See the link below for more info on building a JDBC URL for the Cloud SQL JDBC Socket Factory
// https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory#creating-the-jdbc-url

// Configure which instance and what database user to connect with.
config.setJdbcUrl(String.format("jdbc:mysql:///%s", DB_NAME));
config.setUsername(DB_USER); // e.g. "root", "mysql"
config.setPassword(DB_PASS); // e.g. "my-password"

config.addDataSourceProperty("socketFactory", "com.google.cloud.sql.mysql.SocketFactory");
config.addDataSourceProperty("cloudSqlInstance", INSTANCE_CONNECTION_NAME);

// The ipTypes argument can be used to specify a comma delimited list of preferred IP types
// for connecting to a Cloud SQL instance. The argument ipTypes=PRIVATE will force the
// SocketFactory to connect with an instance's associated private IP.
config.addDataSourceProperty("ipTypes", "PUBLIC,PRIVATE");

// ... Specify additional connection properties here.
// [START_EXCLUDE]

// [START cloud_sql_mysql_servlet_limit]
// maximumPoolSize limits the total number of concurrent connections this pool will keep. Ideal
// values for this setting are highly variable on app design, infrastructure, and database.
config.setMaximumPoolSize(5);
// minimumIdle is the minimum number of idle connections Hikari maintains in the pool.
// Additional connections will be established to meet this value unless the pool is full.
config.setMinimumIdle(5);
// [END cloud_sql_mysql_servlet_limit]

// [START cloud_sql_mysql_servlet_timeout]
// setConnectionTimeout is the maximum number of milliseconds to wait for a connection checkout.
// Any attempt to retrieve a connection from this pool that exceeds the set limit will throw an
// SQLException.
config.setConnectionTimeout(10000); // 10 seconds
// idleTimeout is the maximum amount of time a connection can sit in the pool. Connections that
// sit idle for this many milliseconds are retried if minimumIdle is exceeded.
config.setIdleTimeout(600000); // 10 minutes
// [END cloud_sql_mysql_servlet_timeout]

// [START cloud_sql_mysql_servlet_backoff]
// Hikari automatically delays between failed connection attempts, eventually reaching a
// maximum delay of `connectionTimeout / 2` between attempts.
// [END cloud_sql_mysql_servlet_backoff]

// [START cloud_sql_mysql_servlet_lifetime]
// maxLifetime is the maximum possible lifetime of a connection in the pool. Connections that
// live longer than this many milliseconds will be closed and reestablished between uses. This
// value should be several minutes shorter than the database's timeout value to avoid unexpected
// terminations.
config.setMaxLifetime(1800000); // 30 minutes
// [END cloud_sql_mysql_servlet_lifetime]

// [END_EXCLUDE]

// Initialize the connection pool using the configuration object.
DataSource pool = new HikariDataSource(config);
// [END cloud_sql_mysql_servlet_create]
return pool;
}

private void createTable(DataSource pool) throws SQLException {
// Safely attempt to create the table schema.
try (Connection conn = pool.getConnection()) {
String stmt =
"CREATE TABLE IF NOT EXISTS votes ( "
+ "vote_id SERIAL NOT NULL, time_cast timestamp NOT NULL, candidate CHAR(6) NOT NULL,"
+ " PRIMARY KEY (vote_id) );";
try (PreparedStatement createTableStatement = conn.prepareStatement(stmt);) {
createTableStatement.execute();
}
}
}

@Override
public void contextDestroyed(ServletContextEvent event) {
// This function is called when the Servlet is destroyed.
Expand All @@ -145,11 +47,15 @@ public void contextInitialized(ServletContextEvent event) {
ServletContext servletContext = event.getServletContext();
DataSource pool = (DataSource) servletContext.getAttribute("my-pool");
if (pool == null) {
pool = createConnectionPool();
if (System.getenv("INSTANCE_HOST") != null) {
pool = TcpConnectionPoolFactory.createConnectionPool();
} else {
pool = ConnectorConnectionPoolFactory.createConnectionPool();
}
servletContext.setAttribute("my-pool", pool);
}
try {
createTable(pool);
Utils.createTable(pool);
} catch (SQLException ex) {
throw new RuntimeException(
"Unable to verify table schema. Please double check the steps"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.cloudsql;

import com.zaxxer.hikari.HikariConfig;

public class ConnectionPoolFactory {

public static HikariConfig configureConnectionPool(HikariConfig config) {
// [START cloud_sql_mysql_servlet_limit]
// maximumPoolSize limits the total number of concurrent connections this pool will keep. Ideal
// values for this setting are highly variable on app design, infrastructure, and database.
config.setMaximumPoolSize(5);
// minimumIdle is the minimum number of idle connections Hikari maintains in the pool.
// Additional connections will be established to meet this value unless the pool is full.
config.setMinimumIdle(5);
// [END cloud_sql_mysql_servlet_limit]

// [START cloud_sql_mysql_servlet_timeout]
// setConnectionTimeout is the maximum number of milliseconds to wait for a connection checkout.
// Any attempt to retrieve a connection from this pool that exceeds the set limit will throw an
// SQLException.
config.setConnectionTimeout(10000); // 10 seconds
// idleTimeout is the maximum amount of time a connection can sit in the pool. Connections that
// sit idle for this many milliseconds are retried if minimumIdle is exceeded.
config.setIdleTimeout(600000); // 10 minutes
// [END cloud_sql_mysql_servlet_timeout]

// [START cloud_sql_mysql_servlet_backoff]
// Hikari automatically delays between failed connection attempts, eventually reaching a
// maximum delay of `connectionTimeout / 2` between attempts.
// [END cloud_sql_mysql_servlet_backoff]

// [START cloud_sql_mysql_servlet_lifetime]
// maxLifetime is the maximum possible lifetime of a connection in the pool. Connections that
// live longer than this many milliseconds will be closed and reestablished between uses. This
// value should be several minutes shorter than the database's timeout value to avoid unexpected
// terminations.
config.setMaxLifetime(1800000); // 30 minutes
// [END cloud_sql_mysql_servlet_lifetime]
return config;
}
}
Loading