/*
* 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.ignite.spi.discovery.tcp.ipfinder.vm;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import org.apache.ignite.IgniteException;
import org.apache.ignite.IgniteLogger;
import org.apache.ignite.IgniteSystemProperties;
import org.apache.ignite.internal.util.tostring.GridToStringInclude;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.resources.LoggerResource;
import org.apache.ignite.spi.IgniteSpiConfiguration;
import org.apache.ignite.spi.IgniteSpiException;
import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinderAdapter;
import static org.apache.ignite.IgniteSystemProperties.IGNITE_TCP_DISCOVERY_ADDRESSES;
/**
* IP Finder which works only with pre-configured list of IP addresses specified
* via {@link #setAddresses(Collection)} method. By default, this IP finder is
* not {@code shared}, which means that all grid nodes have to be configured with the
* same list of IP addresses when this IP finder is used.
* <h1 class="header">Configuration</h1>
* <h2 class="header">Mandatory</h2>
* There are no mandatory configuration parameters.
* <h2 class="header">Optional</h2>
* <ul>
* <li>Addresses for initialization (see {@link #setAddresses(Collection)})</li>
* <li>Shared flag (see {@link #setShared(boolean)})</li>
* </ul>
*/
public class TcpDiscoveryVmIpFinder extends TcpDiscoveryIpFinderAdapter {
/** Grid logger. */
@LoggerResource
private IgniteLogger log;
/** Addresses. */
@GridToStringInclude
private Collection<InetSocketAddress> addrs;
/**
* Initialize from system property.
*/
{
String ips = IgniteSystemProperties.getString(IGNITE_TCP_DISCOVERY_ADDRESSES);
if (!F.isEmpty(ips)) {
Collection<InetSocketAddress> addrsList = new LinkedHashSet<>();
for (String s : ips.split(",")) {
if (!F.isEmpty(s)) {
s = s.trim();
if (!F.isEmpty(s)) {
try {
addrsList.addAll(address(s));
}
catch (IgniteSpiException e) {
throw new IgniteException(e);
}
}
}
}
addrs = addrsList;
}
else
addrs = new LinkedHashSet<>();
}
/**
* Constructs new IP finder.
*/
public TcpDiscoveryVmIpFinder() {
// No-op.
}
/**
* Constructs new IP finder.
*
* @param shared {@code true} if IP finder is shared.
* @see #setShared(boolean)
*/
public TcpDiscoveryVmIpFinder(boolean shared) {
setShared(shared);
}
/**
* Parses provided values and initializes the internal collection of addresses.
* <p>
* Addresses may be represented as follows:
* <ul>
* <li>IP address (e.g. 127.0.0.1, 9.9.9.9, etc);</li>
* <li>IP address and port (e.g. 127.0.0.1:47500, 9.9.9.9:47501, etc);</li>
* <li>IP address and port range (e.g. 127.0.0.1:47500..47510, 9.9.9.9:47501..47504, etc);</li>
* <li>Hostname (e.g. host1.com, host2, etc);</li>
* <li>Hostname and port (e.g. host1.com:47500, host2:47502, etc).</li>
* <li>Hostname and port range (e.g. host1.com:47500..47510, host2:47502..47508, etc).</li>
* </ul>
* <p>
* If port is 0 or not provided then default port will be used (depends on
* discovery SPI configuration).
* <p>
* If port range is provided (e.g. host:port1..port2) the following should be considered:
* <ul>
* <li>{@code port1 < port2} should be {@code true};</li>
* <li>Both {@code port1} and {@code port2} should be greater than {@code 0}.</li>
* </ul>
*
* @param addrs Known nodes addresses.
* @throws IgniteSpiException If any error occurs.
* @return {@code this} for chaining.
*/
@IgniteSpiConfiguration(optional = true)
public synchronized TcpDiscoveryVmIpFinder setAddresses(Collection<String> addrs) throws IgniteSpiException {
if (F.isEmpty(addrs))
return this;
Collection<InetSocketAddress> newAddrs = new LinkedHashSet<>();
for (String ipStr : addrs)
newAddrs.addAll(address(ipStr));
this.addrs = newAddrs;
return this;
}
/**
* Creates address from string.
*
* @param ipStr Address string.
* @return Socket addresses (may contain 1 or more addresses if provided string
* includes port range).
* @throws IgniteSpiException If failed.
*/
private static Collection<InetSocketAddress> address(String ipStr) throws IgniteSpiException {
ipStr = ipStr.trim();
String errMsg = "Failed to parse provided address: " + ipStr;
int colonCnt = ipStr.length() - ipStr.replace(":", "").length();
if (colonCnt > 1) {
// IPv6 address (literal IPv6 addresses are enclosed in square brackets, for example
// https://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443).
if (ipStr.startsWith("[")) {
ipStr = ipStr.substring(1);
if (ipStr.contains("]:"))
return addresses(ipStr, "\\]\\:", errMsg);
else if (ipStr.endsWith("]"))
ipStr = ipStr.substring(0, ipStr.length() - 1);
else
throw new IgniteSpiException(errMsg);
}
}
else {
// IPv4 address.
if (ipStr.endsWith(":"))
ipStr = ipStr.substring(0, ipStr.length() - 1);
else if (ipStr.indexOf(':') >= 0)
return addresses(ipStr, "\\:", errMsg);
}
// Provided address does not contain port (will use default one).
return Collections.singleton(new InetSocketAddress(ipStr, 0));
}
/**
* Creates address from string with port information.
*
* @param ipStr Address string
* @param regexDelim Port regex delimiter.
* @param errMsg Error message.
* @return Socket addresses (may contain 1 or more addresses if provided string
* includes port range).
* @throws IgniteSpiException If failed.
*/
private static Collection<InetSocketAddress> addresses(String ipStr, String regexDelim, String errMsg)
throws IgniteSpiException {
String[] tokens = ipStr.split(regexDelim);
if (tokens.length == 2) {
String addrStr = tokens[0];
String portStr = tokens[1];
if (portStr.contains("..")) {
try {
int port1 = Integer.parseInt(portStr.substring(0, portStr.indexOf("..")));
int port2 = Integer.parseInt(portStr.substring(portStr.indexOf("..") + 2, portStr.length()));
if (port2 < port1 || port1 == port2 || port1 <= 0 || port2 <= 0)
throw new IgniteSpiException(errMsg);
Collection<InetSocketAddress> res = new ArrayList<>(port2 - port1);
// Upper bound included.
for (int i = port1; i <= port2; i++)
res.add(new InetSocketAddress(addrStr, i));
return res;
}
catch (IllegalArgumentException e) {
throw new IgniteSpiException(errMsg, e);
}
}
else {
try {
int port = Integer.parseInt(portStr);
return Collections.singleton(new InetSocketAddress(addrStr, port));
}
catch (IllegalArgumentException e) {
throw new IgniteSpiException(errMsg, e);
}
}
}
else
throw new IgniteSpiException(errMsg);
}
/** {@inheritDoc} */
@Override public synchronized Collection<InetSocketAddress> getRegisteredAddresses() {
return Collections.unmodifiableCollection(addrs);
}
/** {@inheritDoc} */
@Override public synchronized void registerAddresses(Collection<InetSocketAddress> addrs) {
assert !F.isEmpty(addrs);
this.addrs = new LinkedHashSet<>(this.addrs);
this.addrs.addAll(addrs);
}
/** {@inheritDoc} */
@Override public synchronized void unregisterAddresses(Collection<InetSocketAddress> addrs) {
assert !F.isEmpty(addrs);
this.addrs = new LinkedHashSet<>(this.addrs);
this.addrs.removeAll(addrs);
}
/** {@inheritDoc} */
@Override public TcpDiscoveryVmIpFinder setShared(boolean shared) {
super.setShared(shared);
return this;
}
/** {@inheritDoc} */
@Override public String toString() {
return S.toString(TcpDiscoveryVmIpFinder.class, this, "super", super.toString());
}
}