/*
* Copyright (c) 2015 Google, Inc.
*
* 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.cloudera.director.google.sql;
import static com.cloudera.director.google.sql.GoogleCloudSQLInstanceTemplateConfigurationProperty.ENGINE;
import static com.cloudera.director.google.sql.GoogleCloudSQLInstanceTemplateConfigurationProperty.MASTER_USER_PASSWORD;
import static com.cloudera.director.google.sql.GoogleCloudSQLInstanceTemplateConfigurationProperty.MASTER_USERNAME;
import static com.cloudera.director.google.sql.GoogleCloudSQLInstanceTemplateConfigurationProperty.PREFERRED_LOCATION;
import static com.cloudera.director.google.sql.GoogleCloudSQLInstanceTemplateConfigurationProperty.TIER;
import static com.cloudera.director.google.sql.GoogleCloudSQLProviderConfigurationProperty.REGION;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import com.cloudera.director.google.TestFixture;
import com.cloudera.director.google.TestUtils;
import com.cloudera.director.google.internal.GoogleCredentials;
import com.cloudera.director.google.shaded.com.typesafe.config.Config;
import com.cloudera.director.spi.v1.database.DatabaseServerInstance;
import com.cloudera.director.spi.v1.model.Configured;
import com.cloudera.director.spi.v1.model.ConfigurationProperty;
import com.cloudera.director.spi.v1.model.ConfigurationValidator;
import com.cloudera.director.spi.v1.model.InstanceState;
import com.cloudera.director.spi.v1.model.InstanceStatus;
import com.cloudera.director.spi.v1.model.exception.PluginExceptionConditionAccumulator;
import com.cloudera.director.spi.v1.model.exception.PluginExceptionDetails;
import com.cloudera.director.spi.v1.model.exception.UnrecoverableProviderException;
import com.cloudera.director.spi.v1.model.util.DefaultLocalizationContext;
import com.cloudera.director.spi.v1.model.util.SimpleConfiguration;
import com.cloudera.director.spi.v1.provider.ResourceProviderMetadata;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Logger;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;
/**
* Performs 'live' tests of the full cycle of {@link GoogleCloudSQLProvider}: allocate, getInstanceState, find, delete.
*
* This property is required: GCP_PROJECT_ID.
* These two system properties are optional: JSON_KEY_PATH, HALT_AFTER_ALLOCATION.
*
* If JSON_KEY_PATH is not specified, Application Default Credentials will be used.
*
* @see <a href="https://developers.google.com/identity/protocols/application-default-credentials"</a>
*/
public class GoogleCloudSQLProviderFullCycleTest {
private static final Logger LOG = Logger.getLogger(GoogleCloudSQLProviderFullCycleTest.class.getName());
private static final DefaultLocalizationContext DEFAULT_LOCALIZATION_CONTEXT =
new DefaultLocalizationContext(Locale.getDefault(), "");
private static final int POLLING_INTERVAL_SECONDS = 5;
private static final String MY_REGION_NAME = "us-central";
private static final String MY_TIER = "D4";
private static final String MY_USER_PASSWORD = "admin";
private static final String MY_USERNAME = "admin";
private static final String MY_DATABASE_TYPE = "MYSQL";
private static final String MY_PREFERRED_LOCATION = "us-central1-f";
private static TestFixture testFixture;
@BeforeClass
public static void beforeClass() throws IOException {
Assume.assumeFalse(System.getProperty("GCP_PROJECT_ID", "").isEmpty());
testFixture = TestFixture.newTestFixture(false);
}
public GoogleCloudSQLProviderFullCycleTest() {}
@Test
public void testFullCycle() throws InterruptedException, IOException {
// Retrieve and list out the provider configuration properties for Google Cloud SQL provider.
ResourceProviderMetadata sqlMetadata = GoogleCloudSQLProvider.METADATA;
LOG.info("Configurations required for 'sql' resource provider:");
for (ConfigurationProperty property :
sqlMetadata.getProviderConfigurationProperties()) {
LOG.info(property.getName(DEFAULT_LOCALIZATION_CONTEXT));
}
// Prepare configuration for Google Cloud SQL provider.
Map<String, String> sqlAdminConfig = new HashMap<String, String>();
sqlAdminConfig.put(REGION.unwrap().getConfigKey(), MY_REGION_NAME);
Configured resourceProviderConfiguration = new SimpleConfiguration(sqlAdminConfig);
// Create Google credentials for use by both the validator and the provider.
Config applicationPropertiesConfig = TestUtils.buildApplicationPropertiesConfig();
GoogleCredentials credentials = new GoogleCredentials(applicationPropertiesConfig, testFixture.getProjectId(),
testFixture.getJsonKey());
// Validate the Google Cloud SQL provider configuration.
LOG.info("About to validate the resource provider configuration...");
ConfigurationValidator resourceProviderValidator = new GoogleCloudSQLProviderConfigurationValidator(credentials);
PluginExceptionConditionAccumulator accumulator = new PluginExceptionConditionAccumulator();
resourceProviderValidator.validate("resource provider configuration", resourceProviderConfiguration, accumulator,
DEFAULT_LOCALIZATION_CONTEXT);
assertFalse(accumulator.getConditionsByKey().toString(), accumulator.hasError());
// Create the Google Cloud SQL provider.
GoogleCloudSQLProvider sqlAdmin = new GoogleCloudSQLProvider(resourceProviderConfiguration, credentials,
applicationPropertiesConfig, TestUtils.buildGoogleConfig(), DEFAULT_LOCALIZATION_CONTEXT);
// Retrieve and list out the resource template configuration properties for Google Cloud SQL provider.
LOG.info("Configurations required for template:");
for (ConfigurationProperty property :
sqlMetadata.getResourceTemplateConfigurationProperties()) {
LOG.info(property.getName(DEFAULT_LOCALIZATION_CONTEXT));
}
// Prepare configuration for resource template.
Map<String, String> templateConfig = new HashMap<String, String>();
templateConfig.put(TIER.unwrap().getConfigKey(), MY_TIER);
templateConfig.put(MASTER_USERNAME.unwrap().getConfigKey(), MY_USERNAME);
templateConfig.put(MASTER_USER_PASSWORD.unwrap().getConfigKey(), MY_USER_PASSWORD);
templateConfig.put(ENGINE.unwrap().getConfigKey(), MY_DATABASE_TYPE);
templateConfig.put(PREFERRED_LOCATION.unwrap().getConfigKey(), MY_PREFERRED_LOCATION);
Map<String, String> tags = new HashMap<String, String>();
tags.put("test-tag-1", "some-value-1");
tags.put("test-tag-2", "some-value-2");
Configured templateConfiguration = new SimpleConfiguration(templateConfig);
// Validate the template configuration.
LOG.info("About to validate the template configuration...");
ConfigurationValidator templateConfigurationValidator = sqlAdmin.getResourceTemplateConfigurationValidator();
accumulator = new PluginExceptionConditionAccumulator();
templateConfigurationValidator.validate("instance-resource-template", templateConfiguration, accumulator,
DEFAULT_LOCALIZATION_CONTEXT);
assertFalse(accumulator.getConditionsByKey().toString(), accumulator.hasError());
// Create the resource template.
GoogleCloudSQLInstanceTemplate template =
sqlAdmin.createResourceTemplate("template-1", templateConfiguration, tags);
assertNotNull(template);
List<String> instanceIds = Arrays.asList(UUID.randomUUID().toString());
// Verify that instances are not created.
Collection<GoogleCloudSQLInstance> instances = sqlAdmin.find(template, instanceIds);
assertEquals(0, instances.size());
Map<String, InstanceState> instanceStates = sqlAdmin.getInstanceState(template, instanceIds);
assertEquals(instanceIds.size(), instanceStates.size());
// Use the template to provision one resource.
LOG.info("About to provision an instance...");
try {
sqlAdmin.allocate(template, instanceIds, 1);
} catch (UnrecoverableProviderException e) {
PluginExceptionDetails details = e.getDetails();
if (details != null) {
LOG.info("Caught on allocate(): " + details.getConditionsByKey());
}
throw e;
}
// Run a find by ID.
LOG.info("About to lookup an instance...");
instances = sqlAdmin.find(template, instanceIds);
assertEquals(1, instances.size());
// Verify that no exception is thrown.
instanceStates = sqlAdmin.getInstanceState(template, instanceIds);
assertEquals(instanceIds.size(), instanceStates.size());
for (DatabaseServerInstance foundInstance : instances) {
LOG.info("Found instance '" + foundInstance.getId() + "' with private ip " +
foundInstance.getPrivateIpAddress() + ".");
}
// Verify the id of the returned instance.
DatabaseServerInstance instance = instances.iterator().next();
assertEquals(instanceIds.get(0), instance.getId());
// Use the template to request creation of the same resource again.
LOG.info("About to provision the same instance again...");
sqlAdmin.allocate(template, instanceIds, 1);
instances = sqlAdmin.find(template, instanceIds);
assertEquals(1, instances.size());
// Verify that no exception is thrown.
instanceStates = sqlAdmin.getInstanceState(template, instanceIds);
assertEquals(instanceIds.size(), instanceStates.size());
// Verify the id of the returned instance.
instance = instances.iterator().next();
assertEquals(instanceIds.get(0), instance.getId());
// Query the instance state until the instance status is RUNNING.
pollInstanceState(sqlAdmin, template, instanceIds, InstanceStatus.RUNNING);
// List all display properties.
LOG.info("Display properties:");
Map<String, String> displayPropertyMap = instance.getProperties();
for (Map.Entry<String, String> keyValuePair : displayPropertyMap.entrySet()) {
LOG.info(" " + keyValuePair.getKey() + " -> " + keyValuePair.getValue());
}
if (testFixture.getHaltAfterAllocation()) {
LOG.info("HALT_AFTER_ALLOCATION flag is set.");
return;
}
// Delete the resources.
LOG.info("About to delete an instance...");
sqlAdmin.delete(template, instanceIds);
// Query the instance state again until the instance status is UNKNOWN.
pollInstanceState(sqlAdmin, template, instanceIds, InstanceStatus.UNKNOWN);
// Verify that the instance has been deleted.
instances = sqlAdmin.find(template, instanceIds);
assertEquals(0, instances.size());
// Verify that no exception is thrown.
instanceStates = sqlAdmin.getInstanceState(template, instanceIds);
assertEquals(instanceIds.size(), instanceStates.size());
LOG.info("About to delete the same instance again...");
try {
sqlAdmin.delete(template, instanceIds);
} catch (UnrecoverableProviderException e) {
PluginExceptionDetails details = e.getDetails();
if (details != null) {
LOG.info("Caught on delete():" + details.getConditionsByKey());
}
throw e;
}
}
private static void pollInstanceState(
GoogleCloudSQLProvider sqlAdmin,
GoogleCloudSQLInstanceTemplate template,
List<String> instanceIds,
InstanceStatus desiredStatus) throws InterruptedException {
// Query the instance state until the instance status matches desiredStatus.
LOG.info("About to query instance state until " + desiredStatus + "...");
Map<String, InstanceState> idToInstanceStateMap = sqlAdmin.getInstanceState(template, instanceIds);
assertEquals(instanceIds.size(), idToInstanceStateMap.size());
for (Map.Entry<String, InstanceState> entry : idToInstanceStateMap.entrySet()) {
LOG.info(entry.getKey() + " -> " + entry.getValue().getInstanceStateDescription(DEFAULT_LOCALIZATION_CONTEXT));
}
while (idToInstanceStateMap.size() == 1
&& idToInstanceStateMap.values().toArray(new InstanceState[1])[0].getInstanceStatus() != desiredStatus) {
Thread.sleep(POLLING_INTERVAL_SECONDS * 1000);
LOG.info("Polling...");
idToInstanceStateMap = sqlAdmin.getInstanceState(template, instanceIds);
for (Map.Entry<String, InstanceState> entry : idToInstanceStateMap.entrySet()) {
LOG.info(entry.getKey() + " -> " + entry.getValue().getInstanceStateDescription(DEFAULT_LOCALIZATION_CONTEXT));
}
}
}
}