/*
* Copyright 2010 Proofpoint, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.airlift.node;
import com.google.common.collect.ImmutableList;
import com.google.common.net.InetAddresses;
import com.google.inject.Singleton;
import io.airlift.node.NodeConfig.AddressSource;
import org.weakref.jmx.Managed;
import javax.inject.Inject;
import java.io.UncheckedIOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import static com.google.common.base.MoreObjects.toStringHelper;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;
@Singleton
public class NodeInfo
{
private final String environment;
private final String pool;
private final String nodeId;
private final String location;
private final String binarySpec;
private final String configSpec;
private final String instanceId = UUID.randomUUID().toString();
private final String internalAddress;
private final String externalAddress;
private final InetAddress bindIp;
private final long startTime = System.currentTimeMillis();
public NodeInfo(String environment)
{
this(new NodeConfig().setEnvironment(environment));
}
@Inject
public NodeInfo(NodeConfig config)
{
this(config.getEnvironment(),
config.getPool(),
config.getNodeId(),
config.getNodeInternalAddress(),
config.getNodeBindIp(),
config.getNodeExternalAddress(),
config.getLocation(),
config.getBinarySpec(),
config.getConfigSpec(),
config.getInternalAddressSource()
);
}
public NodeInfo(String environment,
String pool,
String nodeId,
String internalAddress,
InetAddress bindIp,
String externalAddress,
String location,
String binarySpec,
String configSpec,
AddressSource internalAddressSource)
{
requireNonNull(environment, "environment is null");
requireNonNull(pool, "pool is null");
requireNonNull(internalAddressSource, "internalAddressSource is null");
checkArgument(environment.matches(NodeConfig.ENV_REGEXP), "environment '%s' is invalid", environment);
checkArgument(pool.matches(NodeConfig.POOL_REGEXP), "pool '%s' is invalid", pool);
this.environment = environment;
this.pool = pool;
if (nodeId != null) {
checkArgument(nodeId.matches(NodeConfig.ID_REGEXP), "nodeId '%s' is invalid", nodeId);
this.nodeId = nodeId;
}
else {
this.nodeId = UUID.randomUUID().toString();
}
if (location != null) {
this.location = location;
}
else {
this.location = "/" + this.nodeId;
}
this.binarySpec = binarySpec;
this.configSpec = configSpec;
if (internalAddress != null) {
this.internalAddress = internalAddress;
}
else {
this.internalAddress = findInternalAddress(internalAddressSource);
}
if (bindIp != null) {
this.bindIp = bindIp;
}
else {
this.bindIp = InetAddresses.fromInteger(0);
}
if (externalAddress != null) {
this.externalAddress = externalAddress;
}
else {
this.externalAddress = this.internalAddress;
}
}
/**
* The environment in which this server is running.
*/
@Managed
public String getEnvironment()
{
return environment;
}
/**
* The pool of which this server is a member.
*/
@Managed
public String getPool()
{
return pool;
}
/**
* The unique id of the deployment slot in which this binary is running. This id should
* represents the physical deployment location and should not change.
*/
@Managed
public String getNodeId()
{
return nodeId;
}
/**
* Location of this JavaVM.
*/
@Managed
public String getLocation()
{
return location;
}
/**
* Binary this JavaVM is running.
*/
@Managed
public String getBinarySpec()
{
return binarySpec;
}
/**
* Configuration this JavaVM is running.
*/
@Managed
public String getConfigSpec()
{
return configSpec;
}
/**
* The unique id of this JavaVM instance. This id will change every time the vm is restarted.
*/
@Managed
public String getInstanceId()
{
return instanceId;
}
/**
* The internal network address the server should use when announcing its location to other machines.
* This address should available to all machines within the environment, but may not be globally routable.
* If this is not set, the following algorithm is used to choose the public address:
*
* <ol>
* <li>InetAddress.getLocalHost() if good IPv4</li>
* <li>First good IPv4 address of an up network interface</li>
* <li>First good IPv6 address of an up network interface</li>
* <li>InetAddress.getLocalHost()</li>
* </ol>
* An address is considered good if it is not a loopback address, a multicast address, or an any-local-address address.
*/
@Managed
public String getInternalAddress()
{
return internalAddress;
}
/**
* The address to use when contacting this server from an external network. If possible, ip address should be globally
* routable. The address is returned as a string because the name may not be resolvable from the local machine.
* <p>
* If this is not set, the internal address is used.
*/
@Managed
public String getExternalAddress()
{
return externalAddress;
}
/**
* The IP address the server should use when binding a server socket.
*
* If this is not set, this will be the IPv4 any local address (e.g., 0.0.0.0).
*/
@Managed
public InetAddress getBindIp()
{
return bindIp;
}
/**
* The time this server was started.
*/
@Managed
public long getStartTime()
{
return startTime;
}
@Override
public String toString()
{
return toStringHelper(this)
.add("nodeId", nodeId)
.add("instanceId", instanceId)
.add("internalAddress", internalAddress)
.add("externalAddress", externalAddress)
.add("bindIp", bindIp)
.add("startTime", startTime)
.toString();
}
private static String findInternalAddress(AddressSource addressSource)
{
switch (addressSource) {
case IP:
return InetAddresses.toAddrString(findInternalIp());
case HOSTNAME:
return getLocalHost().getHostName();
case FQDN:
return getLocalHost().getCanonicalHostName();
default:
throw new IllegalArgumentException();
}
}
private static InetAddress findInternalIp()
{
// Check if local host address is a good v4 address
InetAddress localAddress = null;
try {
localAddress = InetAddress.getLocalHost();
if (isV4Address(localAddress) && getGoodAddresses().contains(localAddress)) {
return localAddress;
}
}
catch (UnknownHostException ignored) {
}
if (localAddress == null) {
try {
localAddress = InetAddress.getByAddress(new byte[]{127, 0, 0, 1});
}
catch (UnknownHostException e) {
throw new AssertionError("Could not get local ip address");
}
}
// check all up network interfaces for a good v4 address
for (InetAddress address : getGoodAddresses()) {
if (isV4Address(address)) {
return address;
}
}
// check all up network interfaces for a good v6 address
for (InetAddress address : getGoodAddresses()) {
if (isV6Address(address)) {
return address;
}
}
// just return the local host address
// it is most likely that this is a disconnected developer machine
return localAddress;
}
private static List<InetAddress> getGoodAddresses()
{
ImmutableList.Builder<InetAddress> list = ImmutableList.builder();
for (NetworkInterface networkInterface : getGoodNetworkInterfaces()) {
for (InetAddress address : Collections.list(networkInterface.getInetAddresses())) {
if (isGoodAddress(address)) {
list.add(address);
}
}
}
return list.build();
}
private static List<NetworkInterface> getGoodNetworkInterfaces()
{
ImmutableList.Builder<NetworkInterface> builder = ImmutableList.builder();
try {
for (NetworkInterface networkInterface : Collections.list(NetworkInterface.getNetworkInterfaces())) {
try {
if (!networkInterface.isLoopback() && networkInterface.isUp()) {
builder.add(networkInterface);
}
}
catch (Exception ignored) {
}
}
}
catch (SocketException ignored) {
}
return builder.build();
}
private static boolean isV4Address(InetAddress address)
{
return address instanceof Inet4Address;
}
private static boolean isV6Address(InetAddress address)
{
return address instanceof Inet6Address;
}
private static boolean isGoodAddress(InetAddress address)
{
return !address.isAnyLocalAddress() &&
!address.isLinkLocalAddress() &&
!address.isLoopbackAddress() &&
!address.isMulticastAddress();
}
private static InetAddress getLocalHost()
{
try {
return InetAddress.getLocalHost();
}
catch (UnknownHostException e) {
throw new UncheckedIOException(e);
}
}
}