From 59e39c0b8814d76acf535aab844f7c8ca597d2d3 Mon Sep 17 00:00:00 2001 From: Warren Zhu Date: Thu, 21 Jun 2018 14:35:11 +0800 Subject: [PATCH] Add spring cloud azure sql auto config for SqlServer --- spring-cloud-azure-autoconfigure/pom.xml | 37 +++++++++ ...stractJdbcDatasourcePropertiesUpdater.java | 67 +++++++++++++++ .../sql/AzureSqlAutoConfiguration.java | 81 +++++++++++++++++++ .../autoconfigure/sql/AzureSqlProperties.java | 44 ++++++++++ .../cloud/autoconfigure/sql/DatabaseType.java | 33 ++++++++ .../sql/JdbcDataSourcePropertiesUpdater.java | 19 +++++ ...ServerJdbcDataSourcePropertiesUpdater.java | 45 +++++++++++ .../main/resources/META-INF/spring.factories | 3 +- .../spring/cloud/context/core/AzureAdmin.java | 36 +++++++++ 9 files changed, 364 insertions(+), 1 deletion(-) create mode 100644 spring-cloud-azure-autoconfigure/src/main/java/com/microsoft/azure/spring/cloud/autoconfigure/sql/AbstractJdbcDatasourcePropertiesUpdater.java create mode 100644 spring-cloud-azure-autoconfigure/src/main/java/com/microsoft/azure/spring/cloud/autoconfigure/sql/AzureSqlAutoConfiguration.java create mode 100644 spring-cloud-azure-autoconfigure/src/main/java/com/microsoft/azure/spring/cloud/autoconfigure/sql/AzureSqlProperties.java create mode 100644 spring-cloud-azure-autoconfigure/src/main/java/com/microsoft/azure/spring/cloud/autoconfigure/sql/DatabaseType.java create mode 100644 spring-cloud-azure-autoconfigure/src/main/java/com/microsoft/azure/spring/cloud/autoconfigure/sql/JdbcDataSourcePropertiesUpdater.java create mode 100644 spring-cloud-azure-autoconfigure/src/main/java/com/microsoft/azure/spring/cloud/autoconfigure/sql/SqlServerJdbcDataSourcePropertiesUpdater.java diff --git a/spring-cloud-azure-autoconfigure/pom.xml b/spring-cloud-azure-autoconfigure/pom.xml index 68f0bbeae..73fd9e4bb 100644 --- a/spring-cloud-azure-autoconfigure/pom.xml +++ b/spring-cloud-azure-autoconfigure/pom.xml @@ -25,17 +25,54 @@ ${project.version} + com.microsoft.azure spring-cloud-azure-eventhub ${project.version} + com.microsoft.azure spring-cloud-azure-storage ${project.version} + + + + org.springframework + spring-jdbc + true + + + + com.zaxxer + HikariCP + test + + + + + org.postgresql + postgresql + true + + + + + mysql + mysql-connector-java + true + + + + + com.microsoft.sqlserver + mssql-jdbc + true + + \ No newline at end of file diff --git a/spring-cloud-azure-autoconfigure/src/main/java/com/microsoft/azure/spring/cloud/autoconfigure/sql/AbstractJdbcDatasourcePropertiesUpdater.java b/spring-cloud-azure-autoconfigure/src/main/java/com/microsoft/azure/spring/cloud/autoconfigure/sql/AbstractJdbcDatasourcePropertiesUpdater.java new file mode 100644 index 000000000..2473e1cc3 --- /dev/null +++ b/spring-cloud-azure-autoconfigure/src/main/java/com/microsoft/azure/spring/cloud/autoconfigure/sql/AbstractJdbcDatasourcePropertiesUpdater.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for + * license information. + */ + +package com.microsoft.azure.spring.cloud.autoconfigure.sql; + +import com.microsoft.azure.spring.cloud.context.core.AzureAdmin; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Base class of {@link JdbcDataSourcePropertiesUpdater} + * + * @author Warren Zhu + */ +public abstract class AbstractJdbcDatasourcePropertiesUpdater implements JdbcDataSourcePropertiesUpdater { + protected static final Log LOGGER = LogFactory.getLog(SqlServerJdbcDataSourcePropertiesUpdater.class); + + protected final DatabaseType databaseType; + protected final AzureSqlProperties azureSqlProperties; + protected final AzureAdmin azureAdmin; + + public AbstractJdbcDatasourcePropertiesUpdater(AzureSqlProperties azureSqlProperties, DatabaseType databaseType, + AzureAdmin azureAdmin) { + this.azureSqlProperties = azureSqlProperties; + this.databaseType = databaseType; + this.azureAdmin = azureAdmin; + Assert.hasText(this.azureSqlProperties.getDatabaseName(), "A database name must be provided."); + Assert.hasText(this.azureSqlProperties.getServerName(), "A database server must be provided."); + } + + @Override + public void updateDataSourceProperties(DataSourceProperties dataSourceProperties) { + Assert.hasText(dataSourceProperties.getPassword(), "spring.datasource.username must not be empty"); + + if (StringUtils.isEmpty(dataSourceProperties.getUsername())) { + dataSourceProperties.setUsername(getUserName()); + LOGGER.info(String.format("spring.datasource.username is auto config into '%s'", + getUserName())); + } + + if (StringUtils.isEmpty(dataSourceProperties.getDriverClassName())) { + dataSourceProperties.setDriverClassName(getDriverClass()); + } else { + LOGGER.warn("spring.datasource.driver-class-name is specified. " + + "Not using generated Cloud SQL configuration"); + } + + if (StringUtils.isEmpty(dataSourceProperties.getUrl())) { + dataSourceProperties.setUrl(getUrl(dataSourceProperties)); + } else { + LOGGER.warn("spring.datasource.url is specified. " + "Not using generated Cloud SQL configuration"); + } + } + + String getDriverClass(){ + return databaseType.getJdbcDriverName(); + } + + abstract String getUserName(); + abstract String getUrl(DataSourceProperties dataSourceProperties); +} diff --git a/spring-cloud-azure-autoconfigure/src/main/java/com/microsoft/azure/spring/cloud/autoconfigure/sql/AzureSqlAutoConfiguration.java b/spring-cloud-azure-autoconfigure/src/main/java/com/microsoft/azure/spring/cloud/autoconfigure/sql/AzureSqlAutoConfiguration.java new file mode 100644 index 000000000..835f82d5f --- /dev/null +++ b/spring-cloud-azure-autoconfigure/src/main/java/com/microsoft/azure/spring/cloud/autoconfigure/sql/AzureSqlAutoConfiguration.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for + * license information. + */ + +package com.microsoft.azure.spring.cloud.autoconfigure.sql; + +import com.microsoft.azure.spring.cloud.autoconfigure.context.AzureContextAutoConfiguration; +import com.microsoft.azure.spring.cloud.context.core.AzureAdmin; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; +import org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Primary; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; + +import javax.sql.DataSource; + +/** + * Provides Azure SQL instance connectivity through Spring JDBC by providing + * database server name and database name. + * + * @author Warren Zhu + */ +@Configuration +@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class}) +@ConditionalOnProperty(name = "spring.cloud.azure.sql.enabled", havingValue = "true", matchIfMissing = true) +@EnableConfigurationProperties({AzureSqlProperties.class, DataSourceProperties.class}) +@AutoConfigureBefore({DataSourceAutoConfiguration.class, JndiDataSourceAutoConfiguration.class, + XADataSourceAutoConfiguration.class}) +@AutoConfigureAfter(AzureContextAutoConfiguration.class) +public class AzureSqlAutoConfiguration { + + /** + * The Sql Server Configuration for the {@link SqlServerJdbcDataSourcePropertiesUpdater} + * based on the {@link DatabaseType#SQLSERVER}. + */ + @ConditionalOnClass(com.microsoft.sqlserver.jdbc.SQLServerDriver.class) + @ConditionalOnMissingBean(JdbcDataSourcePropertiesUpdater.class) + static class SqlServerJdbcInfoProviderConfiguration { + + @Bean + public JdbcDataSourcePropertiesUpdater defaultSqlServerJdbcInfoProvider(AzureSqlProperties + azureSqlProperties, AzureAdmin azureAdmin) { + return new SqlServerJdbcDataSourcePropertiesUpdater(azureSqlProperties, azureAdmin); + } + } + + /** + * The Configuration to populated {@link DataSourceProperties} bean + * based on the cloud-specific properties. + */ + @Configuration + @Import({SqlServerJdbcInfoProviderConfiguration.class}) + static class CloudSqlDataSourcePropertiesConfiguration { + + @Bean + @Primary + @ConditionalOnBean(JdbcDataSourcePropertiesUpdater.class) + public DataSourceProperties cloudSqlDataSourceProperties( + DataSourceProperties dataSourceProperties, + JdbcDataSourcePropertiesUpdater dataSourcePropertiesProvider) { + + dataSourcePropertiesProvider.updateDataSourceProperties(dataSourceProperties); + + return dataSourceProperties; + } + } +} + diff --git a/spring-cloud-azure-autoconfigure/src/main/java/com/microsoft/azure/spring/cloud/autoconfigure/sql/AzureSqlProperties.java b/spring-cloud-azure-autoconfigure/src/main/java/com/microsoft/azure/spring/cloud/autoconfigure/sql/AzureSqlProperties.java new file mode 100644 index 000000000..f364a34c2 --- /dev/null +++ b/spring-cloud-azure-autoconfigure/src/main/java/com/microsoft/azure/spring/cloud/autoconfigure/sql/AzureSqlProperties.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for + * license information. + */ + +package com.microsoft.azure.spring.cloud.autoconfigure.sql; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Azure SQL properties. + * + * @author Warren Zhu + */ +@ConfigurationProperties("spring.cloud.azure.sql") +public class AzureSqlProperties { + + /** + * Name of the database in the Azure SQL instance. + */ + private String databaseName; + + /** + * Name of the database server in the Azure SQL instance. + */ + private String serverName; + + public String getServerName() { + return serverName; + } + + public void setServerName(String serverName) { + this.serverName = serverName; + } + + public String getDatabaseName() { + return this.databaseName; + } + + public void setDatabaseName(String databaseName) { + this.databaseName = databaseName; + } +} diff --git a/spring-cloud-azure-autoconfigure/src/main/java/com/microsoft/azure/spring/cloud/autoconfigure/sql/DatabaseType.java b/spring-cloud-azure-autoconfigure/src/main/java/com/microsoft/azure/spring/cloud/autoconfigure/sql/DatabaseType.java new file mode 100644 index 000000000..6a15585f8 --- /dev/null +++ b/spring-cloud-azure-autoconfigure/src/main/java/com/microsoft/azure/spring/cloud/autoconfigure/sql/DatabaseType.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for + * license information. + */ + +package com.microsoft.azure.spring.cloud.autoconfigure.sql; + +/** + * @author Warren Zhu + */ +public enum DatabaseType { + SQLSERVER("com.microsoft.sqlserver.jdbc.SQLServerDriver", "jdbc:sqlserver://%s:1433;database=%s;" + + "encrypt=true;trustServerCertificate=false;hostNameInCertificate=*.database.windows.net;loginTimeout=30;"); + + private final String jdbcDriverName; + + private final String jdbcUrlTemplate; + + DatabaseType(String jdbcDriverName, String jdbcUrlTemplate) { + this.jdbcDriverName = jdbcDriverName; + this.jdbcUrlTemplate = jdbcUrlTemplate; + } + + public String getJdbcDriverName() { + return this.jdbcDriverName; + } + + public String getJdbcUrlTemplate() { + return this.jdbcUrlTemplate; + } + +} diff --git a/spring-cloud-azure-autoconfigure/src/main/java/com/microsoft/azure/spring/cloud/autoconfigure/sql/JdbcDataSourcePropertiesUpdater.java b/spring-cloud-azure-autoconfigure/src/main/java/com/microsoft/azure/spring/cloud/autoconfigure/sql/JdbcDataSourcePropertiesUpdater.java new file mode 100644 index 000000000..2fb9c323a --- /dev/null +++ b/spring-cloud-azure-autoconfigure/src/main/java/com/microsoft/azure/spring/cloud/autoconfigure/sql/JdbcDataSourcePropertiesUpdater.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for + * license information. + */ + +package com.microsoft.azure.spring.cloud.autoconfigure.sql; + +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; + +/** + * Update {@link DataSourceProperties} based on {@link AzureSqlProperties} + * + * @author Warren Zhu + */ +public interface JdbcDataSourcePropertiesUpdater { + + void updateDataSourceProperties(DataSourceProperties dataSourceProperties); +} diff --git a/spring-cloud-azure-autoconfigure/src/main/java/com/microsoft/azure/spring/cloud/autoconfigure/sql/SqlServerJdbcDataSourcePropertiesUpdater.java b/spring-cloud-azure-autoconfigure/src/main/java/com/microsoft/azure/spring/cloud/autoconfigure/sql/SqlServerJdbcDataSourcePropertiesUpdater.java new file mode 100644 index 000000000..230838ef9 --- /dev/null +++ b/spring-cloud-azure-autoconfigure/src/main/java/com/microsoft/azure/spring/cloud/autoconfigure/sql/SqlServerJdbcDataSourcePropertiesUpdater.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for + * license information. + */ + +package com.microsoft.azure.spring.cloud.autoconfigure.sql; + +import com.microsoft.azure.management.sql.SqlServer; +import com.microsoft.azure.spring.cloud.context.core.AzureAdmin; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; + +/** + * @author Warren Zhu + */ +public class SqlServerJdbcDataSourcePropertiesUpdater extends AbstractJdbcDatasourcePropertiesUpdater + implements JdbcDataSourcePropertiesUpdater { + + public SqlServerJdbcDataSourcePropertiesUpdater(AzureSqlProperties azureSqlProperties, AzureAdmin azureAdmin) { + super(azureSqlProperties, DatabaseType.SQLSERVER, azureAdmin); + } + + @Override + String getUserName() { + SqlServer sqlServer = azureAdmin.getSqlServer(azureSqlProperties.getServerName()); + if (sqlServer == null) { + throw new IllegalArgumentException("SqlServer not found. If you want to auto create sqlServer. Please" + + " provide username and password"); + } + + return sqlServer.administratorLogin(); + } + + @Override + String getUrl(DataSourceProperties dataSourceProperties) { + SqlServer sqlServer = + azureAdmin.getOrCreateSqlServer(azureSqlProperties.getServerName(), dataSourceProperties.getUsername(), + dataSourceProperties.getPassword()); + azureAdmin.createSqlDatabaseIfNotExists(azureSqlProperties.getServerName(), + azureSqlProperties.getDatabaseName()); + return String + .format(databaseType.getJdbcUrlTemplate(), sqlServer.fullyQualifiedDomainName(), + azureSqlProperties.getDatabaseName()); + } +} diff --git a/spring-cloud-azure-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-cloud-azure-autoconfigure/src/main/resources/META-INF/spring.factories index 7d0304ece..58760ad56 100644 --- a/spring-cloud-azure-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-azure-autoconfigure/src/main/resources/META-INF/spring.factories @@ -2,6 +2,7 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.microsoft.azure.spring.cloud.autoconfigure.context.AzureContextAutoConfiguration,\ com.microsoft.azure.spring.cloud.autoconfigure.cache.AzureRedisAutoConfiguration,\ com.microsoft.azure.spring.cloud.autoconfigure.storage.AzureStorageAutoConfiguration,\ -com.microsoft.azure.spring.cloud.autoconfigure.eventhub.AzureEventHubAutoConfiguration +com.microsoft.azure.spring.cloud.autoconfigure.eventhub.AzureEventHubAutoConfiguration,\ +com.microsoft.azure.spring.cloud.autoconfigure.sql.AzureSqlAutoConfiguration diff --git a/spring-cloud-azure-context/src/main/java/com/microsoft/azure/spring/cloud/context/core/AzureAdmin.java b/spring-cloud-azure-context/src/main/java/com/microsoft/azure/spring/cloud/context/core/AzureAdmin.java index 5a210b00e..198e90f1a 100644 --- a/spring-cloud-azure-context/src/main/java/com/microsoft/azure/spring/cloud/context/core/AzureAdmin.java +++ b/spring-cloud-azure-context/src/main/java/com/microsoft/azure/spring/cloud/context/core/AzureAdmin.java @@ -9,6 +9,7 @@ import com.microsoft.azure.management.Azure; import com.microsoft.azure.management.eventhub.EventHub; import com.microsoft.azure.management.eventhub.EventHubNamespace; +import com.microsoft.azure.management.sql.SqlServer; import com.microsoft.azure.management.storage.StorageAccount; import org.springframework.util.Assert; @@ -93,4 +94,39 @@ public boolean eventHubConsumerGroupExists(String namespace, String name, String return azure.eventHubs().getByName(resourceGroup, namespace, name).listConsumerGroups().stream() .anyMatch(c -> c.equals(group)); } + + public void createSqlDatabaseIfNotExists(String sqlServerName, String databaseName) { + if (!sqlDatabaseExists(sqlServerName, databaseName)) { + azure.sqlServers().databases().define(databaseName) + .withExistingSqlServer(resourceGroup, sqlServerName, region).create(); + } + } + + public void createSqlServer(String sqlServerName, String username, String password) { + azure.sqlServers().define(sqlServerName).withRegion(region).withExistingResourceGroup(resourceGroup) + .withAdministratorLogin(username).withAdministratorPassword(password).create(); + } + + public SqlServer getOrCreateSqlServer(String sqlServerName, String username, String password) { + SqlServer sqlServer = getSqlServer(sqlServerName); + + if (sqlServer != null) { + return sqlServer; + } + + return azure.sqlServers().define(sqlServerName).withRegion(region).withExistingResourceGroup(resourceGroup) + .withAdministratorLogin(username).withAdministratorPassword(password).create(); + } + + public SqlServer getSqlServer(String sqlServerName) { + return azure.sqlServers().getByResourceGroup(resourceGroup, sqlServerName); + } + + public boolean sqlDatabaseExists(String sqlServerName, String databaseName) { + return azure.sqlServers().databases().getBySqlServer(resourceGroup, sqlServerName, databaseName) == null; + } + + public boolean sqlServerExists(String sqlServerName) { + return azure.sqlServers().getByResourceGroup(resourceGroup, sqlServerName) == null; + } }