/* * 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.compute; import static com.cloudera.director.google.compute.GoogleComputeInstanceTemplateConfigurationProperty.IMAGE; import static com.cloudera.director.google.compute.GoogleComputeInstanceTemplateConfigurationProperty.LOCAL_SSD_INTERFACE_TYPE; import static com.cloudera.director.google.compute.GoogleComputeInstanceTemplateConfigurationProperty.NETWORK_NAME; import static com.cloudera.director.google.compute.GoogleComputeInstanceTemplateConfigurationProperty.TYPE; import static com.cloudera.director.google.compute.GoogleComputeInstanceTemplateConfigurationProperty.USE_PREEMPTIBLE_INSTANCES; import static com.cloudera.director.google.compute.GoogleComputeInstanceTemplateConfigurationProperty.ZONE; import static com.cloudera.director.google.compute.GoogleComputeProviderConfigurationProperty.REGION; import static com.cloudera.director.spi.v1.compute.ComputeInstanceTemplate.ComputeInstanceTemplateConfigurationPropertyToken.SSH_OPENSSH_PUBLIC_KEY; import static com.cloudera.director.spi.v1.compute.ComputeInstanceTemplate.ComputeInstanceTemplateConfigurationPropertyToken.SSH_PORT; import static com.cloudera.director.spi.v1.compute.ComputeInstanceTemplate.ComputeInstanceTemplateConfigurationPropertyToken.SSH_USERNAME; 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.compute.ComputeInstance; 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; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; /** * Performs 'live' tests of the full cycle of {@link GoogleComputeProvider}: allocate, getInstanceState, find, delete. * * These three system properties are required: GCP_PROJECT_ID, SSH_PUBLIC_KEY_PATH, SSH_USER_NAME. * 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> */ @RunWith(Parameterized.class) public class GoogleComputeProviderFullCycleTest { private static final Logger LOG = Logger.getLogger(GoogleComputeProviderFullCycleTest.class.getName()); private static final DefaultLocalizationContext DEFAULT_LOCALIZATION_CONTEXT = new DefaultLocalizationContext(Locale.getDefault(), ""); private static final int POLLING_INTERVAL_SECONDS = 5; private static TestFixture testFixture; @BeforeClass public static void beforeClass() throws IOException { Assume.assumeFalse(System.getProperty("GCP_PROJECT_ID", "").isEmpty()); testFixture = TestFixture.newTestFixture(true); } private String localSSDInterfaceType; private String image; public GoogleComputeProviderFullCycleTest(String localSSDInterfaceType, String image) { this.localSSDInterfaceType = localSSDInterfaceType; this.image = image; } @Parameterized.Parameters(name = "{index}: localSSDInterfaceType={0}, image={1}") public static Iterable<Object[]> data1() { return Arrays.asList(new Object[][]{ {"SCSI", "centos6"}, {"SCSI", "rhel6"} }); } @Test public void testFullCycle() throws InterruptedException, IOException { doTestFullCycle(null); } @Test public void testFullCyclePreemptible() throws InterruptedException, IOException { doTestFullCycle(true); } public void doTestFullCycle(Boolean usePreemptibleInstances) throws InterruptedException, IOException { // Retrieve and list out the provider configuration properties for Google compute provider. ResourceProviderMetadata computeMetadata = GoogleComputeProvider.METADATA; LOG.info("Configurations required for 'compute' resource provider:"); for (ConfigurationProperty property : computeMetadata.getProviderConfigurationProperties()) { LOG.info(property.getName(DEFAULT_LOCALIZATION_CONTEXT)); } // Prepare configuration for Google compute provider. Map<String, String> computeConfig = new HashMap<String, String>(); computeConfig.put(REGION.unwrap().getConfigKey(), "us-central1"); Configured resourceProviderConfiguration = new SimpleConfiguration(computeConfig); // 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 compute provider configuration. LOG.info("About to validate the resource provider configuration..."); ConfigurationValidator resourceProviderValidator = new GoogleComputeProviderConfigurationValidator(credentials); PluginExceptionConditionAccumulator accumulator = new PluginExceptionConditionAccumulator(); resourceProviderValidator.validate("resource provider configuration", resourceProviderConfiguration, accumulator, DEFAULT_LOCALIZATION_CONTEXT); assertFalse(accumulator.getConditionsByKey().toString(), accumulator.hasError()); // Create the Google compute provider. GoogleComputeProvider compute = new GoogleComputeProvider(resourceProviderConfiguration, credentials, applicationPropertiesConfig, TestUtils.buildGoogleConfig(), DEFAULT_LOCALIZATION_CONTEXT); // Retrieve and list out the resource template configuration properties for Google compute provider. LOG.info("Configurations required for template:"); for (ConfigurationProperty property : computeMetadata.getResourceTemplateConfigurationProperties()) { LOG.info(property.getName(DEFAULT_LOCALIZATION_CONTEXT)); } // Prepare configuration for resource template. Map<String, String> templateConfig = new HashMap<String, String>(); templateConfig.put(IMAGE.unwrap().getConfigKey(), image); templateConfig.put(TYPE.unwrap().getConfigKey(), "n1-standard-1"); templateConfig.put(NETWORK_NAME.unwrap().getConfigKey(), "default"); templateConfig.put(ZONE.unwrap().getConfigKey(), "us-central1-f"); templateConfig.put(LOCAL_SSD_INTERFACE_TYPE.unwrap().getConfigKey(), localSSDInterfaceType); templateConfig.put(SSH_OPENSSH_PUBLIC_KEY.unwrap().getConfigKey(), testFixture.getSshPublicKey()); templateConfig.put(SSH_USERNAME.unwrap().getConfigKey(), testFixture.getUserName()); templateConfig.put(SSH_PORT.unwrap().getConfigKey(), "22"); LOG.info("Use preemptible instances: " + usePreemptibleInstances); if (usePreemptibleInstances != null) { templateConfig.put(USE_PREEMPTIBLE_INSTANCES.unwrap().getConfigKey(), usePreemptibleInstances.toString()); } 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 = compute.getResourceTemplateConfigurationValidator(); accumulator = new PluginExceptionConditionAccumulator(); templateConfigurationValidator.validate("instance resource template", templateConfiguration, accumulator, DEFAULT_LOCALIZATION_CONTEXT); assertFalse(accumulator.getConditionsByKey().toString(), accumulator.hasError()); // Create the resource template. GoogleComputeInstanceTemplate template = compute.createResourceTemplate("template-1", templateConfiguration, tags); assertNotNull(template); // Use the template to provision one resource. LOG.info("About to provision an instance..."); List<String> instanceIds = Arrays.asList(UUID.randomUUID().toString()); try { compute.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..."); Collection<GoogleComputeInstance> instances = compute.find(template, instanceIds); assertEquals(1, instances.size()); for (ComputeInstance foundInstance : instances) { LOG.info("Found instance '" + foundInstance.getId() + "' with private ip " + foundInstance.getPrivateIpAddress() + "."); } // Verify the id of the returned instance. ComputeInstance 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..."); compute.allocate(template, instanceIds, 1); instances = compute.find(template, instanceIds); assertEquals(1, instances.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(compute, 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..."); compute.delete(template, instanceIds); // Query the instance state again until the instance status is UNKNOWN. pollInstanceState(compute, template, instanceIds, InstanceStatus.UNKNOWN); // Verify that the instance has been deleted. instances = compute.find(template, instanceIds); assertEquals(0, instances.size()); LOG.info("About to delete the same instance again..."); try { compute.delete(template, instanceIds); } catch (UnrecoverableProviderException e) { PluginExceptionDetails details = e.getDetails(); System.out.println(e); if (details != null) { LOG.info("Caught on delete():" + details.getConditionsByKey()); } throw e; } } private static void pollInstanceState( GoogleComputeProvider compute, GoogleComputeInstanceTemplate 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 = compute.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 = compute.getInstanceState(template, instanceIds); for (Map.Entry<String, InstanceState> entry : idToInstanceStateMap.entrySet()) { LOG.info(entry.getKey() + " -> " + entry.getValue().getInstanceStateDescription(DEFAULT_LOCALIZATION_CONTEXT)); } } } }