http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsWinRmMachineLocation.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsWinRmMachineLocation.java b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsWinRmMachineLocation.java deleted file mode 100644 index d555cd9..0000000 --- a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsWinRmMachineLocation.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * 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 static brooklyn.util.JavaGroovyEquivalents.groovyTruth; - -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.Iterator; -import java.util.Set; - -import org.jclouds.compute.domain.NodeMetadata; -import org.jclouds.compute.domain.Template; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import brooklyn.location.basic.WinRmMachineLocation; -import brooklyn.util.flags.SetFromFlag; -import brooklyn.util.net.Networking; - -import com.google.common.base.Objects; -import com.google.common.base.Optional; -import com.google.common.net.HostAndPort; - -public class JcloudsWinRmMachineLocation extends WinRmMachineLocation implements JcloudsMachineLocation { - - private static final Logger LOG = LoggerFactory.getLogger(JcloudsWinRmMachineLocation.class); - - @SetFromFlag - JcloudsLocation jcloudsParent; - - @SetFromFlag - NodeMetadata node; - - @SetFromFlag - Template template; - - public JcloudsWinRmMachineLocation() { - } - - @Override - public String toVerboseString() { - return Objects.toStringHelper(this).omitNullValues() - .add("id", getId()).add("name", getDisplayName()) - .add("user", getUser()) - .add("address", getAddress()) - .add("port", getPort()) - .add("node", getNode()) - .add("jcloudsId", getJcloudsId()) - .add("privateAddresses", node.getPrivateAddresses()) - .add("publicAddresses", node.getPublicAddresses()) - .add("parentLocation", getParent()) - .add("osDetails", getOsDetails()) - .toString(); - } - - @Override - public int getPort() { - return getConfig(WINRM_PORT); - } - - @Override - public NodeMetadata getNode() { - return node; - } - - @Override - public Template getTemplate() { - return template; - } - - @Override - public JcloudsLocation getParent() { - return jcloudsParent; - } - - @Override - public String getHostname() { - InetAddress address = getAddress(); - return (address != null) ? address.getHostAddress() : null; - } - - @Override - public Set<String> getPublicAddresses() { - return node.getPublicAddresses(); - } - - @Override - public Set<String> getPrivateAddresses() { - return node.getPrivateAddresses(); - } - - @Override - public String getSubnetHostname() { - // TODO: TEMP FIX: WAS: - // String publicHostname = jcloudsParent.getPublicHostname(node, Optional.<HostAndPort>absent(), config().getBag()); - // but this causes a call to JcloudsUtil.getFirstReachableAddress, which searches for accessible SSH service. - // This workaround is good for public nodes but not private-subnet ones. - return getHostname(); - } - - @Override - public String getSubnetIp() { - Optional<String> privateAddress = getPrivateAddress(); - if (privateAddress.isPresent()) { - return privateAddress.get(); - } - - String hostname = jcloudsParent.getPublicHostname(node, Optional.<HostAndPort>absent(), config().getBag()); - if (hostname != null && !Networking.isValidIp4(hostname)) { - try { - return InetAddress.getByName(hostname).getHostAddress(); - } catch (UnknownHostException e) { - LOG.debug("Cannot resolve IP for hostname {} of machine {} (so returning hostname): {}", new Object[] {hostname, this, e}); - } - } - return hostname; - } - - protected Optional<String> getPrivateAddress() { - if (groovyTruth(node.getPrivateAddresses())) { - Iterator<String> pi = node.getPrivateAddresses().iterator(); - while (pi.hasNext()) { - String p = pi.next(); - // disallow local only addresses - if (Networking.isLocalOnly(p)) continue; - // other things may be public or private, but either way, return it - return Optional.of(p); - } - } - return Optional.absent(); - } - - @Override - public String getJcloudsId() { - return node.getId(); - } -}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/brooklyn/location/jclouds/SudoTtyFixingCustomizer.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/main/java/brooklyn/location/jclouds/SudoTtyFixingCustomizer.java b/locations/jclouds/src/main/java/brooklyn/location/jclouds/SudoTtyFixingCustomizer.java deleted file mode 100644 index 889c7e3..0000000 --- a/locations/jclouds/src/main/java/brooklyn/location/jclouds/SudoTtyFixingCustomizer.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 org.jclouds.compute.ComputeService; - -import com.google.common.annotations.Beta; -import com.google.common.base.Preconditions; - -import brooklyn.location.basic.SshMachineLocation; -import brooklyn.util.task.DynamicTasks; -import brooklyn.util.task.ssh.SshTasks; -import brooklyn.util.task.ssh.SshTasks.OnFailingTask; - -/** - * Wraps Brooklyn's sudo-tty mitigations in a {@link JcloudsLocationCustomizer} for easy(-ish) consumption - * in YAML blueprints: - * - * <pre> - * name: My App - * brooklyn.config: - * provisioning.properties: - * customizerType: brooklyn.location.jclouds.SudoTtyFixingCustomizer - * services: ... - * </pre> - * - * <p>This class should be seen as a temporary workaround and might disappear completely if/when Brooklyn takes care of this automatically. - * - * <p>See - * <a href='http://unix.stackexchange.com/questions/122616/why-do-i-need-a-tty-to-run-sudo-if-i-can-sudo-without-a-password'>http://unix.stackexchange.com/questions/122616/why-do-i-need-a-tty-to-run-sudo-if-i-can-sudo-without-a-password</a> - * for background. - */ -@Beta -public class SudoTtyFixingCustomizer extends BasicJcloudsLocationCustomizer { - - @Override - public void customize(JcloudsLocation location, ComputeService computeService, JcloudsMachineLocation machine) { - Preconditions.checkArgument(machine instanceof SshMachineLocation, "machine must be SshMachineLocation, but is %s", machine.getClass()); - DynamicTasks.queueIfPossible(SshTasks.dontRequireTtyForSudo((SshMachineLocation)machine, OnFailingTask.FAIL)).orSubmitAndBlock(); - DynamicTasks.waitForLast(); - } -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/brooklyn/location/jclouds/networking/JcloudsLocationSecurityGroupCustomizer.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/main/java/brooklyn/location/jclouds/networking/JcloudsLocationSecurityGroupCustomizer.java b/locations/jclouds/src/main/java/brooklyn/location/jclouds/networking/JcloudsLocationSecurityGroupCustomizer.java deleted file mode 100644 index 3944912..0000000 --- a/locations/jclouds/src/main/java/brooklyn/location/jclouds/networking/JcloudsLocationSecurityGroupCustomizer.java +++ /dev/null @@ -1,561 +0,0 @@ -/* - * 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.networking; - -import static com.google.common.base.Preconditions.checkNotNull; - -import java.util.Iterator; -import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; - -import javax.annotation.Nullable; - -import org.apache.brooklyn.api.entity.Entity; -import org.jclouds.aws.AWSResponseException; -import org.jclouds.compute.ComputeService; -import org.jclouds.compute.domain.SecurityGroup; -import org.jclouds.compute.domain.Template; -import org.jclouds.compute.extensions.SecurityGroupExtension; -import org.jclouds.domain.Location; -import org.jclouds.net.domain.IpPermission; -import org.jclouds.net.domain.IpProtocol; -import org.jclouds.providers.ProviderMetadata; -import org.jclouds.providers.Providers; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import brooklyn.location.geo.LocalhostExternalIpLoader; -import brooklyn.location.jclouds.BasicJcloudsLocationCustomizer; -import brooklyn.location.jclouds.JcloudsLocation; -import brooklyn.location.jclouds.JcloudsMachineLocation; -import brooklyn.util.exceptions.Exceptions; -import brooklyn.util.net.Cidr; -import brooklyn.util.task.Tasks; -import brooklyn.util.time.Duration; - -import com.google.common.annotations.Beta; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; -import com.google.common.base.Optional; -import com.google.common.base.Predicate; -import com.google.common.base.Predicates; -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; -import com.google.common.base.Throwables; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import com.google.common.collect.FluentIterable; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; - -/** - * Configures custom security groups on Jclouds locations. - * - * @see SecurityGroupExtension is an optional extension to jclouds compute service. It allows the manipulation of - * {@link SecurityGroup}s. - * - * This customizer can be injected into {@link JcloudsLocation#obtainOnce} using - * It will be executed after the provisiioning of the {@link JcloudsMachineLocation} to apply app-specific - * customization related to the security groups. - * - * @since 0.7.0 - */ -@Beta -public class JcloudsLocationSecurityGroupCustomizer extends BasicJcloudsLocationCustomizer { - - private static final Logger LOG = LoggerFactory.getLogger(JcloudsLocationSecurityGroupCustomizer.class); - - // Caches instances of JcloudsLocationSecurityGroupCustomizer by application IDs. - private static final LoadingCache<String, JcloudsLocationSecurityGroupCustomizer> CUSTOMISERS = CacheBuilder.newBuilder() - .build(new CacheLoader<String, JcloudsLocationSecurityGroupCustomizer>() { - @Override - public JcloudsLocationSecurityGroupCustomizer load(final String appContext) throws Exception { - return new JcloudsLocationSecurityGroupCustomizer(appContext); - } - }); - - /** Caches the base security group that should be shared between all instances in the same Jclouds location */ - private final Cache<Location, SecurityGroup> sharedGroupCache = CacheBuilder.newBuilder().build(); - - /** Caches security groups unique to instances */ - private final Cache<String, SecurityGroup> uniqueGroupCache = CacheBuilder.newBuilder().build(); - - /** The context for this location customizer. */ - private final String applicationId; - - /** The CIDR for addresses that may SSH to machines. */ - private Supplier<Cidr> sshCidrSupplier; - - /** - * A predicate indicating whether the customiser can retry a request to add a security group - * or a rule after an throwable is thrown. - */ - private Predicate<Exception> isExceptionRetryable = Predicates.alwaysFalse(); - - protected JcloudsLocationSecurityGroupCustomizer(String applicationId) { - // Would be better to restrict with something like LocalhostExternalIpCidrSupplier, but - // we risk making machines inaccessible from Brooklyn when HA fails over. - this(applicationId, Suppliers.ofInstance(new Cidr("0.0.0.0/0"))); - } - - protected JcloudsLocationSecurityGroupCustomizer(String applicationId, Supplier<Cidr> sshCidrSupplier) { - this.applicationId = applicationId; - this.sshCidrSupplier = sshCidrSupplier; - } - - /** - * Gets the customizer for the given applicationId. Multiple calls to this method with the - * same application context will return the same JcloudsLocationSecurityGroupCustomizer instance. - * @param applicationId An identifier for the application the customizer is to be used for - * @return the unique customizer for the given context - */ - public static JcloudsLocationSecurityGroupCustomizer getInstance(String applicationId) { - return CUSTOMISERS.getUnchecked(applicationId); - } - - /** - * Gets a customizer for the given entity's application. Multiple calls to this method with entities - * in the same application will return the same JcloudsLocationSecurityGroupCustomizer instance. - * @param entity The entity the customizer is to be used for - * @return the unique customizer for the entity's owning application - */ - public static JcloudsLocationSecurityGroupCustomizer getInstance(Entity entity) { - return getInstance(entity.getApplicationId()); - } - - /** - * @param predicate - * A predicate whose return value indicates whether a request to add a security group - * or permission may be retried after its input {@link Exception} was thrown. - * @return this - */ - public JcloudsLocationSecurityGroupCustomizer setRetryExceptionPredicate(Predicate<Exception> predicate) { - this.isExceptionRetryable = checkNotNull(predicate, "predicate"); - return this; - } - - /** - * @param cidrSupplier A supplier returning a CIDR for hosts that are allowed to SSH to locations. - */ - public JcloudsLocationSecurityGroupCustomizer setSshCidrSupplier(Supplier<Cidr> cidrSupplier) { - this.sshCidrSupplier = checkNotNull(cidrSupplier, "cidrSupplier"); - return this; - } - - /** @see #addPermissionsToLocation(brooklyn.location.jclouds.JcloudsSshMachineLocation, java.lang.Iterable) */ - public JcloudsLocationSecurityGroupCustomizer addPermissionsToLocation(final JcloudsMachineLocation location, IpPermission... permissions) { - addPermissionsToLocation(location, ImmutableList.copyOf(permissions)); - return this; - } - - /** @see #addPermissionsToLocation(brooklyn.location.jclouds.JcloudsSshMachineLocation, java.lang.Iterable) */ - public JcloudsLocationSecurityGroupCustomizer addPermissionsToLocation(final JcloudsMachineLocation location, SecurityGroupDefinition securityGroupDefinition) { - addPermissionsToLocation(location, securityGroupDefinition.getPermissions()); - return this; - } - - /** - * Applies the given security group permissions to the given location. - * <p> - * Takes no action if the location's compute service does not have a security group extension. - * @param permissions The set of permissions to be applied to the location - * @param location Location to gain permissions - */ - public JcloudsLocationSecurityGroupCustomizer addPermissionsToLocation(final JcloudsMachineLocation location, final Iterable<IpPermission> permissions) { - ComputeService computeService = location.getParent().getComputeService(); - String nodeId = location.getNode().getId(); - addPermissionsToLocation(permissions, nodeId, computeService); - return this; - } - - /** - * Applies the given security group permissions to the given node with the given compute service. - * <p> - * Takes no action if the compute service does not have a security group extension. - * @param permissions The set of permissions to be applied to the node - * @param nodeId The id of the node to update - * @param computeService The compute service to use to apply the changes - */ - @VisibleForTesting - void addPermissionsToLocation(Iterable<IpPermission> permissions, final String nodeId, ComputeService computeService) { - if (!computeService.getSecurityGroupExtension().isPresent()) { - LOG.warn("Security group extension for {} absent; cannot update node {} with {}", - new Object[] {computeService, nodeId, permissions}); - return; - } - final SecurityGroupExtension securityApi = computeService.getSecurityGroupExtension().get(); - final String locationId = computeService.getContext().unwrap().getId(); - - // Expect to have two security groups on the node: one shared between all nodes in the location, - // that is cached in sharedGroupCache, and one created by Jclouds that is unique to the node. - // Relies on customize having been called before. This should be safe because the arguments - // needed to call this method are not available until post-instance creation. - SecurityGroup machineUniqueSecurityGroup; - Tasks.setBlockingDetails("Loading unique security group for node: " + nodeId); - try { - machineUniqueSecurityGroup = uniqueGroupCache.get(nodeId, new Callable<SecurityGroup>() { - @Override public SecurityGroup call() throws Exception { - SecurityGroup sg = getUniqueSecurityGroupForNodeCachingSharedGroupIfPreviouslyUnknown(nodeId, locationId, securityApi); - if (sg == null) { - throw new IllegalStateException("Failed to find machine-unique group on node: " + nodeId); - } - return sg; - } - }); - } catch (ExecutionException e) { - throw Throwables.propagate(new Exception(e.getCause())); - } finally { - Tasks.resetBlockingDetails(); - } - for (IpPermission permission : permissions) { - addPermission(permission, machineUniqueSecurityGroup, securityApi); - } - } - - /** - * Loads the security groups attached to the node with the given ID and returns the group - * that is unique to the node, per the application context. This method will also update - * {@link #sharedGroupCache} if no mapping for the shared group's location previously - * existed (e.g. Brooklyn was restarted and rebound to an existing application). - * - * Notice that jclouds will attach 2 securityGroups to the node if the locationId is `aws-ec2` so it needs to - * look for the uniqueSecurityGroup rather than the shared securityGroup. - * - * @param nodeId The id of the node in question - * @param locationId The id of the location in question - * @param securityApi The API to use to list security groups - * @return the security group unique to the given node, or null if one could not be determined. - */ - private SecurityGroup getUniqueSecurityGroupForNodeCachingSharedGroupIfPreviouslyUnknown(String nodeId, String locationId, SecurityGroupExtension securityApi) { - Set<SecurityGroup> groupsOnNode = securityApi.listSecurityGroupsForNode(nodeId); - SecurityGroup unique; - if (locationId.equals("aws-ec2")) { - if (groupsOnNode.size() != 2) { - LOG.warn("Expected to find two security groups on node {} in app {} (one shared, one unique). Found {}: {}", - new Object[]{nodeId, applicationId, groupsOnNode.size(), groupsOnNode}); - return null; - } - String expectedSharedName = getNameForSharedSecurityGroup(); - Iterator<SecurityGroup> it = groupsOnNode.iterator(); - SecurityGroup shared = it.next(); - if (shared.getName().endsWith(expectedSharedName)) { - unique = it.next(); - } else { - unique = shared; - shared = it.next(); - } - if (!shared.getName().endsWith(expectedSharedName)) { - LOG.warn("Couldn't determine which security group is shared between instances in app {}. Expected={}, found={}", - new Object[]{applicationId, expectedSharedName, groupsOnNode}); - return null; - } - // Shared entry might be missing if Brooklyn has rebound to an application - SecurityGroup old = sharedGroupCache.asMap().putIfAbsent(shared.getLocation(), shared); - LOG.info("Loaded unique security group for node {} (in {}): {}", - new Object[]{nodeId, applicationId, unique}); - if (old == null) { - LOG.info("Proactively set shared group for app {} to: {}", applicationId, shared); - } - return unique; - } - return Iterables.getOnlyElement(groupsOnNode); - } - - /** - * Replaces security groups configured on the given template with one that allows - * SSH access on port 22 and allows communication on all ports between machines in - * the same group. Security groups are reused when templates have equal - * {@link org.jclouds.compute.domain.Template#getLocation locations}. - * <p> - * This method is called by Brooklyn when obtaining machines, as part of the - * {@link brooklyn.location.jclouds.JcloudsLocationCustomizer} contract. It - * should not be called from anywhere else. - * - * @param location The Brooklyn location that has called this method while obtaining a machine - * @param computeService The compute service being used by the location argument to provision a machine - * @param template The machine template created by the location argument - */ - @Override - public void customize(JcloudsLocation location, ComputeService computeService, Template template) { - if (!computeService.getSecurityGroupExtension().isPresent()) { - LOG.warn("Security group extension for {} absent; cannot configure security groups in context: {}", computeService, applicationId); - } else if (template.getLocation() == null) { - LOG.warn("No location has been set on {}; cannot configure security groups in context: {}", template, applicationId); - } else { - LOG.info("Configuring security groups on location {} in context {}", location, applicationId); - setSecurityGroupOnTemplate(location, template, computeService.getSecurityGroupExtension().get()); - } - } - - private void setSecurityGroupOnTemplate(final JcloudsLocation location, final Template template, final SecurityGroupExtension securityApi) { - SecurityGroup shared; - Tasks.setBlockingDetails("Loading security group shared by instances in " + template.getLocation() + - " in app " + applicationId); - try { - shared = sharedGroupCache.get(template.getLocation(), new Callable<SecurityGroup>() { - @Override public SecurityGroup call() throws Exception { - return getOrCreateSharedSecurityGroup(template.getLocation(), securityApi); - } - }); - } catch (ExecutionException e) { - throw Throwables.propagate(new Exception(e.getCause())); - } finally { - Tasks.resetBlockingDetails(); - } - - Set<String> originalGroups = template.getOptions().getGroups(); - template.getOptions().securityGroups(shared.getName()); - if (!originalGroups.isEmpty()) { - LOG.info("Replaced configured security groups: configured={}, replaced with={}", originalGroups, template.getOptions().getGroups()); - } else { - LOG.debug("Configured security groups at {} to: {}", location, template.getOptions().getGroups()); - } - } - - /** - * Loads the security group to be shared between nodes in the same application in the - * given Location. If no such security group exists it is created. - * - * @param location The location in which the security group will be found - * @param securityApi The API to use to list and create security groups - * @return the security group to share between instances in the given location in this application - */ - private SecurityGroup getOrCreateSharedSecurityGroup(Location location, SecurityGroupExtension securityApi) { - final String groupName = getNameForSharedSecurityGroup(); - // Could sort-and-search if straight search is too expensive - Optional<SecurityGroup> shared = Iterables.tryFind(securityApi.listSecurityGroupsInLocation(location), new Predicate<SecurityGroup>() { - @Override - public boolean apply(final SecurityGroup input) { - // endsWith because Jclouds prepends 'jclouds#' to security group names. - return input.getName().endsWith(groupName); - } - }); - if (shared.isPresent()) { - LOG.info("Found existing shared security group in {} for app {}: {}", - new Object[]{location, applicationId, groupName}); - return shared.get(); - } else { - LOG.info("Creating new shared security group in {} for app {}: {}", - new Object[]{location, applicationId, groupName}); - return createBaseSecurityGroupInLocation(groupName, location, securityApi); - } - } - - /** - * Creates a security group with rules to: - * <ul> - * <li>Allow SSH access on port 22 from the world</li> - * <li>Allow TCP, UDP and ICMP communication between machines in the same group</li> - * </ul> - * - * It needs to consider locationId as port ranges and groupId are cloud provider-dependent e.g openstack nova - * wants from 1-65535 while aws-ec2 accepts from 0-65535. - * - * - * @param groupName The name of the security group to create - * @param location The location in which the security group will be created - * @param securityApi The API to use to create the security group - * - * @return the created security group - */ - private SecurityGroup createBaseSecurityGroupInLocation(String groupName, Location location, SecurityGroupExtension securityApi) { - SecurityGroup group = addSecurityGroupInLocation(groupName, location, securityApi); - - Set<String> openstackNovaIds = getJcloudsLocationIds("openstack-nova"); - - String groupId = group.getProviderId(); - int fromPort = 0; - if (location.getParent() != null && Iterables.contains(openstackNovaIds, location.getParent().getId())) { - groupId = group.getId(); - fromPort = 1; - } - // Note: For groupName to work with GCE we also need to tag the machines with the same ID. - // See sourceTags section at https://developers.google.com/compute/docs/networking#firewalls - IpPermission.Builder allWithinGroup = IpPermission.builder() - .groupId(groupId) - .fromPort(fromPort) - .toPort(65535); - addPermission(allWithinGroup.ipProtocol(IpProtocol.TCP).build(), group, securityApi); - addPermission(allWithinGroup.ipProtocol(IpProtocol.UDP).build(), group, securityApi); - addPermission(allWithinGroup.ipProtocol(IpProtocol.ICMP).fromPort(-1).toPort(-1).build(), group, securityApi); - - IpPermission sshPermission = IpPermission.builder() - .fromPort(22) - .toPort(22) - .ipProtocol(IpProtocol.TCP) - .cidrBlock(getBrooklynCidrBlock()) - .build(); - addPermission(sshPermission, group, securityApi); - - return group; - } - - private Set<String> getJcloudsLocationIds(final String jcloudsApiId) { - Set<String> openstackNovaProviders = FluentIterable.from(Providers.all()) - .filter(new Predicate<ProviderMetadata>() { - @Override - public boolean apply(ProviderMetadata providerMetadata) { - return providerMetadata.getApiMetadata().getId().equals(jcloudsApiId); - } - }).transform(new Function<ProviderMetadata, String>() { - @Nullable - @Override - public String apply(ProviderMetadata input) { - return input.getId(); - } - }).toSet(); - - return new ImmutableSet.Builder<String>() - .addAll(openstackNovaProviders) - .add(jcloudsApiId) - .build(); - } - - protected SecurityGroup addSecurityGroupInLocation(final String groupName, final Location location, final SecurityGroupExtension securityApi) { - LOG.debug("Creating security group {} in {}", groupName, location); - Callable<SecurityGroup> callable = new Callable<SecurityGroup>() { - @Override - public SecurityGroup call() throws Exception { - return securityApi.createSecurityGroup(groupName, location); - } - }; - return runOperationWithRetry(callable); - } - - protected SecurityGroup addPermission(final IpPermission permission, final SecurityGroup group, final SecurityGroupExtension securityApi) { - LOG.debug("Adding permission to security group {}: {}", group.getName(), permission); - Callable<SecurityGroup> callable = new Callable<SecurityGroup>() { - @Override - public SecurityGroup call() throws Exception { - return securityApi.addIpPermission(permission, group); - } - }; - return runOperationWithRetry(callable); - } - - /** @return the CIDR block used to configure Brooklyn's in security groups */ - public String getBrooklynCidrBlock() { - return sshCidrSupplier.get().toString(); - } - - /** - * @return The name to be used by security groups that will be shared between machines - * in the same location for this instance's application context. - */ - @VisibleForTesting - String getNameForSharedSecurityGroup() { - return "brooklyn-" + applicationId.toLowerCase() + "-shared"; - } - - /** - * Invalidates all entries in {@link #sharedGroupCache} and {@link #uniqueGroupCache}. - * Use to simulate the effects of rebinding Brooklyn to a deployment. - */ - @VisibleForTesting - void clearSecurityGroupCaches() { - LOG.info("Clearing security group caches"); - sharedGroupCache.invalidateAll(); - uniqueGroupCache.invalidateAll(); - } - - /** - * Runs the given callable. Repeats until the operation succeeds or {@link #isExceptionRetryable} indicates - * that the request cannot be retried. - */ - protected <T> T runOperationWithRetry(Callable<T> operation) { - int backoff = 64; - Exception lastException = null; - for (int retries = 0; retries < 100; retries++) { - try { - return operation.call(); - } catch (Exception e) { - lastException = e; - if (isExceptionRetryable.apply(e)) { - LOG.debug("Attempt #{} failed to add security group: {}", retries + 1, e.getMessage()); - try { - Thread.sleep(backoff); - } catch (InterruptedException e1) { - throw Exceptions.propagate(e1); - } - backoff = backoff << 1; - } else { - break; - } - } - } - - throw new RuntimeException("Unable to add security group rule; repeated errors from provider", lastException); - } - - /** - * @return - * A predicate that is true if an exception contains an {@link org.jclouds.aws.AWSResponseException} - * whose error code is either <code>InvalidGroup.InUse</code>, <code>DependencyViolation</code> or - * <code>RequestLimitExceeded</code>. - */ - public static Predicate<Exception> newAwsExceptionRetryPredicate() { - return new AwsExceptionRetryPredicate(); - } - - private static class AwsExceptionRetryPredicate implements Predicate<Exception> { - // Error reference: http://docs.aws.amazon.com/AWSEC2/latest/APIReference/errors-overview.html - private static final Set<String> AWS_ERRORS_TO_RETRY = ImmutableSet.of( - "InvalidGroup.InUse", "DependencyViolation", "RequestLimitExceeded"); - - @Override - public boolean apply(Exception input) { - @SuppressWarnings("ThrowableResultOfMethodCallIgnored") - AWSResponseException exception = Exceptions.getFirstThrowableOfType(input, AWSResponseException.class); - if (exception != null) { - String code = exception.getError().getCode(); - return AWS_ERRORS_TO_RETRY.contains(code); - } - return false; - } - } - - /** - * A supplier of CIDRs that loads the external IP address of the localhost machine. - */ - private static class LocalhostExternalIpCidrSupplier implements Supplier<Cidr> { - - private volatile Cidr cidr; - - @Override - public Cidr get() { - Cidr local = cidr; - if (local == null) { - synchronized (this) { - local = cidr; - if (local == null) { - String externalIp = LocalhostExternalIpLoader.getLocalhostIpWithin(Duration.seconds(5)); - cidr = local = new Cidr(externalIp + "/32"); - } - } - } - return local; - } - - } - -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/brooklyn/location/jclouds/networking/JcloudsPortForwarderExtension.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/main/java/brooklyn/location/jclouds/networking/JcloudsPortForwarderExtension.java b/locations/jclouds/src/main/java/brooklyn/location/jclouds/networking/JcloudsPortForwarderExtension.java deleted file mode 100644 index adfdde3..0000000 --- a/locations/jclouds/src/main/java/brooklyn/location/jclouds/networking/JcloudsPortForwarderExtension.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.networking; - -import org.jclouds.compute.domain.NodeMetadata; - -import brooklyn.location.access.BrooklynAccessUtils; -import brooklyn.location.access.PortForwardManager; -import brooklyn.util.net.Cidr; -import brooklyn.util.net.Protocol; - -import com.google.common.base.Optional; -import com.google.common.net.HostAndPort; - -public interface JcloudsPortForwarderExtension { - - /** - * Opens port forwarding (e.g. DNAT or iptables port-forwarding) to reach the given given - * target port on this node (from the given cidr). - * - * This should also register the port with the {@link PortForwardManager}, via - * {@code portForwardManager.associate(node.getId(), result, targetPort)} so that - * subsequent calls to {@link BrooklynAccessUtils#getBrooklynAccessibleAddress(brooklyn.entity.Entity, int)} - * will know about the mapped port. - */ - public HostAndPort openPortForwarding(NodeMetadata node, int targetPort, Optional<Integer> optionalPublicPort, Protocol protocol, Cidr accessingCidr); - - public void closePortForwarding(NodeMetadata node, int targetPort, HostAndPort publicHostAndPort, Protocol protocol); -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/brooklyn/location/jclouds/networking/SecurityGroupDefinition.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/main/java/brooklyn/location/jclouds/networking/SecurityGroupDefinition.java b/locations/jclouds/src/main/java/brooklyn/location/jclouds/networking/SecurityGroupDefinition.java deleted file mode 100644 index 7ced79f..0000000 --- a/locations/jclouds/src/main/java/brooklyn/location/jclouds/networking/SecurityGroupDefinition.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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.networking; - -import java.util.List; -import java.util.concurrent.Callable; - -import org.jclouds.aws.ec2.AWSEC2Api; -import org.jclouds.compute.ComputeServiceContext; -import org.jclouds.net.domain.IpPermission; -import org.jclouds.net.domain.IpProtocol; -import org.jclouds.net.util.IpPermissions; - -import brooklyn.util.collections.MutableList; -import brooklyn.util.exceptions.Exceptions; -import brooklyn.util.net.Cidr; -import brooklyn.util.text.Identifiers; - -import com.google.common.annotations.Beta; - -/** WIP to define a security group in an up-front way, where subsequently it can be applied to a jclouds location */ -@Beta -public class SecurityGroupDefinition { - - private Callable<String> groupNameFactory = new Callable<String>() { public String call() { return "br-sg-"+Identifiers.makeRandomId(8); } }; - private List<IpPermission> ipPerms = MutableList.of(); - - public void createGroupInAwsRegion(ComputeServiceContext computeServiceContext, String region) { - AWSEC2Api ec2Client = computeServiceContext.unwrapApi(AWSEC2Api.class); - String sgId = ec2Client.getSecurityGroupApi().get().createSecurityGroupInRegionAndReturnId(region, getName(), "Brooklyn-managed security group "+getName()); - ec2Client.getSecurityGroupApi().get().authorizeSecurityGroupIngressInRegion(region, sgId, ipPerms); - } - - /** allows access to the given port on TCP from within the subnet */ - public SecurityGroupDefinition allowingInternalPort(int port) { - return allowing(IpPermissions.permit(IpProtocol.TCP).port(port)); - } - public SecurityGroupDefinition allowingInternalPorts(int port1, int port2, int ...ports) { - allowing(IpPermissions.permit(IpProtocol.TCP).port(port1)); - allowing(IpPermissions.permit(IpProtocol.TCP).port(port2)); - for (int port: ports) - allowing(IpPermissions.permit(IpProtocol.TCP).port(port)); - return this; - } - public SecurityGroupDefinition allowingInternalPortRange(int portRangeStart, int portRangeEnd) { - return allowing(IpPermissions.permit(IpProtocol.TCP).fromPort(portRangeStart).to(portRangeEnd)); - } - public SecurityGroupDefinition allowingInternalPing() { - return allowing(IpPermissions.permit(IpProtocol.ICMP)); - } - - public SecurityGroupDefinition allowingPublicPort(int port) { - return allowing(IpPermissions.permit(IpProtocol.TCP).port(port).originatingFromCidrBlock(Cidr.UNIVERSAL.toString())); - } - public SecurityGroupDefinition allowingPublicPorts(int port1, int port2, int ...ports) { - allowing(IpPermissions.permit(IpProtocol.TCP).port(port1).originatingFromCidrBlock(Cidr.UNIVERSAL.toString())); - allowing(IpPermissions.permit(IpProtocol.TCP).port(port2).originatingFromCidrBlock(Cidr.UNIVERSAL.toString())); - for (int port: ports) - allowing(IpPermissions.permit(IpProtocol.TCP).port(port).originatingFromCidrBlock(Cidr.UNIVERSAL.toString())); - return this; - } - public SecurityGroupDefinition allowingPublicPortRange(int portRangeStart, int portRangeEnd) { - return allowing(IpPermissions.permit(IpProtocol.TCP).fromPort(portRangeStart).to(portRangeEnd).originatingFromCidrBlock(Cidr.UNIVERSAL.toString())); - } - public SecurityGroupDefinition allowingPublicPing() { - return allowing(IpPermissions.permit(IpProtocol.ICMP).originatingFromCidrBlock(Cidr.UNIVERSAL.toString())); - } - - public SecurityGroupDefinition allowing(IpPermission permission) { - ipPerms.add(permission); - return this; - } - - // TODO use cloud machine namer - public SecurityGroupDefinition named(final String name) { - groupNameFactory = new Callable<String>() { public String call() { return name; } }; - return this; - } - public String getName() { - try { return groupNameFactory.call(); } - catch (Exception e) { throw Exceptions.propagate(e); } - } - - public Iterable<IpPermission> getPermissions() { - return ipPerms; - } -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/brooklyn/location/jclouds/networking/SecurityGroupTool.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/main/java/brooklyn/location/jclouds/networking/SecurityGroupTool.java b/locations/jclouds/src/main/java/brooklyn/location/jclouds/networking/SecurityGroupTool.java deleted file mode 100644 index 31b06a0..0000000 --- a/locations/jclouds/src/main/java/brooklyn/location/jclouds/networking/SecurityGroupTool.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * 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.networking; - -import java.util.Set; - -import org.jclouds.aws.ec2.AWSEC2Api; -import org.jclouds.aws.util.AWSUtils; -import org.jclouds.compute.domain.SecurityGroup; -import org.jclouds.compute.extensions.SecurityGroupExtension; -import org.jclouds.net.domain.IpPermission; -import org.jclouds.rest.ApiContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import brooklyn.location.jclouds.JcloudsLocation; -import brooklyn.location.jclouds.JcloudsLocationConfig; -import brooklyn.util.exceptions.Exceptions; -import brooklyn.util.text.Strings; - -import com.google.common.annotations.Beta; -import com.google.common.base.Optional; -import com.google.common.base.Preconditions; - -/** WIP to apply a security group to a jclouds endpoint. - * <p> - * sections of this code have been used but overall it is not yet in a working state, - * but merged May 2014 to make it easier to pick up if and when needed. - * (if not needed after several months this may simply be removed.) */ -@Beta -public class SecurityGroupTool { - - private static final Logger log = LoggerFactory.getLogger(SecurityGroupTool.class); - - protected final JcloudsLocation location; - protected final SecurityGroupDefinition sgDef; - - public SecurityGroupTool(JcloudsLocation location, SecurityGroupDefinition sgDef) { - this.location = Preconditions.checkNotNull(location); - this.sgDef = Preconditions.checkNotNull(sgDef); - } - - public String getName() { - return sgDef.getName(); - } - - public void apply() { - Optional<SecurityGroupExtension> sgExtO = location.getComputeService().getSecurityGroupExtension(); - if (!sgExtO.isPresent()) { - throw new IllegalStateException("Advanced networking not supported in this location ("+location+")"); - } - SecurityGroupExtension sgExt = sgExtO.get(); - - SecurityGroup sg = findSecurityGroupWithName(sgExt, getName()); - if (sg==null) { - // TODO initialize the location - org.jclouds.domain.Location sgLoc = null; - - // TODO record that we created it - // create it - try { - // FIXME this will always fail for providers which need a location, until we set it above - // https://github.com/brooklyncentral/brooklyn/pull/1343#discussion_r12275188 - sg = sgExt.createSecurityGroup(getName(), sgLoc); - } catch (Exception e) { - Exceptions.propagateIfFatal(e); - // check if someone else already created it - sg = findSecurityGroupWithName(sgExt, getName()); - if (sg==null) { - // no - so propagate error - throw Exceptions.propagate(e); - } else { - log.debug("Looks like parallel thread created security group "+getName()+"; ignoring error in our thread ("+e+") as we now have an SG"); - } - } - } - - if (sg==null) - throw new IllegalStateException("Unable to find or create security group ID for "+getName()); - - addPermissions(sgExt, sg); - } - - protected SecurityGroup findSecurityGroupWithName(SecurityGroupExtension sgExt, String name) { - Set<SecurityGroup> groups = sgExt.listSecurityGroups(); - // jclouds appends this sometimes so for portability let's add this - String nameAlt = name.startsWith("jclouds#") ? Strings.removeFromStart(name, "jclouds#") : "jclouds#"+name; - for (SecurityGroup g: groups) { - if (name.equals(g.getName())) return g; - if (nameAlt.equals(g.getName())) return g; - } - return null; - } - - protected void addPermissions(SecurityGroupExtension sgExt, SecurityGroup sg) { - - Object api = ((ApiContext<?>)location.getComputeService().getContext().unwrap()).getApi(); - if (api instanceof AWSEC2Api) { - // optimization for AWS where rules can be added all at once, and it cuts down Req Limit Exceeded problems! - String region = AWSUtils.getRegionFromLocationOrNull(sg.getLocation()); - String id = sg.getProviderId(); - - ((AWSEC2Api)api).getSecurityGroupApi().get().authorizeSecurityGroupIngressInRegion(region, id, sgDef.getPermissions()); - - } else { - for (IpPermission p: sgDef.getPermissions()) { - sgExt.addIpPermission(p, sg); - } - } - } - - - // TODO remove this method once we've confirmed the above works nicely (this is an early attempt) - protected void applyOldEc2(AWSEC2Api client) { - String region = location.getConfig(JcloudsLocationConfig.CLOUD_REGION_ID); - if (region==null) { - // TODO where does the default come from? - log.warn("No region set for "+location+"; assuming EC2"); - region = "us-east-1"; - } - - Set<org.jclouds.ec2.domain.SecurityGroup> groups = client.getSecurityGroupApi().get().describeSecurityGroupsInRegion(region, getName()); - String id = null; - if (groups.isEmpty()) { - // create it - try { - id = client.getSecurityGroupApi().get().createSecurityGroupInRegionAndReturnId(region , getName(), "Brooklyn-managed security group "+getName()); - } catch (Exception e) { - Exceptions.propagateIfFatal(e); - // check if someone else already created it! - groups = client.getSecurityGroupApi().get().describeSecurityGroupsInRegion(region, getName()); - if (groups.isEmpty()) { - // no - so propagate error - throw Exceptions.propagate(e); - } else { - log.debug("Looks like parallel thread created security group "+getName()+"; ignoring error in our thread ("+e+") as we now have an SG"); - } - } - } - if (!groups.isEmpty()) { - if (groups.size()>1) - log.warn("Multiple security groups matching '"+getName()+"' (using the first): "+groups); - id = groups.iterator().next().getId(); - } - if (id==null) - throw new IllegalStateException("Unable to find or create security group ID for "+getName()); - - client.getSecurityGroupApi().get().authorizeSecurityGroupIngressInRegion(region, id, sgDef.getPermissions()); - } - -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/brooklyn/location/jclouds/pool/MachinePool.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/main/java/brooklyn/location/jclouds/pool/MachinePool.java b/locations/jclouds/src/main/java/brooklyn/location/jclouds/pool/MachinePool.java deleted file mode 100644 index d2dcf80..0000000 --- a/locations/jclouds/src/main/java/brooklyn/location/jclouds/pool/MachinePool.java +++ /dev/null @@ -1,395 +0,0 @@ -/* - * 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.pool; - -import static brooklyn.location.jclouds.pool.MachinePoolPredicates.compose; -import static brooklyn.location.jclouds.pool.MachinePoolPredicates.matching; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.jclouds.compute.ComputeService; -import org.jclouds.compute.RunNodesException; -import org.jclouds.compute.domain.ComputeMetadata; -import org.jclouds.compute.domain.NodeMetadata; -import org.jclouds.compute.domain.Template; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.base.Predicate; -import com.google.common.base.Throwables; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; - -/** - * Contains details of machines detected at a given cloud (ComputeService), - * and records claims made against those machines via this pool. - * <p> - * Machine instances themselves are persisted and rescanned as new instances of this class are created. - * Claims however are specific to this instance of the class, i.e. <b>not</b> persisted. - * <p> - * This class is believed to be thread-safe. - * Refreshes to the remote detected machines are synchronized on the pool instance. - * Details of detected and claimed machines are also synchronized on the pool instance. - * (If it is necessary to claim machines whilst the pool is being rescanned, - * we can investigate a more sophisticated threading model. - * Access to some fields is clearly independent and uses a tighter synchonization - * strategy, e.g. templates. - * Synchronization of fields within a synch block on the class instance - * is permitted, but not the other way round, - * and synching on multiple fields is also not permitted.) - * <p> - * Callers wishing to guarantee results of e.g. ensureUnclaimed remaining available - * can synchronize on this class for the duration that they wish to have that guarantee - * (at the cost, of course, of any other threads being able to access this pool). - * <p> - * If underlying provisioning/destroying operations fail, the pool - * currently may be in an unknown state, currently. - * If more robustness is needed this can be added. - * - * @deprecated since 0.6.0; never used in production setting, and thus of dubious value; best avoided as unlikely to be supported in future versions - */ -@Deprecated -public class MachinePool { - - private static final Logger log = LoggerFactory.getLogger(MachinePool.class); - - protected final ComputeService computeService; - final AtomicBoolean refreshNeeded = new AtomicBoolean(true); - final List<ReusableMachineTemplate> templates = new ArrayList<ReusableMachineTemplate>(); - String poolName = null; - - /** all machines detected, less those in the black list */ - volatile MachineSet detectedMachines = new MachineSet(); - volatile MachineSet matchedMachines = new MachineSet(); - volatile MachineSet claimedMachines = new MachineSet(); - volatile MachineSet blacklistedMachines = new MachineSet(); - - public MachinePool(ComputeService computeService) { - this.computeService = computeService; - } - - protected synchronized void init() { - if (!refreshNeeded.get()) return; - refresh(); - } - - public void setPoolName(String poolName) { - if (poolName!=null) - log.warn("Changing pool name of "+this+" (from "+this.poolName+" to "+poolName+") is discouraged."); - this.poolName = poolName; - } - /** pool name is used as a group/label by jclouds, for convenience only; - * it has no special properties for detecting matching instances - * (use explicit tags on the templates, for that). - * defaults to name of pool class and user name. - * callers should set pool name before getting, if using a custom name. */ - public synchronized String getPoolName() { - if (poolName==null) - poolName = getClass().getSimpleName()+"-"+System.getProperty("user.name"); - return poolName; - } - - /** refreshes the pool of machines from the server (finding all instances matching the registered templates) */ - public synchronized void refresh() { - refreshNeeded.set(false); - Set<? extends ComputeMetadata> computes = computeService.listNodes(); - Set<NodeMetadata> nodes = new LinkedHashSet<NodeMetadata>(); - for (ComputeMetadata c: computes) { - if (c instanceof NodeMetadata) { - nodes.add((NodeMetadata)c); - } else { - // TODO should we try to fetch more info? - log.warn("MachinePool "+this+" ignoring non-Node record for remote machine: "+c); - } - } - - MachineSet allNewDetectedMachines = new MachineSet(nodes); - MachineSet newDetectedMachines = filterForAllowedMachines(allNewDetectedMachines); - MachineSet oldDetectedMachines = detectedMachines; - MachineSet newMatchedMachines = new MachineSet(); - detectedMachines = newDetectedMachines; - - MachineSet appearedMachinesIncludingBlacklist = allNewDetectedMachines.removed(oldDetectedMachines); - MachineSet appearedMachines = filterForAllowedMachines(appearedMachinesIncludingBlacklist); - if (appearedMachinesIncludingBlacklist.size()>appearedMachines.size()) - if (log.isDebugEnabled()) log.debug("Pool "+this+", ignoring "+(appearedMachinesIncludingBlacklist.size()-appearedMachines.size())+" disallowed"); - int matchedAppeared = 0; - for (NodeMetadata m: appearedMachines) { - if (m.getStatus() != NodeMetadata.Status.RUNNING) { - if (log.isDebugEnabled()) - log.debug("Pool "+this+", newly detected machine "+m+", not running ("+m.getStatus()+")"); - } else { - Set<ReusableMachineTemplate> ts = getTemplatesMatchingInstance(m); - if (!ts.isEmpty()) { - matchedAppeared++; - newMatchedMachines = newMatchedMachines.added(new MachineSet(m)); - if (log.isDebugEnabled()) - log.debug("Pool "+this+", newly detected machine "+m+", matches pool templates "+ts); - } else { - if (log.isDebugEnabled()) - log.debug("Pool "+this+", newly detected machine "+m+", does not match any pool templates"); - } - } - } - if (matchedAppeared>0) { - log.info("Pool "+this+" discovered "+matchedAppeared+" matching machines (of "+appearedMachines.size()+" total new; "+newDetectedMachines.size()+" total including claimed and unmatched)"); - } else { - if (log.isDebugEnabled()) - log.debug("Pool "+this+" discovered "+matchedAppeared+" matching machines (of "+appearedMachines.size()+" total new; "+newDetectedMachines.size()+" total including claimed and unmatched)"); - } - matchedMachines = newMatchedMachines; - } - - protected MachineSet filterForAllowedMachines(MachineSet input) { - return input.removed(blacklistedMachines); - } - - // TODO template registry and claiming from a template could be a separate responsibility - - protected ReusableMachineTemplate registerTemplate(ReusableMachineTemplate template) { - registerTemplates(template); - return template; - } - protected void registerTemplates(ReusableMachineTemplate ...templatesToReg) { - synchronized (templates) { - for (ReusableMachineTemplate template: templatesToReg) - templates.add(template); - } - } - - protected ReusableMachineTemplate newTemplate(String name) { - return registerTemplate(new ReusableMachineTemplate(name)); - } - - - public List<ReusableMachineTemplate> getTemplates() { - List<ReusableMachineTemplate> result; - synchronized (templates) { result = ImmutableList.copyOf(templates); } - return result; - } - - /** all machines matching any templates */ - public MachineSet all() { - init(); - return matchedMachines; - } - - /** machines matching any templates which have not been claimed */ - public MachineSet unclaimed() { - init(); - synchronized (this) { - return matchedMachines.removed(claimedMachines); - } - } - - /** returns all machines matching the given criteria (may be claimed) */ - @SuppressWarnings("unchecked") - public MachineSet all(Predicate<NodeMetadata> criterion) { - // To avoid generics complaints in callers caused by varargs, overload here - return all(new Predicate[] {criterion}); - } - - /** returns all machines matching the given criteria (may be claimed) */ - public MachineSet all(Predicate<NodeMetadata> ...ops) { - return new MachineSet(Iterables.filter(all(), compose(ops))); - } - - /** returns unclaimed machines matching the given criteria */ - @SuppressWarnings("unchecked") - public MachineSet unclaimed(Predicate<NodeMetadata> criterion) { - // To avoid generics complaints in callers caused by varargs, overload here - return unclaimed(new Predicate[] {criterion}); - } - - /** returns unclaimed machines matching the given criteria */ - public MachineSet unclaimed(Predicate<NodeMetadata> ...criteria) { - return new MachineSet(Iterables.filter(unclaimed(), compose(criteria))); - } - - /** creates machines if necessary so that this spec exists (may already be claimed however) - * returns a set of all matching machines, guaranteed non-empty - * (but possibly some are already claimed) */ - public MachineSet ensureExists(ReusableMachineTemplate template) { - return ensureExists(1, template); - } - - public synchronized void addToBlacklist(MachineSet newToBlacklist) { - setBlacklist(blacklistedMachines.added(newToBlacklist)); - } - - /** replaces the blacklist set; callers should generally perform a refresh() - * afterwards, to trigger re-detection of blacklisted machines - */ - public synchronized void setBlacklist(MachineSet newBlacklist) { - blacklistedMachines = newBlacklist; - detectedMachines = detectedMachines.removed(blacklistedMachines); - matchedMachines = matchedMachines.removed(blacklistedMachines); - } - - /** creates machines if necessary so that this spec exists (may already be claimed however); - * returns a set of all matching machines, of size at least count (but possibly some are already claimed). - * (the pool can change at any point, so this set is a best-effort but may be out of date. - * see javadoc comments on this class.) */ - public MachineSet ensureExists(int count, ReusableMachineTemplate template) { - MachineSet current; - current = all(matching(template)); - if (current.size() >= count) - return current; - //have to create more - MachineSet moreNeeded = create(count-current.size(), template); - return current.added(moreNeeded); - } - - /** creates machines if necessary so that this spec can subsequently be claimed; - * returns all such unclaimed machines, guaranteed to be non-empty. - * (the pool can change at any point, so this set is a best-effort but may be out of date. - * see javadoc comments on this class.) */ - public MachineSet ensureUnclaimed(ReusableMachineTemplate template) { - return ensureUnclaimed(1, template); - } - - /** creates machines if necessary so that this spec can subsequently be claimed; - * returns a set of at least count unclaimed machines */ - public MachineSet ensureUnclaimed(int count, ReusableMachineTemplate template) { - MachineSet current; - current = unclaimed(matching(template)); - if (current.size() >= count) - return current; - //have to create more - MachineSet moreNeeded = create(count-current.size(), template); - return current.added(moreNeeded); - } - - public Set<ReusableMachineTemplate> getTemplatesMatchingInstance(NodeMetadata nm) { - Set<ReusableMachineTemplate> result = new LinkedHashSet<ReusableMachineTemplate>(); - for (ReusableMachineTemplate t: getTemplates()) { - if (matching(t).apply(nm)) { - result.add(t); - } - } - return result; - } - - /** creates the given number of machines of the indicated template */ - public MachineSet create(int count, ReusableMachineTemplate template) { - Set<? extends NodeMetadata> nodes; - try { - Template t = template.newJcloudsTemplate(computeService); - if (log.isDebugEnabled()) log.debug("Creating "+count+" new instances of "+t); - nodes = computeService.createNodesInGroup(getPoolName(), count, t); - } catch (RunNodesException e) { - throw Throwables.propagate(e); - } - MachineSet result = new MachineSet(nodes); - registerNewNodes(result, template); - return result; - } - protected void registerNewNodes(MachineSet result, ReusableMachineTemplate template) { - for (NodeMetadata m: result) { - Set<ReusableMachineTemplate> ts = getTemplatesMatchingInstance(m); - if (ts.isEmpty()) { - log.error("Pool "+this+", created machine "+m+" from template "+template+", but no pool templates match!"); - } else { - if (log.isDebugEnabled()) - log.debug("Pool "+this+", created machine "+m+" from template "+template+", matching templates "+ts); - } - } - synchronized (this) { - detectedMachines = detectedMachines.added(result); - matchedMachines = matchedMachines.added(result); - } - } - - /** claims the indicated number of machines with the indicated spec, creating if necessary */ - public MachineSet claim(int count, ReusableMachineTemplate t) { - init(); - Set<NodeMetadata> claiming = new LinkedHashSet<NodeMetadata>(); - while (claiming.size() < count) { - MachineSet mm = ensureUnclaimed(count - claiming.size(), t); - for (NodeMetadata m : mm) { - synchronized (this) { - if (claiming.size() < count && !claimedMachines.contains(m)) { - claiming.add(m); - claimedMachines = claimedMachines.added(new MachineSet(m)); - } - } - } - } - MachineSet result = new MachineSet(claiming); - return result; - } - - - /** claims the indicated set of machines; - * throws exception if cannot all be claimed; - * returns the set passed in if successful */ - public MachineSet claim(MachineSet set) { - init(); - synchronized (this) { - MachineSet originalClaimed = claimedMachines; - claimedMachines = claimedMachines.added(set); - MachineSet newlyClaimed = claimedMachines.removed(originalClaimed); - if (newlyClaimed.size() != set.size()) { - //did not claim all; unclaim and fail - claimedMachines = originalClaimed; - MachineSet unavailable = set.removed(newlyClaimed); - throw new IllegalArgumentException("Could not claim all requested machines; failed to claim "+unavailable); - } - return newlyClaimed; - } - } - - public int unclaim(MachineSet set) { - init(); - synchronized (this) { - MachineSet originalClaimed = claimedMachines; - claimedMachines = claimedMachines.removed(set); - return originalClaimed.size() - claimedMachines.size(); - } - } - - - public int destroy(final MachineSet set) { - init(); - synchronized (this) { - detectedMachines = detectedMachines.removed(set); - matchedMachines = matchedMachines.removed(set); - claimedMachines = claimedMachines.removed(set); - } - Set<? extends NodeMetadata> destroyed = computeService.destroyNodesMatching(new Predicate<NodeMetadata>() { - @Override - public boolean apply(NodeMetadata input) { - return set.contains(input); - } - }); - synchronized (this) { - //in case a rescan happened while we were destroying - detectedMachines = detectedMachines.removed(set); - matchedMachines = matchedMachines.removed(set); - claimedMachines = claimedMachines.removed(set); - } - return destroyed.size(); - } - - -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/brooklyn/location/jclouds/pool/MachinePoolPredicates.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/main/java/brooklyn/location/jclouds/pool/MachinePoolPredicates.java b/locations/jclouds/src/main/java/brooklyn/location/jclouds/pool/MachinePoolPredicates.java deleted file mode 100644 index e2158c1..0000000 --- a/locations/jclouds/src/main/java/brooklyn/location/jclouds/pool/MachinePoolPredicates.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * 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.pool; - -import java.util.Map; - -import org.jclouds.compute.domain.NodeMetadata; -import org.jclouds.compute.domain.Processor; -import org.jclouds.domain.Location; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.base.Predicate; -import com.google.common.base.Predicates; -import com.google.common.base.Throwables; - -public class MachinePoolPredicates { - - private static final Logger log = LoggerFactory.getLogger(MachinePoolPredicates.class); - - public static Predicate<NodeMetadata> except(final MachineSet removedItems) { - return new Predicate<NodeMetadata>() { - @Override - public boolean apply(NodeMetadata input) { - return !removedItems.contains(input); - } - }; - } - - public static Predicate<NodeMetadata> except(final Predicate<NodeMetadata> predicateToExclude) { - return Predicates.not(predicateToExclude); - } - - public static Predicate<NodeMetadata> matching(final ReusableMachineTemplate template) { - return new Predicate<NodeMetadata>() { - @Override - public boolean apply(NodeMetadata input) { - return matches(template, input); - } - }; - } - - public static Predicate<NodeMetadata> withTag(final String tag) { - return new Predicate<NodeMetadata>() { - @Override - public boolean apply(NodeMetadata input) { - return input.getTags().contains(tag); - } - }; - } - - public static Predicate<NodeMetadata> compose(final Predicate<NodeMetadata> ...predicates) { - return Predicates.and(predicates); - } - - /** True iff the node matches the criteria specified in this template. - * <p> - * NB: This only checks some of the most common fields, - * plus a hashcode (in strict mode). - * In strict mode you're practically guaranteed to match only machines created by this template. - * (Add a tag(uid) and you _will_ be guaranteed, strict mode or not.) - * <p> - * Outside strict mode, some things (OS and hypervisor) can fall through the gaps. - * But if that is a problem we can easily add them in. - * <p> - * (Caveat: If explicit Hardware, Image, and/or Template were specified in the template, - * then the hash code probably will not detect it.) - **/ - public static boolean matches(ReusableMachineTemplate template, NodeMetadata m) { - try { - // tags and user metadata - - if (! m.getTags().containsAll( template.getTags(false) )) return false; - - if (! isSubMapOf(template.getUserMetadata(false), m.getUserMetadata())) return false; - - - // common hardware parameters - - if (template.getMinRam()!=null && m.getHardware().getRam() < template.getMinRam()) return false; - - if (template.getMinCores()!=null) { - double numCores = 0; - for (Processor p: m.getHardware().getProcessors()) numCores += p.getCores(); - if (numCores+0.001 < template.getMinCores()) return false; - } - - if (template.getIs64bit()!=null) { - if (m.getOperatingSystem().is64Bit() != template.getIs64bit()) return false; - } - - if (template.getOsFamily()!=null) { - if (m.getOperatingSystem() == null || - !template.getOsFamily().equals(m.getOperatingSystem().getFamily())) return false; - } - if (template.getOsNameMatchesRegex()!=null) { - if (m.getOperatingSystem() == null || m.getOperatingSystem().getName()==null || - !m.getOperatingSystem().getName().matches(template.getOsNameMatchesRegex())) return false; - } - - if (template.getLocationId()!=null) { - if (!isLocationContainedIn(m.getLocation(), template.getLocationId())) return false; - } - - // TODO other TemplateBuilder fields and TemplateOptions - - return true; - - } catch (Exception e) { - log.warn("Error (rethrowing) trying to match "+m+" against "+template+": "+e, e); - throw Throwables.propagate(e); - } - } - - private static boolean isLocationContainedIn(Location location, String locationId) { - if (location==null) return false; - if (locationId.equals(location.getId())) return true; - return isLocationContainedIn(location.getParent(), locationId); - } - - public static boolean isSubMapOf(Map<String, String> sub, Map<String, String> bigger) { - for (Map.Entry<String, String> e: sub.entrySet()) { - if (e.getValue()==null) { - if (!bigger.containsKey(e.getKey())) return false; - if (bigger.get(e.getKey())!=null) return false; - } else { - if (!e.getValue().equals(bigger.get(e.getKey()))) return false; - } - } - return true; - } - -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/brooklyn/location/jclouds/pool/MachineSet.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/main/java/brooklyn/location/jclouds/pool/MachineSet.java b/locations/jclouds/src/main/java/brooklyn/location/jclouds/pool/MachineSet.java deleted file mode 100644 index fe21e2a..0000000 --- a/locations/jclouds/src/main/java/brooklyn/location/jclouds/pool/MachineSet.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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.pool; - -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.Set; - -import javax.annotation.concurrent.Immutable; - -import org.jclouds.compute.domain.NodeMetadata; - -import com.google.common.base.Predicate; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; - -@Immutable -public class MachineSet implements Iterable<NodeMetadata> { - - final Set<NodeMetadata> members; - - public MachineSet(Iterable<? extends NodeMetadata> m) { - members = ImmutableSet.copyOf(m); - } - public MachineSet(NodeMetadata ...nodes) { - members = ImmutableSet.copyOf(nodes); - } - - @Override - public Iterator<NodeMetadata> iterator() { - return members.iterator(); - } - - public MachineSet removed(MachineSet toRemove) { - Set<NodeMetadata> s = new LinkedHashSet<NodeMetadata>(members); - for (NodeMetadata m: toRemove) s.remove(m); - return new MachineSet(s); - } - public MachineSet added(MachineSet toAdd) { - Set<NodeMetadata> s = new LinkedHashSet<NodeMetadata>(members); - for (NodeMetadata m: toAdd) s.add(m); - return new MachineSet(s); - } - - @SuppressWarnings("unchecked") - public MachineSet filtered(Predicate<NodeMetadata> criterion) { - // To avoid generics complaints in callers caused by varargs, overload here - return filtered(new Predicate[] {criterion}); - } - - public MachineSet filtered(Predicate<NodeMetadata> ...criteria) { - return new MachineSet(Iterables.filter(members, MachinePoolPredicates.compose(criteria))); - } - - public int size() { - return members.size(); - } - - public boolean isEmpty() { - return members.isEmpty(); - } - - public boolean contains(NodeMetadata input) { - return members.contains(input); - } - - @Override - public String toString() { - return members.toString(); - } - - @Override - public int hashCode() { - return members.hashCode(); - } - - @Override - public boolean equals(Object obj) { - return (obj instanceof MachineSet) && (members.equals( ((MachineSet)obj).members )); - } - -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/brooklyn/location/jclouds/pool/ReusableMachineTemplate.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/main/java/brooklyn/location/jclouds/pool/ReusableMachineTemplate.java b/locations/jclouds/src/main/java/brooklyn/location/jclouds/pool/ReusableMachineTemplate.java deleted file mode 100644 index d1f3331..0000000 --- a/locations/jclouds/src/main/java/brooklyn/location/jclouds/pool/ReusableMachineTemplate.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * 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.pool; - - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.jclouds.compute.options.TemplateOptions; - -import brooklyn.location.jclouds.templates.PortableTemplateBuilder; - -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; - -/** - * A facility for having a template we can declare without knowing the provider, - * then find matching instances, create instances, and generally manipulate them. - * <p> - * NB: to be sure of matching a specific template, you should provide a unique id in the constructor. - * (this will force 'strict' mode.) - */ -// TODO tags/metadata semantics are not quite right, as they could apply to the server _image_ or _instance_ - -// TODO we could use a hashcode over the values of template-builder and template-options fields, as a tag/usermetadata, -// to guarantee (virtually) matching only machines created from this template (instead of asking for unique id) -public class ReusableMachineTemplate extends PortableTemplateBuilder<ReusableMachineTemplate> { - - public static final String PREFIX = "brooklyn:template."; - public static final String NAME_METADATA_KEY = PREFIX+"name"; - public static final String DESCRIPTION_METADATA_KEY = PREFIX+"name"; - public static final String HASH_METADATA_KEY = PREFIX+"hash"; - public static final String TEMPLATE_OWNER_METADATA_KEY = PREFIX+"owner"; - - private String name = null; - private String templateOwner = null; - private String description = null; - private boolean strict; - - public ReusableMachineTemplate() { strict = false; } - public ReusableMachineTemplate(String name) { name(name); } - - /** see #getName() */ - public ReusableMachineTemplate name(String name) { - this.name = name; - strict = true; - return this; - } - - /** see #getDescription() */ - public ReusableMachineTemplate description(String description) { - this.description = description; - return this; - } - - /** whether this template only matches machines instances created from this template; - * defaults true if a name is set, otherwise false. - * if false, it will ignore name, owner, and hashcode */ - public ReusableMachineTemplate strict(boolean strict) { - this.strict = strict; - return this; - } - - /** no owner, means anyone can pick this up (default) */ - public ReusableMachineTemplate templateUnowned() { - return templateOwner(null); - } - /** adds user.name as owner of this template */ - public ReusableMachineTemplate templateOwnedByMe() { - return templateOwner(System.getProperty("user.name")); - } - /** adds an owner tag to this template */ - public ReusableMachineTemplate templateOwner(String owner) { - this.templateOwner = owner; - return this; - } - - /** human-friendly name for this template. should normally be unique, it is the primary differentiator for strict matching. */ - public String getName() { - return name; - } - - /** a description for this template; this is set on created machines but _not_ used to filter them - * (so you can change description freely). */ - public String getDescription() { - return description; - } - - public String getOwner() { - return templateOwner; - } - - public boolean isStrict() { - return strict; - } - - @Override - public List<TemplateOptions> getAdditionalOptions() { - List<TemplateOptions> result = new ArrayList<TemplateOptions>(); - result.addAll(super.getAdditionalOptions()); - if (isStrict()) addStrictOptions(result); - return result; - } - - @Override - public List<TemplateOptions> getAdditionalOptionalOptions() { - List<TemplateOptions> result = new ArrayList<TemplateOptions>(); - result.addAll(super.getAdditionalOptions()); - addStrictOptions(result); - return result; - } - - protected void addStrictOptions(List<TemplateOptions> result) { - if (name!=null) result.add(TemplateOptions.Builder.userMetadata(NAME_METADATA_KEY, name)); - if (templateOwner!=null) result.add(TemplateOptions.Builder.userMetadata(TEMPLATE_OWNER_METADATA_KEY, templateOwner)); - // this is too strict -- the hash code seems to change from run to run (would be nice to fix that) -// result.add(TemplateOptions.Builder.userMetadata(HASH_METADATA_KEY, ""+hashCode())); - } - - /** computes the user metadata that this template will set (argument true) or required to match (argument false) */ - public Map<String,String> getUserMetadata(boolean includeOptional) { - return ImmutableMap.copyOf(computeAggregatedOptions(includeOptional).getUserMetadata()); - } - - /** computes the tags that this template will set (argument true) or require to match (argument false) */ - public Set<String> getTags(boolean includeOptional) { - return ImmutableSet.copyOf(computeAggregatedOptions(includeOptional).getTags()); - } - - public ReusableMachineTemplate tag(String tag) { - return tags(tag); - } - public ReusableMachineTemplate tags(String ...tags) { - return addOptions(TemplateOptions.Builder.tags(Arrays.asList(tags))); - } - - public ReusableMachineTemplate metadata(String key, String value) { - return addOptions(TemplateOptions.Builder.userMetadata(key, value)); - } - public ReusableMachineTemplate metadata(Map<String,String> m) { - return addOptions(TemplateOptions.Builder.userMetadata(m)); - } - - public ReusableMachineTemplate tagOptional(String tag) { - return tagsOptional(tag); - } - public ReusableMachineTemplate tagsOptional(String ...tags) { - return addOptionalOptions(TemplateOptions.Builder.tags(Arrays.asList(tags))); - } - - public ReusableMachineTemplate metadataOptional(String key, String value) { - return addOptionalOptions(TemplateOptions.Builder.userMetadata(key, value)); - } - public ReusableMachineTemplate metadataOptional(Map<String,String> m) { - return addOptionalOptions(TemplateOptions.Builder.userMetadata(m)); - } - - @Override - public String toString() { - String s = makeNonTrivialArgumentsString(); - return (name!=null ? name : "Template") + " [ " + s + " ]"; - } - -}
