http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/ResourceUtils.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/ResourceUtils.java b/core/src/main/java/brooklyn/util/ResourceUtils.java index a39f363..cb350af 100644 --- a/core/src/main/java/brooklyn/util/ResourceUtils.java +++ b/core/src/main/java/brooklyn/util/ResourceUtils.java @@ -53,7 +53,7 @@ import org.slf4j.LoggerFactory; import brooklyn.catalog.internal.BasicBrooklynCatalog.BrooklynLoaderTracker; import brooklyn.catalog.internal.CatalogUtils; import brooklyn.internal.BrooklynInitialization; -import brooklyn.location.basic.SshMachineLocation; +import org.apache.brooklyn.location.basic.SshMachineLocation; import brooklyn.management.classloading.JavaBrooklynClassLoadingContext; import brooklyn.util.collections.MutableMap; import brooklyn.util.exceptions.Exceptions;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/file/ArchiveTasks.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/file/ArchiveTasks.java b/core/src/main/java/brooklyn/util/file/ArchiveTasks.java index 9c6ed2f..b183f62 100644 --- a/core/src/main/java/brooklyn/util/file/ArchiveTasks.java +++ b/core/src/main/java/brooklyn/util/file/ArchiveTasks.java @@ -23,7 +23,7 @@ import java.util.Map; import org.apache.brooklyn.api.management.TaskAdaptable; import org.apache.brooklyn.api.management.TaskFactory; -import brooklyn.location.basic.SshMachineLocation; +import org.apache.brooklyn.location.basic.SshMachineLocation; import brooklyn.util.ResourceUtils; import brooklyn.util.net.Urls; import brooklyn.util.task.Tasks; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/file/ArchiveUtils.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/file/ArchiveUtils.java b/core/src/main/java/brooklyn/util/file/ArchiveUtils.java index e8233c2..d072b70 100644 --- a/core/src/main/java/brooklyn/util/file/ArchiveUtils.java +++ b/core/src/main/java/brooklyn/util/file/ArchiveUtils.java @@ -31,7 +31,7 @@ import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import brooklyn.location.basic.SshMachineLocation; +import org.apache.brooklyn.location.basic.SshMachineLocation; import brooklyn.util.ResourceUtils; import brooklyn.util.collections.MutableList; import brooklyn.util.collections.MutableMap; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/task/ssh/SshFetchTaskFactory.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/task/ssh/SshFetchTaskFactory.java b/core/src/main/java/brooklyn/util/task/ssh/SshFetchTaskFactory.java index 31f654c..bd9e96e 100644 --- a/core/src/main/java/brooklyn/util/task/ssh/SshFetchTaskFactory.java +++ b/core/src/main/java/brooklyn/util/task/ssh/SshFetchTaskFactory.java @@ -22,7 +22,7 @@ import org.apache.brooklyn.api.management.TaskFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import brooklyn.location.basic.SshMachineLocation; +import org.apache.brooklyn.location.basic.SshMachineLocation; import brooklyn.util.config.ConfigBag; // cannot be (cleanly) instantiated due to nested generic self-referential type; however trivial subclasses do allow it http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/task/ssh/SshFetchTaskWrapper.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/task/ssh/SshFetchTaskWrapper.java b/core/src/main/java/brooklyn/util/task/ssh/SshFetchTaskWrapper.java index 1d265dc..9553b4f 100644 --- a/core/src/main/java/brooklyn/util/task/ssh/SshFetchTaskWrapper.java +++ b/core/src/main/java/brooklyn/util/task/ssh/SshFetchTaskWrapper.java @@ -27,7 +27,7 @@ import org.apache.brooklyn.api.management.TaskWrapper; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; -import brooklyn.location.basic.SshMachineLocation; +import org.apache.brooklyn.location.basic.SshMachineLocation; import brooklyn.util.config.ConfigBag; import brooklyn.util.exceptions.Exceptions; import brooklyn.util.os.Os; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/task/ssh/SshPutTaskFactory.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/task/ssh/SshPutTaskFactory.java b/core/src/main/java/brooklyn/util/task/ssh/SshPutTaskFactory.java index 381341a..e2c5502 100644 --- a/core/src/main/java/brooklyn/util/task/ssh/SshPutTaskFactory.java +++ b/core/src/main/java/brooklyn/util/task/ssh/SshPutTaskFactory.java @@ -25,7 +25,7 @@ import org.apache.brooklyn.api.management.TaskFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import brooklyn.location.basic.SshMachineLocation; +import org.apache.brooklyn.location.basic.SshMachineLocation; import brooklyn.util.stream.KnownSizeInputStream; import brooklyn.util.stream.ReaderInputStream; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/task/ssh/SshPutTaskStub.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/task/ssh/SshPutTaskStub.java b/core/src/main/java/brooklyn/util/task/ssh/SshPutTaskStub.java index ec99dc8..185e819 100644 --- a/core/src/main/java/brooklyn/util/task/ssh/SshPutTaskStub.java +++ b/core/src/main/java/brooklyn/util/task/ssh/SshPutTaskStub.java @@ -20,7 +20,7 @@ package brooklyn.util.task.ssh; import java.io.InputStream; -import brooklyn.location.basic.SshMachineLocation; +import org.apache.brooklyn.location.basic.SshMachineLocation; import brooklyn.util.config.ConfigBag; import com.google.common.base.Supplier; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/task/ssh/SshTasks.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/task/ssh/SshTasks.java b/core/src/main/java/brooklyn/util/task/ssh/SshTasks.java index bf01bfe..b13b43c 100644 --- a/core/src/main/java/brooklyn/util/task/ssh/SshTasks.java +++ b/core/src/main/java/brooklyn/util/task/ssh/SshTasks.java @@ -35,10 +35,10 @@ import brooklyn.config.ConfigKey; import brooklyn.config.ConfigUtils; import brooklyn.entity.basic.BrooklynTaskTags; import brooklyn.entity.basic.ConfigKeys; -import brooklyn.location.Location; -import brooklyn.location.basic.AbstractLocation; -import brooklyn.location.basic.LocationInternal; -import brooklyn.location.basic.SshMachineLocation; +import org.apache.brooklyn.location.Location; +import org.apache.brooklyn.location.basic.AbstractLocation; +import org.apache.brooklyn.location.basic.LocationInternal; +import org.apache.brooklyn.location.basic.SshMachineLocation; import brooklyn.util.ResourceUtils; import brooklyn.util.config.ConfigBag; import brooklyn.util.internal.ssh.SshTool; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/task/ssh/internal/AbstractSshExecTaskFactory.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/task/ssh/internal/AbstractSshExecTaskFactory.java b/core/src/main/java/brooklyn/util/task/ssh/internal/AbstractSshExecTaskFactory.java index c78ce5d..86764f3 100644 --- a/core/src/main/java/brooklyn/util/task/ssh/internal/AbstractSshExecTaskFactory.java +++ b/core/src/main/java/brooklyn/util/task/ssh/internal/AbstractSshExecTaskFactory.java @@ -20,7 +20,7 @@ package brooklyn.util.task.ssh.internal; import com.google.common.base.Preconditions; -import brooklyn.location.basic.SshMachineLocation; +import org.apache.brooklyn.location.basic.SshMachineLocation; import brooklyn.util.config.ConfigBag; import brooklyn.util.task.system.ProcessTaskFactory; import brooklyn.util.task.system.ProcessTaskWrapper; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/task/ssh/internal/PlainSshExecTaskFactory.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/task/ssh/internal/PlainSshExecTaskFactory.java b/core/src/main/java/brooklyn/util/task/ssh/internal/PlainSshExecTaskFactory.java index a7c6994..efc14db 100644 --- a/core/src/main/java/brooklyn/util/task/ssh/internal/PlainSshExecTaskFactory.java +++ b/core/src/main/java/brooklyn/util/task/ssh/internal/PlainSshExecTaskFactory.java @@ -20,8 +20,7 @@ package brooklyn.util.task.ssh.internal; import java.util.List; -import brooklyn.location.basic.SshMachineLocation; -import brooklyn.util.config.ConfigBag; +import org.apache.brooklyn.location.basic.SshMachineLocation; import brooklyn.util.task.system.ProcessTaskWrapper; import com.google.common.base.Function; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/task/system/ProcessTaskFactory.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/task/system/ProcessTaskFactory.java b/core/src/main/java/brooklyn/util/task/system/ProcessTaskFactory.java index f43e47a..407111c 100644 --- a/core/src/main/java/brooklyn/util/task/system/ProcessTaskFactory.java +++ b/core/src/main/java/brooklyn/util/task/system/ProcessTaskFactory.java @@ -23,7 +23,7 @@ import java.util.Map; import org.apache.brooklyn.api.management.TaskFactory; import brooklyn.config.ConfigKey; -import brooklyn.location.basic.SshMachineLocation; +import org.apache.brooklyn.location.basic.SshMachineLocation; import brooklyn.util.internal.ssh.SshTool; import brooklyn.util.task.system.ProcessTaskStub.ScriptReturnType; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/task/system/ProcessTaskStub.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/task/system/ProcessTaskStub.java b/core/src/main/java/brooklyn/util/task/system/ProcessTaskStub.java index 9fd19e7..df37691 100644 --- a/core/src/main/java/brooklyn/util/task/system/ProcessTaskStub.java +++ b/core/src/main/java/brooklyn/util/task/system/ProcessTaskStub.java @@ -22,7 +22,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import brooklyn.location.basic.SshMachineLocation; +import org.apache.brooklyn.location.basic.SshMachineLocation; import brooklyn.util.collections.MutableMap; import brooklyn.util.config.ConfigBag; import brooklyn.util.text.Strings; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/task/system/internal/AbstractProcessTaskFactory.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/task/system/internal/AbstractProcessTaskFactory.java b/core/src/main/java/brooklyn/util/task/system/internal/AbstractProcessTaskFactory.java index 5aa84c0..e41a9a9 100644 --- a/core/src/main/java/brooklyn/util/task/system/internal/AbstractProcessTaskFactory.java +++ b/core/src/main/java/brooklyn/util/task/system/internal/AbstractProcessTaskFactory.java @@ -26,7 +26,7 @@ import org.slf4j.LoggerFactory; import brooklyn.config.ConfigKey; import brooklyn.entity.basic.BrooklynTaskTags; -import brooklyn.location.basic.SshMachineLocation; +import org.apache.brooklyn.location.basic.SshMachineLocation; import brooklyn.util.stream.Streams; import brooklyn.util.task.TaskBuilder; import brooklyn.util.task.system.ProcessTaskFactory; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/task/system/internal/ExecWithLoggingHelpers.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/task/system/internal/ExecWithLoggingHelpers.java b/core/src/main/java/brooklyn/util/task/system/internal/ExecWithLoggingHelpers.java index f39d7b6..c2b8907 100644 --- a/core/src/main/java/brooklyn/util/task/system/internal/ExecWithLoggingHelpers.java +++ b/core/src/main/java/brooklyn/util/task/system/internal/ExecWithLoggingHelpers.java @@ -28,7 +28,7 @@ import java.util.Map; import org.slf4j.Logger; import brooklyn.config.ConfigKey; -import brooklyn.location.basic.SshMachineLocation; +import org.apache.brooklyn.location.basic.SshMachineLocation; import brooklyn.util.collections.MutableMap; import brooklyn.util.config.ConfigBag; import brooklyn.util.flags.TypeCoercions; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/task/system/internal/SystemProcessTaskFactory.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/task/system/internal/SystemProcessTaskFactory.java b/core/src/main/java/brooklyn/util/task/system/internal/SystemProcessTaskFactory.java index 9b40c7f..e6eb831 100644 --- a/core/src/main/java/brooklyn/util/task/system/internal/SystemProcessTaskFactory.java +++ b/core/src/main/java/brooklyn/util/task/system/internal/SystemProcessTaskFactory.java @@ -23,7 +23,7 @@ import java.io.File; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import brooklyn.location.basic.SshMachineLocation; +import org.apache.brooklyn.location.basic.SshMachineLocation; import brooklyn.util.collections.MutableMap; import brooklyn.util.config.ConfigBag; import brooklyn.util.internal.ssh.ShellTool; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/text/TemplateProcessor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/text/TemplateProcessor.java b/core/src/main/java/brooklyn/util/text/TemplateProcessor.java index 8fdaebb..f936e93 100644 --- a/core/src/main/java/brooklyn/util/text/TemplateProcessor.java +++ b/core/src/main/java/brooklyn/util/text/TemplateProcessor.java @@ -37,7 +37,7 @@ import brooklyn.entity.basic.Entities; import brooklyn.entity.basic.EntityInternal; import brooklyn.event.basic.DependentConfiguration; import brooklyn.event.basic.Sensors; -import brooklyn.location.Location; +import org.apache.brooklyn.location.Location; import brooklyn.management.internal.ManagementContextInternal; import brooklyn.util.collections.MutableMap; import brooklyn.util.exceptions.Exceptions; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/access/BrooklynAccessUtils.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/access/BrooklynAccessUtils.java b/core/src/main/java/org/apache/brooklyn/location/access/BrooklynAccessUtils.java new file mode 100644 index 0000000..eebee14 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/access/BrooklynAccessUtils.java @@ -0,0 +1,143 @@ +/* + * 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 org.apache.brooklyn.location.access; + +import java.util.Collection; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.location.Location; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.Attributes; +import brooklyn.event.basic.BasicConfigKey; +import org.apache.brooklyn.location.MachineLocation; +import org.apache.brooklyn.location.basic.Machines; +import org.apache.brooklyn.location.basic.SshMachineLocation; +import org.apache.brooklyn.location.basic.SupportsPortForwarding; +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.guava.Maybe; +import brooklyn.util.net.Cidr; +import brooklyn.util.task.DynamicTasks; +import brooklyn.util.task.Tasks; +import brooklyn.util.task.ssh.SshTasks; +import brooklyn.util.task.system.ProcessTaskWrapper; +import brooklyn.util.text.Strings; + +import com.google.common.base.Supplier; +import com.google.common.net.HostAndPort; + +public class BrooklynAccessUtils { + + private static final Logger log = LoggerFactory.getLogger(BrooklynAccessUtils.class); + + public static final ConfigKey<PortForwardManager> PORT_FORWARDING_MANAGER = new BasicConfigKey<PortForwardManager>( + PortForwardManager.class, "brooklyn.portforwarding.manager", "A port-forwarding manager to use at an entity " + + "or a location, where supported; note this should normally be a serializable client instance to prevent " + + "the creation of multiple disconnected instances via config duplication"); + + public static final ConfigKey<Cidr> MANAGEMENT_ACCESS_CIDR = new BasicConfigKey<Cidr>( + Cidr.class, "brooklyn.portforwarding.management.cidr", "CIDR to enable by default for port-forwarding for management", + null); // TODO should be a list + + public static HostAndPort getBrooklynAccessibleAddress(Entity entity, int port) { + String host; + + // look up port forwarding + PortForwardManager pfw = entity.getConfig(PORT_FORWARDING_MANAGER); + if (pfw!=null) { + Collection<Location> ll = entity.getLocations(); + Maybe<SupportsPortForwarding> machine = Machines.findUniqueElement(ll, SupportsPortForwarding.class); + if (machine.isPresent()) { + synchronized (BrooklynAccessUtils.class) { + // TODO finer-grained synchronization + + HostAndPort hp = pfw.lookup((MachineLocation)machine.get(), port); + if (hp!=null) return hp; + + Location l = (Location) machine.get(); + if (l instanceof SupportsPortForwarding) { + Cidr source = entity.getConfig(MANAGEMENT_ACCESS_CIDR); + if (source!=null) { + log.debug("BrooklynAccessUtils requesting new port-forwarding rule to access "+port+" on "+entity+" (at "+l+", enabled for "+source+")"); + // TODO discuss, is this the best way to do it + // (will probably _create_ the port forwarding rule!) + hp = ((SupportsPortForwarding) l).getSocketEndpointFor(source, port); + if (hp!=null) return hp; + } else { + log.warn("No "+MANAGEMENT_ACCESS_CIDR.getName()+" configured for "+entity+", so cannot forward port "+port+" " + + "even though "+PORT_FORWARDING_MANAGER.getName()+" was supplied"); + } + } + } + } + } + + host = entity.getAttribute(Attributes.HOSTNAME); + if (host!=null) return HostAndPort.fromParts(host, port); + + throw new IllegalStateException("Cannot find way to access port "+port+" on "+entity+" from Brooklyn (no host.name)"); + } + + /** attempts to resolve hostnameTarget from origin + * @return null if it definitively can't be resolved, + * best-effort IP address if possible, or blank if we could not run ssh or make sense of the output */ + public static String getResolvedAddress(Entity entity, SshMachineLocation origin, String hostnameTarget) { + ProcessTaskWrapper<Integer> task = SshTasks.newSshExecTaskFactory(origin, "ping -c 1 -t 1 "+hostnameTarget) + .summary("checking resolution of "+hostnameTarget).allowingNonZeroExitCode().newTask(); + DynamicTasks.queueIfPossible(task).orSubmitAndBlock(entity).asTask().blockUntilEnded(); + if (task.asTask().isError()) { + log.warn("ping could not be run, at "+entity+" / "+origin+": "+Tasks.getError(task.asTask())); + return ""; + } + if (task.getExitCode()==null || task.getExitCode()!=0) { + if (task.getExitCode()!=null && task.getExitCode()<10) { + // small number means ping failed to resolve or ping the hostname + log.debug("not able to resolve "+hostnameTarget+" from "+origin+" for "+entity+" because exit code was "+task.getExitCode()); + return null; + } + // large number means ping probably did not run + log.warn("ping not run as expected, at "+entity+" / "+origin+" (code "+task.getExitCode()+"):\n"+task.getStdout().trim()+" --- "+task.getStderr().trim()); + return ""; + } + String out = task.getStdout(); + try { + String line1 = Strings.getFirstLine(out); + String ip = Strings.getFragmentBetween(line1, "(", ")"); + if (Strings.isNonBlank(ip)) + return ip; + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + /* ignore non-parseable output */ + } + if (out.contains("127.0.0.1")) return "127.0.0.1"; + return ""; + } + + public static Supplier<String> resolvedAddressSupplier(final Entity entity, final SshMachineLocation origin, final String hostnameTarget) { + return new Supplier<String>() { + @Override + public String get() { + return getResolvedAddress(entity, origin, hostnameTarget); + } + }; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManager.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManager.java b/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManager.java new file mode 100644 index 0000000..922231b --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManager.java @@ -0,0 +1,327 @@ +/* + * 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 org.apache.brooklyn.location.access; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.ConfigKeys; +import org.apache.brooklyn.location.Location; +import com.google.common.annotations.Beta; +import com.google.common.base.Objects; +import com.google.common.base.Predicate; +import com.google.common.net.HostAndPort; + +import java.util.Collection; + +/** + * Acts as a registry for existing port mappings (e.g. the public endpoints for accessing specific + * ports on private VMs). This could be using DNAT, or iptables port-forwarding, or Docker port-mapping + * via the host, or any other port mapping approach. + * + * Also controls the allocation of ports via {@link #acquirePublicPort(String)} + * (e.g. for port-mapping with DNAT, then which port to use for the public side). + * + * Implementations typically will not know anything about what the firewall/IP actually is, they just + * handle a unique identifier for it. + * + * To use, see {@link PortForwardManagerLocationResolver}, with code such as + * {@code managementContext.getLocationRegistry().resolve("portForwardManager(scope=global)")}. + * + * @see PortForwardManagerImpl for implementation notes and considerations. + */ +@Beta +public interface PortForwardManager extends Location { + + @Beta + class AssociationMetadata { + private final String publicIpId; + private final HostAndPort publicEndpoint; + private final Location location; + private final int privatePort; + + /** + * Users are discouraged from calling this constructor; the signature may change in future releases. + * Instead, instances will be created automatically by Brooklyn to be passed to the + * {@link AssociationListener#onAssociationCreated(AssociationMetadata)} method. + */ + public AssociationMetadata(String publicIpId, HostAndPort publicEndpoint, Location location, int privatePort) { + this.publicIpId = publicIpId; + this.publicEndpoint = publicEndpoint; + this.location = location; + this.privatePort = privatePort; + } + + public String getPublicIpId() { + return publicIpId; + } + + public HostAndPort getPublicEndpoint() { + return publicEndpoint; + } + + public Location getLocation() { + return location; + } + + public int getPrivatePort() { + return privatePort; + } + + public String toString() { + return Objects.toStringHelper(this) + .add("publicIpId", publicIpId) + .add("publicEndpoint", publicEndpoint) + .add("location", location) + .add("privatePort", privatePort) + .toString(); + } + } + + @Beta + interface AssociationListener { + void onAssociationCreated(AssociationMetadata metadata); + void onAssociationDeleted(AssociationMetadata metadata); + } + + /** + * The intention is that there is one PortForwardManager instance per "scope". If you + * use global, then it will be a shared instance (for that management context). If you + * pass in your own name (e.g. "docker-fjie3") then it will shared with just any other + * places that use that same location spec (e.g. {@code portForwardManager(scope=docker-fjie3)}). + */ + // TODO Note: using name "scope" rather than "brooklyn.portForwardManager.scope" so that location spec + // "portForwardManager(scope=global)" works, rather than having to do + // portForwardManager(brooklyn.portForwardManager.scope=global). + // The config being read by the PortForwardManagerLocationResolver doesn't respect @SetFromFlag("scope"). + public static final ConfigKey<String> SCOPE = ConfigKeys.newStringConfigKey( + "scope", + "The scope that this applies to, defaulting to global", + "global"); + + @Beta + public static final ConfigKey<Integer> PORT_FORWARD_MANAGER_STARTING_PORT = ConfigKeys.newIntegerConfigKey( + "brooklyn.portForwardManager.startingPort", + "The starting port for assigning port numbers, such as for DNAT", + 11000); + + public String getScope(); + + /** + * Reserves a unique public port on the given publicIpId. + * <p> + * Often followed by {@link #associate(String, HostAndPort, int)} or {@link #associate(String, HostAndPort, Location, int)} + * to enable {@link #lookup(String, int)} or {@link #lookup(Location, int)} respectively. + */ + public int acquirePublicPort(String publicIpId); + + /** + * Records a location and private port against a public endpoint (ip and port), + * to support {@link #lookup(Location, int)}. + * <p> + * Superfluous if {@link #acquirePublicPort(String, Location, int)} was used, + * but strongly recommended if {@link #acquirePublicPortExplicit(String, int)} was used + * e.g. if the location is not known ahead of time. + */ + public void associate(String publicIpId, HostAndPort publicEndpoint, Location l, int privatePort); + + /** + * Records a mapping for publicIpId:privatePort to a public endpoint, such that it can + * subsequently be looked up using {@link #lookup(String, int)}. + */ + public void associate(String publicIpId, HostAndPort publicEndpoint, int privatePort); + + /** + * Registers a listener, which will be notified each time a new port mapping is associated. See {@link #associate(String, HostAndPort, int)} + * and {@link #associate(String, HostAndPort, Location, int)}. + */ + @Beta + public void addAssociationListener(AssociationListener listener, Predicate<? super AssociationMetadata> filter); + + @Beta + public void removeAssociationListener(AssociationListener listener); + + /** + * Returns the public ip hostname and public port for use contacting the given endpoint. + * <p> + * Will return null if: + * <ul> + * <li>No publicPort is associated with this location and private port. + * <li>No publicIpId is associated with this location and private port. + * <li>No publicIpHostname is recorded against the associated publicIpId. + * </ul> + * Conceivably this may have to be access-location specific. + * + * @see #recordPublicIpHostname(String, String) + */ + public HostAndPort lookup(Location l, int privatePort); + + /** + * Returns the public endpoint (host and port) for use contacting the given endpoint. + * + * Expects a previous call to {@link #associate(String, HostAndPort, int)}, to register + * the endpoint. + * + * Will return null if there has not been a public endpoint associated with this pairing. + */ + public HostAndPort lookup(String publicIpId, int privatePort); + + /** + * Clears the given port mapping, returning true if there was a match. + */ + public boolean forgetPortMapping(String publicIpId, int publicPort); + + /** + * Clears the port mappings associated with the given location, returning true if there were any matches. + */ + public boolean forgetPortMappings(Location location); + + /** + * Clears the port mappings associated with the given publicIpId, returning true if there were any matches. + */ + public boolean forgetPortMappings(String publicIpId); + + public String toVerboseString(); + + + /////////////////////////////////////////////////////////////////////////////////// + // Deprecated + /////////////////////////////////////////////////////////////////////////////////// + + /** + * Reserves a unique public port for the purpose of forwarding to the given target, + * associated with a given location for subsequent lookup purpose. + * <p> + * If already allocated, returns the previously allocated. + * + * @deprecated since 0.7.0; use {@link #acquirePublicPort(String)}, and then use {@link #associate(String, HostAndPort, int)} or {@link #associate(String, HostAndPort, Location, int)} + */ + @Deprecated + public int acquirePublicPort(String publicIpId, Location l, int privatePort); + + /** + * Returns old mapping if it existed, null if it is new. + * + * @deprecated since 0.7.0; use {@link #associate(String, HostAndPort, int)} or {@link #associate(String, HostAndPort, Location, int)} + */ + @Deprecated + public PortMapping acquirePublicPortExplicit(String publicIpId, int port); + + /** + * Records a location and private port against a publicIp and public port, + * to support {@link #lookup(Location, int)}. + * <p> + * Superfluous if {@link #acquirePublicPort(String, Location, int)} was used, + * but strongly recommended if {@link #acquirePublicPortExplicit(String, int)} was used + * e.g. if the location is not known ahead of time. + * + * @deprecated Use {@link #associate(String, HostAndPort, Location, int)} + */ + @Deprecated + public void associate(String publicIpId, int publicPort, Location l, int privatePort); + + /** + * Records a public hostname or address to be associated with the given publicIpId for lookup purposes. + * <p> + * Conceivably this may have to be access-location specific. + * + * @deprecated Use {@link #associate(String, HostAndPort, int)} or {@link #associate(String, HostAndPort, Location, int)} + */ + @Deprecated + public void recordPublicIpHostname(String publicIpId, String hostnameOrPublicIpAddress); + + /** + * Returns a recorded public hostname or address. + * + * @deprecated Use {@link #lookup(String, int)} or {@link #lookup(Location, int)} + */ + @Deprecated + public String getPublicIpHostname(String publicIpId); + + /** + * Clears a previous call to {@link #recordPublicIpHostname(String, String)}. + * + * @deprecated Use {@link #forgetPortMapping(String, int)} or {@link #forgetPortMappings(Location)} + */ + @Deprecated + public boolean forgetPublicIpHostname(String publicIpId); + + /** + * Returns true if this implementation is a client which is immutable/safe for serialization + * i.e. it delegates to something on an entity or location elsewhere. + * + * @deprecated since 0.7.0; no need to separate client-proxy from impl + */ + @Deprecated + public boolean isClient(); + + + /////////////////////////////////////////////////////////////////////////////////// + // Deprecated; just internal + /////////////////////////////////////////////////////////////////////////////////// + + /** + * Returns the port mapping for a given publicIpId and public port. + * + * @deprecated since 0.7.0; this method will be internal only + */ + @Deprecated + public PortMapping getPortMappingWithPublicSide(String publicIpId, int publicPort); + + /** + * Returns the subset of port mappings associated with a given public IP ID. + * + * @deprecated since 0.7.0; this method will be internal only + */ + @Deprecated + public Collection<PortMapping> getPortMappingWithPublicIpId(String publicIpId); + + /** + * @see {@link #forgetPortMapping(String, int)} and {@link #forgetPortMappings(Location)} + * + * @deprecated since 0.7.0; this method will be internal only + */ + @Deprecated + public boolean forgetPortMapping(PortMapping m); + + /** + * Returns the public host and port for use accessing the given mapping. + * <p> + * Conceivably this may have to be access-location specific. + * + * @deprecated since 0.7.0; this method will be internal only + */ + @Deprecated + public HostAndPort getPublicHostAndPort(PortMapping m); + + /** + * Returns the subset of port mappings associated with a given location. + * + * @deprecated since 0.7.0; this method will be internal only + */ + @Deprecated + public Collection<PortMapping> getLocationPublicIpIds(Location l); + + /** + * Returns the mapping to a given private port, or null if none. + * + * @deprecated since 0.7.0; this method will be internal only + */ + @Deprecated + public PortMapping getPortMappingWithPrivateSide(Location l, int privatePort); + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerAuthority.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerAuthority.java b/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerAuthority.java new file mode 100644 index 0000000..da7d098 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerAuthority.java @@ -0,0 +1,47 @@ +/* + * 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 org.apache.brooklyn.location.access; + + +import org.apache.brooklyn.api.entity.Entity; + +import brooklyn.entity.basic.EntityInternal; + +/** + * @deprecated since 0.7.0; use {@link PortForwardManagerImpl} + */ +@Deprecated +public class PortForwardManagerAuthority extends PortForwardManagerImpl { + private Entity owningEntity; + + public PortForwardManagerAuthority() { + } + + public PortForwardManagerAuthority(Entity owningEntity) { + this.owningEntity = owningEntity; + } + + protected void onChanged() { + if (owningEntity != null) { + ((EntityInternal) owningEntity).requestPersist(); + } else { + super.onChanged(); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerClient.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerClient.java b/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerClient.java new file mode 100644 index 0000000..09aea72 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerClient.java @@ -0,0 +1,406 @@ +/* + * 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 org.apache.brooklyn.location.access; + +import java.util.Collection; +import java.util.Map; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.event.AttributeSensor; + +import brooklyn.config.ConfigKey; +import brooklyn.config.ConfigKey.HasConfigKey; +import org.apache.brooklyn.location.Location; +import brooklyn.util.exceptions.Exceptions; + +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.base.Supplier; +import com.google.common.net.HostAndPort; + +/** + * @deprecated since 0.7.0; just use the {@link PortForwardManager}, or a direct reference to its impl {@link PortForwardManagerImpl} + */ +@Deprecated +public class PortForwardManagerClient implements PortForwardManager { + + private static final long serialVersionUID = -295204304305332895L; + + protected final Supplier<PortForwardManager> delegateSupplier; + private transient volatile PortForwardManager _delegate; + + protected PortForwardManagerClient(Supplier<PortForwardManager> supplier) { + this.delegateSupplier = supplier; + } + + /** creates an instance given a supplier; + * the supplier should be brooklyn-persistable, that is to say + * references should be in terms of entities/locations + * which can retrieve an authoritative source even under cloning */ + public static PortForwardManager fromSupplier(Supplier<PortForwardManager> supplier) { + return new PortForwardManagerClient(supplier); + } + + /** creates an instance given an entity and an interface method it implements to retrieve the PortForwardManager */ + public static PortForwardManager fromMethodOnEntity(final Entity entity, final String getterMethodOnEntity) { + Preconditions.checkNotNull(entity); + Preconditions.checkNotNull(getterMethodOnEntity); + return new PortForwardManagerClient(new Supplier<PortForwardManager>() { + @Override + public PortForwardManager get() { + PortForwardManager result; + try { + result = (PortForwardManager) entity.getClass().getMethod(getterMethodOnEntity).invoke(entity); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + throw new IllegalStateException("Cannot invoke "+getterMethodOnEntity+" on "+entity+" ("+entity.getClass()+"): "+e, e); + } + if (result==null) + throw new IllegalStateException("No PortForwardManager available via "+getterMethodOnEntity+" on "+entity+" (returned null)"); + return result; + } + }); + } + + /** creates an instance given an entity and {@link AttributeSensor} to retrieve the PortForwardManager */ + public static PortForwardManager fromAttributeOnEntity(final Entity entity, final AttributeSensor<PortForwardManager> attributeOnEntity) { + Preconditions.checkNotNull(entity); + Preconditions.checkNotNull(attributeOnEntity); + return new PortForwardManagerClient(new Supplier<PortForwardManager>() { + @Override + public PortForwardManager get() { + PortForwardManager result = entity.getAttribute(attributeOnEntity); + if (result==null) + throw new IllegalStateException("No PortForwardManager available via "+attributeOnEntity+" on "+entity+" (returned null)"); + return result; + } + }); + } + + protected PortForwardManager getDelegate() { + if (_delegate==null) { + _delegate = delegateSupplier.get(); + } + return _delegate; + } + + @Override + public int acquirePublicPort(String publicIpId) { + return getDelegate().acquirePublicPort(publicIpId); + } + + @Override + public void associate(String publicIpId, HostAndPort publicEndpoint, Location l, int privatePort) { + getDelegate().associate(publicIpId, publicEndpoint, l, privatePort); + } + + @Override + public void associate(String publicIpId, HostAndPort publicEndpoint, int privatePort) { + getDelegate().associate(publicIpId, publicEndpoint, privatePort); + } + + @Override + public HostAndPort lookup(Location l, int privatePort) { + return getDelegate().lookup(l, privatePort); + } + + @Override + public HostAndPort lookup(String publicIpId, int privatePort) { + return getDelegate().lookup(publicIpId, privatePort); + } + + @Override + public boolean forgetPortMapping(String publicIpId, int publicPort) { + return getDelegate().forgetPortMapping(publicIpId, publicPort); + } + + @Override + public boolean forgetPortMappings(Location location) { + return getDelegate().forgetPortMappings(location); + } + + @Override + public boolean forgetPortMappings(String publicIpId) { + return getDelegate().forgetPortMappings(publicIpId); + } + + @Override + public String getId() { + return getDelegate().getId(); + } + + @Override + public String getScope() { + return getDelegate().getScope(); + } + + @Override + public void addAssociationListener(AssociationListener listener, Predicate<? super AssociationMetadata> filter) { + getDelegate().addAssociationListener(listener, filter); + } + + @Override + public void removeAssociationListener(AssociationListener listener) { + getDelegate().removeAssociationListener(listener); + } + + @Override + public String toVerboseString() { + return getClass().getName()+"[wrapping="+getDelegate().toVerboseString()+"]"; + } + + /////////////////////////////////////////////////////////////////////////////////// + // Deprecated + /////////////////////////////////////////////////////////////////////////////////// + + /** + * Reserves a unique public port for the purpose of forwarding to the given target, + * associated with a given location for subsequent lookup purpose. + * <p> + * If already allocated, returns the previously allocated. + * + * @deprecated since 0.7.0; use {@link #acquirePublicPort(String)}, and then use {@link #associate(String, HostAndPort, int)} or {@link #associate(String, HostAndPort, Location, int)} + */ + @Override + @Deprecated + public int acquirePublicPort(String publicIpId, Location l, int privatePort) { + return getDelegate().acquirePublicPort(publicIpId, l, privatePort); + } + + /** + * Returns old mapping if it existed, null if it is new. + * + * @deprecated since 0.7.0; use {@link #associate(String, HostAndPort, int)} or {@link #associate(String, HostAndPort, Location, int)} + */ + @Override + @Deprecated + public PortMapping acquirePublicPortExplicit(String publicIpId, int publicPort) { + return getDelegate().acquirePublicPortExplicit(publicIpId, publicPort); + } + + /** + * Records a location and private port against a publicIp and public port, + * to support {@link #lookup(Location, int)}. + * <p> + * Superfluous if {@link #acquirePublicPort(String, Location, int)} was used, + * but strongly recommended if {@link #acquirePublicPortExplicit(String, int)} was used + * e.g. if the location is not known ahead of time. + * + * @deprecated Use {@link #associate(String, HostAndPort, Location, int)} + */ + @Override + @Deprecated + public void associate(String publicIpId, int publicPort, Location l, int privatePort) { + getDelegate().associate(publicIpId, publicPort, l, privatePort); + } + + /** + * Records a public hostname or address to be associated with the given publicIpId for lookup purposes. + * <p> + * Conceivably this may have to be access-location specific. + * + * @deprecated Use {@link #associate(String, HostAndPort, int)} or {@link #associate(String, HostAndPort, Location, int)} + */ + @Override + @Deprecated + public void recordPublicIpHostname(String publicIpId, String hostnameOrPublicIpAddress) { + getDelegate().recordPublicIpHostname(publicIpId, hostnameOrPublicIpAddress); + } + + /** + * Returns a recorded public hostname or address. + * + * @deprecated Use {@link #lookup(String, int)} or {@link #lookup(Location, int)} + */ + @Override + @Deprecated + public String getPublicIpHostname(String publicIpId) { + return getDelegate().getPublicIpHostname(publicIpId); + } + + /** + * Clears a previous call to {@link #recordPublicIpHostname(String, String)}. + * + * @deprecated Use {@link #forgetPortMapping(String, int)} or {@link #forgetPortMapping(Location, int)} + */ + @Override + @Deprecated + public boolean forgetPublicIpHostname(String publicIpId) { + return getDelegate().forgetPublicIpHostname(publicIpId); + } + + @Override + @Deprecated + public boolean isClient() { + return true; + } + + + /////////////////////////////////////////////////////////////////////////////////// + // Deprecated; just internal + /////////////////////////////////////////////////////////////////////////////////// + + /** + * Returns the port mapping for a given publicIpId and public port. + * + * @deprecated since 0.7.0; this method will be internal only + */ + @Override + @Deprecated + public PortMapping getPortMappingWithPublicSide(String publicIpId, int publicPort) { + return getDelegate().getPortMappingWithPublicSide(publicIpId, publicPort); + } + + /** + * Returns the subset of port mappings associated with a given public IP ID. + * + * @deprecated since 0.7.0; this method will be internal only + */ + @Override + @Deprecated + public Collection<PortMapping> getPortMappingWithPublicIpId(String publicIpId) { + return getDelegate().getPortMappingWithPublicIpId(publicIpId); + } + + /** + * @see #forgetPortMapping(String, int) + * + * @deprecated since 0.7.0; this method will be internal only + */ + @Override + @Deprecated + public boolean forgetPortMapping(PortMapping m) { + return getDelegate().forgetPortMapping(m); + } + + /** + * Returns the public host and port for use accessing the given mapping. + * <p> + * Conceivably this may have to be access-location specific. + * + * @deprecated since 0.7.0; this method will be internal only + */ + @Override + @Deprecated + public HostAndPort getPublicHostAndPort(PortMapping m) { + return getDelegate().getPublicHostAndPort(m); + } + + /** + * Returns the subset of port mappings associated with a given location. + * + * @deprecated since 0.7.0; this method will be internal only + */ + @Override + @Deprecated + public Collection<PortMapping> getLocationPublicIpIds(Location l) { + return getDelegate().getLocationPublicIpIds(l); + } + + /** + * Returns the mapping to a given private port, or null if none. + * + * @deprecated since 0.7.0; this method will be internal only + */ + @Override + @Deprecated + public PortMapping getPortMappingWithPrivateSide(Location l, int privatePort) { + return getDelegate().getPortMappingWithPrivateSide(l, privatePort); + } + + @Override + public String toString() { + return getClass().getName()+"[id="+getId()+"]"; + } + + @Override + public String getDisplayName() { + return getDelegate().getDisplayName(); + } + + @Override + public Location getParent() { + return getDelegate().getParent(); + } + + @Override + public Collection<Location> getChildren() { + return getDelegate().getChildren(); + } + + @Override + public void setParent(Location newParent) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsLocation(Location potentialDescendent) { + return getDelegate().containsLocation(potentialDescendent); + } + + @Override + public <T> T getConfig(ConfigKey<T> key) { + return getDelegate().getConfig(key); + } + + @Override + public <T> T getConfig(HasConfigKey<T> key) { + return getDelegate().getConfig(key); + } + + @Override + public boolean hasConfig(ConfigKey<?> key, boolean includeInherited) { + return getDelegate().hasConfig(key, includeInherited); + } + + @Override + public Map<String, Object> getAllConfig(boolean includeInherited) { + return getDelegate().getAllConfig(includeInherited); + } + + @Override + public boolean hasExtension(Class<?> extensionType) { + return getDelegate().hasExtension(extensionType); + } + + @Override + public <T> T getExtension(Class<T> extensionType) { + return getDelegate().getExtension(extensionType); + } + + @Override + public String getCatalogItemId() { + return getDelegate().getCatalogItemId(); + } + + @Override + public TagSupport tags() { + return getDelegate().tags(); + } + + @Override + public <T> T setConfig(ConfigKey<T> key, T val) { + return getDelegate().setConfig(key, val); + } + + @Override + public ConfigurationSupport config() { + return getDelegate().config(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerImpl.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerImpl.java b/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerImpl.java new file mode 100644 index 0000000..6493f30 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerImpl.java @@ -0,0 +1,506 @@ +/* + * 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 org.apache.brooklyn.location.access; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.brooklyn.api.entity.rebind.RebindContext; +import org.apache.brooklyn.api.entity.rebind.RebindSupport; +import org.apache.brooklyn.location.Location; +import org.apache.brooklyn.location.basic.AbstractLocation; +import org.apache.brooklyn.mementos.LocationMemento; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.entity.rebind.BasicLocationRebindSupport; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.exceptions.Exceptions; + +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.net.HostAndPort; + +/** + * + * @author aled + * + * TODO This implementation is not efficient, and currently has a cap of about 50000 rules. + * Need to improve the efficiency and scale. + * A quick win could be to use a different portReserved counter for each publicIpId, + * when calling acquirePublicPort? + * + * TODO Callers need to be more careful in acquirePublicPort for which ports are actually in use. + * If multiple apps sharing the same public-ip (e.g. in the same vcloud-director vOrg) then they + * must not allocate the same public port (e.g. ensure they share the same PortForwardManager + * by using the same scope in + * {@code managementContext.getLocationRegistry().resolve("portForwardManager(scope=global)")}. + * However, this still doesn't check if the port is *actually* available. For example, if a + * different Brooklyn instance is also deploying there then we can get port conflicts, or if + * some ports in that range are already in use (e.g. due to earlier dev/test runs) then this + * will not be respected. Callers should probably figure out the port number themselves, but + * that also leads to concurrency issues. + * + * TODO The publicIpId means different things to different callers: + * <ul> + * <li> In acquirePublicPort() it is (often?) an identifier of the actual public ip. + * <li> In later calls to associate(), it is (often?) an identifier for the target machine + * such as the jcloudsMachine.getJcloudsId(). + * </ul> + */ +@SuppressWarnings("serial") +public class PortForwardManagerImpl extends AbstractLocation implements PortForwardManager { + + private static final Logger log = LoggerFactory.getLogger(PortForwardManagerImpl.class); + + protected final Map<String,PortMapping> mappings = new LinkedHashMap<String,PortMapping>(); + + private final Map<AssociationListener, Predicate<? super AssociationMetadata>> associationListeners = new ConcurrentHashMap<AssociationListener, Predicate<? super AssociationMetadata>>(); + + @Deprecated + protected final Map<String,String> publicIpIdToHostname = new LinkedHashMap<String,String>(); + + // horrible hack -- see javadoc above + private final AtomicInteger portReserved = new AtomicInteger(11000); + + private final Object mutex = new Object(); + + public PortForwardManagerImpl() { + super(); + if (isLegacyConstruction()) { + log.warn("Deprecated construction of "+PortForwardManagerImpl.class.getName()+"; instead use location resolver"); + } + } + + @Override + public void init() { + super.init(); + Integer portStartingPoint; + Object rawPort = getAllConfigBag().getStringKey(PORT_FORWARD_MANAGER_STARTING_PORT.getName()); + if (rawPort != null) { + portStartingPoint = getConfig(PORT_FORWARD_MANAGER_STARTING_PORT); + } else { + portStartingPoint = getManagementContext().getConfig().getConfig(PORT_FORWARD_MANAGER_STARTING_PORT); + } + portReserved.set(portStartingPoint); + log.debug(this+" set initial port to "+portStartingPoint); + } + + // TODO Need to use attributes for these so they are persisted (once a location is an entity), + // rather than this deprecated approach of custom fields. + @Override + public RebindSupport<LocationMemento> getRebindSupport() { + return new BasicLocationRebindSupport(this) { + @Override public LocationMemento getMemento() { + Map<String, PortMapping> mappingsCopy; + Map<String,String> publicIpIdToHostnameCopy; + synchronized (mutex) { + mappingsCopy = MutableMap.copyOf(mappings); + publicIpIdToHostnameCopy = MutableMap.copyOf(publicIpIdToHostname); + } + return getMementoWithProperties(MutableMap.<String,Object>of( + "mappings", mappingsCopy, + "portReserved", portReserved.get(), + "publicIpIdToHostname", publicIpIdToHostnameCopy)); + } + @Override + protected void doReconstruct(RebindContext rebindContext, LocationMemento memento) { + super.doReconstruct(rebindContext, memento); + mappings.putAll( Preconditions.checkNotNull((Map<String, PortMapping>) memento.getCustomField("mappings"), "mappings was not serialized correctly")); + portReserved.set( (Integer)memento.getCustomField("portReserved")); + publicIpIdToHostname.putAll( Preconditions.checkNotNull((Map<String, String>)memento.getCustomField("publicIpIdToHostname"), "publicIpIdToHostname was not serialized correctly") ); + } + }; + } + + @Override + public int acquirePublicPort(String publicIpId) { + int port; + synchronized (mutex) { + // far too simple -- see javadoc above + port = getNextPort(); + + // TODO When delete deprecated code, stop registering PortMapping until associate() is called + PortMapping mapping = new PortMapping(publicIpId, port, null, -1); + log.debug(this+" allocating public port "+port+" on "+publicIpId+" (no association info yet)"); + + mappings.put(makeKey(publicIpId, port), mapping); + } + onChanged(); + return port; + } + + protected int getNextPort() { + // far too simple -- see javadoc above + return portReserved.getAndIncrement(); + } + + @Override + public void associate(String publicIpId, HostAndPort publicEndpoint, Location l, int privatePort) { + associateImpl(publicIpId, publicEndpoint, l, privatePort); + emitAssociationCreatedEvent(publicIpId, publicEndpoint, l, privatePort); + } + + @Override + public void associate(String publicIpId, HostAndPort publicEndpoint, int privatePort) { + associateImpl(publicIpId, publicEndpoint, null, privatePort); + emitAssociationCreatedEvent(publicIpId, publicEndpoint, null, privatePort); + } + + protected void associateImpl(String publicIpId, HostAndPort publicEndpoint, Location l, int privatePort) { + synchronized (mutex) { + String publicIp = publicEndpoint.getHostText(); + int publicPort = publicEndpoint.getPort(); + recordPublicIpHostname(publicIpId, publicIp); + PortMapping mapping = new PortMapping(publicIpId, publicEndpoint, l, privatePort); + PortMapping oldMapping = getPortMappingWithPublicSide(publicIpId, publicPort); + log.debug(this+" associating public "+publicEndpoint+" on "+publicIpId+" with private port "+privatePort+" at "+l+" ("+mapping+")" + +(oldMapping == null ? "" : " (overwriting "+oldMapping+" )")); + mappings.put(makeKey(publicIpId, publicPort), mapping); + } + onChanged(); + } + + private void emitAssociationCreatedEvent(String publicIpId, HostAndPort publicEndpoint, Location location, int privatePort) { + AssociationMetadata metadata = new AssociationMetadata(publicIpId, publicEndpoint, location, privatePort); + for (Map.Entry<AssociationListener, Predicate<? super AssociationMetadata>> entry : associationListeners.entrySet()) { + if (entry.getValue().apply(metadata)) { + try { + entry.getKey().onAssociationCreated(metadata); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + log.warn("Exception thrown when emitting association creation event " + metadata, e); + } + } + } + } + + @Override + public HostAndPort lookup(Location l, int privatePort) { + synchronized (mutex) { + for (PortMapping m: mappings.values()) { + if (l.equals(m.target) && privatePort == m.privatePort) + return getPublicHostAndPort(m); + } + } + return null; + } + + @Override + public HostAndPort lookup(String publicIpId, int privatePort) { + synchronized (mutex) { + for (PortMapping m: mappings.values()) { + if (publicIpId.equals(m.publicIpId) && privatePort==m.privatePort) + return getPublicHostAndPort(m); + } + } + return null; + } + + @Override + public boolean forgetPortMapping(String publicIpId, int publicPort) { + PortMapping old; + synchronized (mutex) { + old = mappings.remove(makeKey(publicIpId, publicPort)); + if (old != null) { + emitAssociationDeletedEvent(associationMetadataFromPortMapping(old)); + } + log.debug("cleared port mapping for "+publicIpId+":"+publicPort+" - "+old); + } + if (old != null) onChanged(); + return (old != null); + } + + @Override + public boolean forgetPortMappings(Location l) { + List<PortMapping> result = Lists.newArrayList(); + synchronized (mutex) { + for (Iterator<PortMapping> iter = mappings.values().iterator(); iter.hasNext();) { + PortMapping m = iter.next(); + if (l.equals(m.target)) { + iter.remove(); + result.add(m); + emitAssociationDeletedEvent(associationMetadataFromPortMapping(m)); + } + } + } + if (log.isDebugEnabled()) log.debug("cleared all port mappings for "+l+" - "+result); + if (!result.isEmpty()) { + onChanged(); + } + return !result.isEmpty(); + } + + @Override + public boolean forgetPortMappings(String publicIpId) { + List<PortMapping> result = Lists.newArrayList(); + synchronized (mutex) { + for (Iterator<PortMapping> iter = mappings.values().iterator(); iter.hasNext();) { + PortMapping m = iter.next(); + if (publicIpId.equals(m.publicIpId)) { + iter.remove(); + result.add(m); + emitAssociationDeletedEvent(associationMetadataFromPortMapping(m)); + } + } + } + if (log.isDebugEnabled()) log.debug("cleared all port mappings for "+publicIpId+" - "+result); + if (!result.isEmpty()) { + onChanged(); + } + return !result.isEmpty(); + } + + private void emitAssociationDeletedEvent(AssociationMetadata metadata) { + for (Map.Entry<AssociationListener, Predicate<? super AssociationMetadata>> entry : associationListeners.entrySet()) { + if (entry.getValue().apply(metadata)) { + try { + entry.getKey().onAssociationDeleted(metadata); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + log.warn("Exception thrown when emitting association creation event " + metadata, e); + } + } + } + } + + @Override + protected ToStringHelper string() { + int size; + synchronized (mutex) { + size = mappings.size(); + } + return super.string().add("scope", getScope()).add("mappingsSize", size); + } + + @Override + public String toVerboseString() { + String mappingsStr; + synchronized (mutex) { + mappingsStr = mappings.toString(); + } + return string().add("mappings", mappingsStr).toString(); + } + + @Override + public String getScope() { + return checkNotNull(getConfig(SCOPE), "scope"); + } + + @Override + public boolean isClient() { + return false; + } + + @Override + public void addAssociationListener(AssociationListener listener, Predicate<? super AssociationMetadata> filter) { + associationListeners.put(listener, filter); + } + + @Override + public void removeAssociationListener(AssociationListener listener) { + associationListeners.remove(listener); + } + + protected String makeKey(String publicIpId, int publicPort) { + return publicIpId+":"+publicPort; + } + + private AssociationMetadata associationMetadataFromPortMapping(PortMapping portMapping) { + String publicIpId = portMapping.getPublicEndpoint().getHostText(); + HostAndPort publicEndpoint = portMapping.getPublicEndpoint(); + Location location = portMapping.getTarget(); + int privatePort = portMapping.getPrivatePort(); + return new AssociationMetadata(publicIpId, publicEndpoint, location, privatePort); + } + + /////////////////////////////////////////////////////////////////////////////////// + // Internal state, for generating memento + /////////////////////////////////////////////////////////////////////////////////// + + public List<PortMapping> getPortMappings() { + synchronized (mutex) { + return ImmutableList.copyOf(mappings.values()); + } + } + + public Map<String, Integer> getPortCounters() { + return ImmutableMap.of("global", portReserved.get()); + } + + + /////////////////////////////////////////////////////////////////////////////////// + // Deprecated + /////////////////////////////////////////////////////////////////////////////////// + + @Override + @Deprecated + public PortMapping acquirePublicPortExplicit(String publicIpId, int port) { + PortMapping mapping = new PortMapping(publicIpId, port, null, -1); + log.debug("assigning explicit public port "+port+" at "+publicIpId); + PortMapping result; + synchronized (mutex) { + result = mappings.put(makeKey(publicIpId, port), mapping); + } + onChanged(); + return result; + } + + @Override + @Deprecated + public boolean forgetPortMapping(PortMapping m) { + return forgetPortMapping(m.publicIpId, m.publicPort); + } + + @Override + @Deprecated + public void recordPublicIpHostname(String publicIpId, String hostnameOrPublicIpAddress) { + log.debug("recording public IP "+publicIpId+" associated with "+hostnameOrPublicIpAddress); + synchronized (mutex) { + String old = publicIpIdToHostname.put(publicIpId, hostnameOrPublicIpAddress); + if (old!=null && !old.equals(hostnameOrPublicIpAddress)) + log.warn("Changing hostname recorded against public IP "+publicIpId+"; from "+old+" to "+hostnameOrPublicIpAddress); + } + onChanged(); + } + + @Override + @Deprecated + public String getPublicIpHostname(String publicIpId) { + synchronized (mutex) { + return publicIpIdToHostname.get(publicIpId); + } + } + + @Override + @Deprecated + public boolean forgetPublicIpHostname(String publicIpId) { + log.debug("forgetting public IP "+publicIpId+" association"); + boolean result; + synchronized (mutex) { + result = (publicIpIdToHostname.remove(publicIpId) != null); + } + onChanged(); + return result; + } + + @Override + @Deprecated + public int acquirePublicPort(String publicIpId, Location l, int privatePort) { + int publicPort; + synchronized (mutex) { + PortMapping old = getPortMappingWithPrivateSide(l, privatePort); + // only works for 1 public IP ID per location (which is the norm) + if (old!=null && old.publicIpId.equals(publicIpId)) { + log.debug("request to acquire public port at "+publicIpId+" for "+l+":"+privatePort+", reusing old assignment "+old); + return old.getPublicPort(); + } + + publicPort = acquirePublicPort(publicIpId); + log.debug("request to acquire public port at "+publicIpId+" for "+l+":"+privatePort+", allocating "+publicPort); + associateImpl(publicIpId, publicPort, l, privatePort); + } + onChanged(); + return publicPort; + } + + @Override + @Deprecated + public void associate(String publicIpId, int publicPort, Location l, int privatePort) { + synchronized (mutex) { + associateImpl(publicIpId, publicPort, l, privatePort); + } + onChanged(); + } + + protected void associateImpl(String publicIpId, int publicPort, Location l, int privatePort) { + synchronized (mutex) { + PortMapping mapping = new PortMapping(publicIpId, publicPort, l, privatePort); + PortMapping oldMapping = getPortMappingWithPublicSide(publicIpId, publicPort); + log.debug("associating public port "+publicPort+" on "+publicIpId+" with private port "+privatePort+" at "+l+" ("+mapping+")" + +(oldMapping == null ? "" : " (overwriting "+oldMapping+" )")); + mappings.put(makeKey(publicIpId, publicPort), mapping); + } + } + + /////////////////////////////////////////////////////////////////////////////////// + // Internal only; make protected when deprecated interface method removed + /////////////////////////////////////////////////////////////////////////////////// + + @Override + public HostAndPort getPublicHostAndPort(PortMapping m) { + if (m.publicEndpoint == null) { + String hostname = getPublicIpHostname(m.publicIpId); + if (hostname==null) + throw new IllegalStateException("No public hostname associated with "+m.publicIpId+" (mapping "+m+")"); + return HostAndPort.fromParts(hostname, m.publicPort); + } else { + return m.publicEndpoint; + } + } + + @Override + public PortMapping getPortMappingWithPublicSide(String publicIpId, int publicPort) { + synchronized (mutex) { + return mappings.get(makeKey(publicIpId, publicPort)); + } + } + + @Override + public Collection<PortMapping> getPortMappingWithPublicIpId(String publicIpId) { + List<PortMapping> result = new ArrayList<PortMapping>(); + synchronized (mutex) { + for (PortMapping m: mappings.values()) + if (publicIpId.equals(m.publicIpId)) result.add(m); + } + return result; + } + + /** returns the subset of port mappings associated with a given location */ + @Override + public Collection<PortMapping> getLocationPublicIpIds(Location l) { + List<PortMapping> result = new ArrayList<PortMapping>(); + synchronized (mutex) { + for (PortMapping m: mappings.values()) + if (l.equals(m.getTarget())) result.add(m); + } + return result; + } + + @Override + public PortMapping getPortMappingWithPrivateSide(Location l, int privatePort) { + synchronized (mutex) { + for (PortMapping m: mappings.values()) + if (l.equals(m.getTarget()) && privatePort==m.privatePort) return m; + } + return null; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerLocationResolver.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerLocationResolver.java b/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerLocationResolver.java new file mode 100644 index 0000000..dcb9048 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerLocationResolver.java @@ -0,0 +1,90 @@ +/* + * 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 org.apache.brooklyn.location.access; + +import java.util.Map; + +import org.apache.brooklyn.location.Location; +import org.apache.brooklyn.location.LocationRegistry; +import org.apache.brooklyn.location.LocationSpec; +import org.apache.brooklyn.location.basic.LocationConfigUtils; +import org.apache.brooklyn.location.basic.LocationInternal; +import org.apache.brooklyn.location.basic.LocationPredicates; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.brooklyn.location.basic.AbstractLocationResolver; +import brooklyn.util.config.ConfigBag; + +import com.google.common.base.Optional; +import com.google.common.base.Predicates; +import com.google.common.collect.Iterables; + +public class PortForwardManagerLocationResolver extends AbstractLocationResolver { + + private static final Logger LOG = LoggerFactory.getLogger(PortForwardManagerLocationResolver.class); + + public static final String PREFIX = "portForwardManager"; + + @Override + public String getPrefix() { + return PREFIX; + } + + @Override + public Location newLocationFromString(Map locationFlags, String spec, LocationRegistry registry) { + ConfigBag config = extractConfig(locationFlags, spec, registry); + Map globalProperties = registry.getProperties(); + String namedLocation = (String) locationFlags.get(LocationInternal.NAMED_SPEC_NAME.getName()); + String scope = config.get(PortForwardManager.SCOPE); + + Optional<Location> result = Iterables.tryFind(managementContext.getLocationManager().getLocations(), + Predicates.and( + Predicates.instanceOf(PortForwardManager.class), + LocationPredicates.configEqualTo(PortForwardManager.SCOPE, scope))); + + if (result.isPresent()) { + return result.get(); + } else { + PortForwardManager loc = managementContext.getLocationManager().createLocation(LocationSpec.create(PortForwardManagerImpl.class) + .configure(config.getAllConfig()) + .configure(LocationConfigUtils.finalAndOriginalSpecs(spec, locationFlags, globalProperties, namedLocation))); + + if (LOG.isDebugEnabled()) LOG.debug("Created "+loc+" for scope "+scope); + return loc; + } + } + + @Override + protected Class<? extends Location> getLocationType() { + return PortForwardManager.class; + } + + @Override + protected SpecParser getSpecParser() { + return new AbstractLocationResolver.SpecParser(getPrefix()).setExampleUsage("\"portForwardManager\" or \"portForwardManager(scope=global)\""); + } + + @Override + protected ConfigBag extractConfig(Map<?,?> locationFlags, String spec, LocationRegistry registry) { + ConfigBag config = super.extractConfig(locationFlags, spec, registry); + config.putAsStringKeyIfAbsent("name", "localhost"); + return config; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/access/PortMapping.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/access/PortMapping.java b/core/src/main/java/org/apache/brooklyn/location/access/PortMapping.java new file mode 100644 index 0000000..086b67c --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/access/PortMapping.java @@ -0,0 +1,101 @@ +/* + * 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 org.apache.brooklyn.location.access; + +import static com.google.common.base.Preconditions.checkNotNull; + +import javax.annotation.Nullable; + +import org.apache.brooklyn.location.Location; + +import com.google.common.annotations.Beta; +import com.google.common.base.Objects; +import com.google.common.net.HostAndPort; + +public class PortMapping { + + final String publicIpId; + final HostAndPort publicEndpoint; + final int publicPort; + + final Location target; + final int privatePort; + // TODO CIDR's ? + + public PortMapping(String publicIpId, HostAndPort publicEndpoint, Location target, int privatePort) { + this.publicIpId = checkNotNull(publicIpId, "publicIpId"); + this.publicEndpoint = checkNotNull(publicEndpoint, "publicEndpoint"); + this.publicPort = publicEndpoint.getPort(); + this.target = target; + this.privatePort = privatePort; + } + + public PortMapping(String publicIpId, int publicPort, Location target, int privatePort) { + this.publicIpId = checkNotNull(publicIpId, "publicIpId"); + this.publicEndpoint = null; + this.publicPort = publicPort; + this.target = target; + this.privatePort = privatePort; + } + + // In a release after 0.7.0, this will no longer be @Nullable + @Beta + @Nullable + public HostAndPort getPublicEndpoint() { + return publicEndpoint; + } + + public int getPublicPort() { + return publicPort; + } + + public Location getTarget() { + return target; + } + + public int getPrivatePort() { + return privatePort; + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("publicIpId", publicIpId+":"+publicPort) + .add("publicEndpoint", (publicEndpoint == null ? publicPort : publicEndpoint)) + .add("targetLocation", target) + .add("targetPort", privatePort) + .toString(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof PortMapping)) return false; + PortMapping opm = (PortMapping)obj; + return Objects.equal(publicIpId, opm.publicIpId) && + Objects.equal(publicPort, opm.publicPort) && + Objects.equal(target, opm.target) && + Objects.equal(privatePort, opm.privatePort); + } + + @Override + public int hashCode() { + return Objects.hashCode(publicIpId, publicPort, target, privatePort); + } + +} \ No newline at end of file
