Repository: incubator-brooklyn
Updated Branches:
refs/heads/master ec7448947 -> ecc62b040
JcloudsLocation set arbitrary template options
Adds new config key for JcloudsLocation, templateOptions, that takes a
Map<String,String> for setting arbitrary template options. For each
entry, searches for a method with the same name as the key, with a
single parameter that TypeCoercions supports. The method is then invoked
with the value. If such a method cannot be found, then a warning is
printed to the log.
The config key's value can be expressed as a YAML fragment in blueprints
or in brooklyn.properties:
```properties
brooklyn.location.named.softlayer-ams01.templateOptions={ domainName:
'example.com' }
```
```yaml
location:
jclouds:softlayer:ams01:
templateOptions:
domainName: 'example.com'
```
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit:
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/22d127b2
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/22d127b2
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/22d127b2
Branch: refs/heads/master
Commit: 22d127b283c0e72336f8d29ed244d238e482d4ac
Parents: ec74489
Author: Richard Downer <[email protected]>
Authored: Wed Jan 21 12:41:22 2015 +0000
Committer: Richard Downer <[email protected]>
Committed: Wed Jan 28 13:20:52 2015 +0000
----------------------------------------------------------------------
.../location/jclouds/JcloudsLocation.java | 45 ++++++++-
.../location/jclouds/JcloudsLocationConfig.java | 4 +
...ationTemplateOptionsCustomisersLiveTest.java | 96 ++++++++++++++++++++
3 files changed, 143 insertions(+), 2 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/22d127b2/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java
----------------------------------------------------------------------
diff --git
a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java
b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java
index ccf5e17..054ce93 100644
---
a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java
+++
b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java
@@ -30,6 +30,9 @@ import static
org.jclouds.scriptbuilder.domain.Statements.exec;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
import java.security.KeyPair;
import java.util.ArrayList;
import java.util.Arrays;
@@ -48,6 +51,7 @@ import java.util.regex.Pattern;
import javax.annotation.Nullable;
+import com.google.common.reflect.TypeToken;
import org.jclouds.abiquo.compute.options.AbiquoTemplateOptions;
import org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions;
import org.jclouds.compute.ComputeService;
@@ -831,7 +835,7 @@ public class JcloudsLocation extends
AbstractCloudMachineProvisioningLocation im
void apply(TemplateBuilder tb, ConfigBag props, Object v);
}
- private static interface CustomizeTemplateOptions {
+ public static interface CustomizeTemplateOptions {
void apply(TemplateOptions tb, ConfigBag props, Object v);
}
@@ -1050,6 +1054,43 @@ public class JcloudsLocation extends
AbstractCloudMachineProvisioningLocation im
public void apply(TemplateOptions t, ConfigBag props,
Object v) {
t.networks((String)v);
}})
+ .put(TEMPLATE_OPTIONS, new CustomizeTemplateOptions() {
+ @Override
+ public void apply(TemplateOptions options, ConfigBag config,
Object v) {
+ if (v == null) return;
+ @SuppressWarnings("unchecked") Map<String, String>
optionsMap = (Map<String, String>) v;
+ if (optionsMap.isEmpty()) return;
+
+ Class<? extends TemplateOptions> clazz =
options.getClass();
+ Iterable<Method> methods =
Arrays.asList(clazz.getMethods());
+ for(final Map.Entry<String, String> option :
optionsMap.entrySet()) {
+ Optional<Method> methodOptional =
Iterables.tryFind(methods, new Predicate<Method>() {
+ @Override
+ public boolean apply(@Nullable Method input) {
+ // Matches a method with the expected name,
and a single parameter that TypeCoercions
+ // can coerce to
+ if (input == null) return false;
+ if
(!input.getName().equals(option.getKey())) return false;
+ Type[] parameterTypes =
input.getGenericParameterTypes();
+ return parameterTypes.length == 1
+ &&
TypeCoercions.tryCoerce(option.getValue(),
TypeToken.of(parameterTypes[0])).isPresentAndNonNull();
+ }
+ });
+ if(methodOptional.isPresent()) {
+ try {
+ Method method = methodOptional.get();
+ method.invoke(options,
TypeCoercions.coerce(option.getValue(),
TypeToken.of(method.getGenericParameterTypes()[0])));
+ } catch (IllegalAccessException e) {
+ throw Exceptions.propagate(e);
+ } catch (InvocationTargetException e) {
+ throw Exceptions.propagate(e);
+ }
+ } else {
+ LOG.warn("Ignoring request to set template
option {} because this is not supported by {}", new Object[] { option.getKey(),
clazz.getCanonicalName() });
+ }
+ }
+ }
+ })
.build();
private static boolean listedAvailableTemplatesOnNoSuchTemplate = false;
@@ -1140,7 +1181,7 @@ public class JcloudsLocation extends
AbstractCloudMachineProvisioningLocation im
if (config.containsKey(key))
code.apply(options, config, config.get(key));
}
-
+
return template;
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/22d127b2/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocationConfig.java
----------------------------------------------------------------------
diff --git
a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocationConfig.java
b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocationConfig.java
index 5047166..b7b61a7 100644
---
a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocationConfig.java
+++
b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocationConfig.java
@@ -20,6 +20,7 @@ package brooklyn.location.jclouds;
import java.util.Collection;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.Semaphore;
import org.jclouds.Constants;
@@ -249,6 +250,9 @@ public interface JcloudsLocationConfig extends
CloudLocationConfig {
"Registry/Factory for creating jclouds ComputeService; default is
almost always fine, except where tests want to customize behaviour",
ComputeServiceRegistryImpl.INSTANCE);
+ public static final ConfigKey<Map<String,String>> TEMPLATE_OPTIONS =
ConfigKeys.newConfigKey(
+ new TypeToken<Map<String, String>>() {}, "templateOptions",
"Additional jclouds template options");
+
// TODO
// "noDefaultSshKeys" - hints that local ssh keys should not be read as
defaults
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/22d127b2/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsLocationTemplateOptionsCustomisersLiveTest.java
----------------------------------------------------------------------
diff --git
a/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsLocationTemplateOptionsCustomisersLiveTest.java
b/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsLocationTemplateOptionsCustomisersLiveTest.java
new file mode 100644
index 0000000..f4c44a4
--- /dev/null
+++
b/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsLocationTemplateOptionsCustomisersLiveTest.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 brooklyn.location.jclouds;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.location.NoMachinesAvailableException;
+import brooklyn.location.basic.SshMachineLocation;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.ssh.BashCommands;
+import brooklyn.util.text.Identifiers;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import org.jclouds.aws.ec2.compute.AWSEC2TemplateOptions;
+import org.jclouds.compute.options.TemplateOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static org.testng.Assert.assertEquals;
+
+public class JcloudsLocationTemplateOptionsCustomisersLiveTest extends
AbstractJcloudsLiveTest {
+
+ private static final String LOCATION_SPEC = AWS_EC2_PROVIDER + ":" +
AWS_EC2_USEAST_REGION_NAME;
+
+ @BeforeMethod(alwaysRun=true)
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ jcloudsLocation = resolve(LOCATION_SPEC);
+ }
+
+ // Doesn't actually do much with the cloud, but jclouds requires identity
and credential before it will work
+ @Test(groups = "Live")
+ public void testGeneralPurposeTemplateOptionCustomisation() throws
Exception {
+ ConfigKey<Map<String, String>> key =
JcloudsLocationConfig.TEMPLATE_OPTIONS;
+
+ ConfigBag config = ConfigBag.newInstance()
+ .configure(key, ImmutableMap.of("iamInstanceProfileName",
"helloworld"));
+ AWSEC2TemplateOptions templateOptions =
jcloudsLocation.getComputeService().templateOptions().as(AWSEC2TemplateOptions.class);
+
+ invokeCustomizeTemplateOptions(templateOptions,
JcloudsLocationConfig.TEMPLATE_OPTIONS, config);
+
+ assertEquals(templateOptions.getIAMInstanceProfileName(),
"helloworld");
+ }
+
+ /**
+ * Invoke a specific template options customizer on a TemplateOptions
instance.
+ *
+ * @param templateOptions the TemplateOptions instance that you expect the
customizer to modify.
+ * @param keyToTest the config key that identifies the customizer. This
must be present in both @{code locationConfig} and @{link
JcloudsLocation.SUPPORTED_TEMPLATE_OPTIONS_PROPERTIES}.
+ * @param locationConfig simulated configuration for the location. This
must contain at least an entry for @{code keyToTest}.
+ */
+ private void invokeCustomizeTemplateOptions(TemplateOptions
templateOptions, ConfigKey<?> keyToTest, ConfigBag locationConfig) {
+ checkNotNull(templateOptions, "templateOptions");
+ checkNotNull(keyToTest, "keyToTest");
+ checkNotNull(locationConfig, "locationConfig");
+
checkState(JcloudsLocation.SUPPORTED_TEMPLATE_OPTIONS_PROPERTIES.containsKey(keyToTest),
+ "SUPPORTED_TEMPLATE_OPTIONS_PROPERTIES does not contain a
customiser for the key " + keyToTest.getName());
+ checkState(locationConfig.containsKey(keyToTest),
+ "location config does not contain the key " +
keyToTest.getName());
+
+ JcloudsLocation.CustomizeTemplateOptions code =
JcloudsLocation.SUPPORTED_TEMPLATE_OPTIONS_PROPERTIES.get(keyToTest);
+ code.apply(templateOptions, locationConfig,
locationConfig.get(keyToTest));
+ }
+
+ private JcloudsLocation resolve(String spec) {
+ return (JcloudsLocation)
managementContext.getLocationRegistry().resolve("jclouds:"+spec);
+ }
+}