All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.aerospike.client.cluster.NodeValidator Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2012-2023 Aerospike, Inc.
 *
 * Portions may be licensed to Aerospike, Inc. under one or more contributor
 * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
 *
 * 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 com.aerospike.client.cluster;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import com.aerospike.client.AerospikeException;
import com.aerospike.client.Host;
import com.aerospike.client.Info;
import com.aerospike.client.Log;
import com.aerospike.client.admin.AdminCommand;
import com.aerospike.client.admin.AdminCommand.LoginCommand;
import com.aerospike.client.util.Util;

public final class NodeValidator {
	Node fallback;
	String name;
	List aliases;
	Host primaryHost;
	InetSocketAddress primaryAddress;
	Connection primaryConn;
	byte[] sessionToken;
	long sessionExpiration;
	int features;

	/**
	 * Return first valid node referenced by seed host aliases. In most cases, aliases
	 * reference a single node.  If round robin DNS configuration is used, the seed host
	 * may have several addresses that reference different nodes in the cluster.
	 */
	public Node seedNode(Cluster cluster, Host host, Peers peers) throws Throwable {
		name = null;
		aliases = null;
		primaryHost = null;
		primaryAddress = null;
		primaryConn = null;
		sessionToken = null;
		sessionExpiration = 0;
		features = 0;

		InetAddress[] addresses = getAddresses(host);
		Throwable exception = null;

		for (InetAddress address : addresses) {
			try {
				validateAddress(cluster, address, host.tlsName, host.port, true);

				// Only add address alias when not set by load balancer detection logic.
				if (this.aliases == null) {
					setAliases(address, host.tlsName, host.port);
				}

				Node node = new Node(cluster, this);

				if (validatePeers(peers, node)) {
					return node;
				}
			}
			catch (Throwable e) {
				// Log exception and continue to next alias.
				if (Log.debugEnabled()) {
					Log.debug("Address " + address + ' ' + host.port + " failed: " + Util.getErrorMessage(e));
				}

				if (exception == null) {
					exception = e;
				}
			}
		}

		// Fallback signifies node exists, but is suspect.
		// Return null so other seeds can be tried.
		if (fallback != null) {
			return null;
		}

		// Exception can't be null here because getAddresses() will throw exception
		// if aliases length is zero.
		throw exception;
	}

	private boolean validatePeers(Peers peers, Node node) {
		if (peers == null) {
			return true;
		}

		try {
			peers.refreshCount = 0;
			node.refreshPeers(peers);
		}
		catch (Throwable e) {
			node.close();
			throw e;
		}

		if (node.peersCount == 0) {
			// Node is suspect because multiple seeds are used and node does not have any peers.
			if (fallback == null) {
				fallback = node;
			}
			else {
				node.close();
			}
			return false;
		}

		// Node is valid. Drop fallback if it exists.
		if (fallback != null) {
			if (Log.infoEnabled()) {
				Log.info("Skip orphan node: " + fallback);
			}
			fallback.close();
			fallback = null;
		}
		return true;
	}

	/**
	 * Verify that a host alias references a valid node.
	 */
	public void validateNode(Cluster cluster, Host host) throws Throwable {
		InetAddress[] addresses = getAddresses(host);
		Throwable exception = null;

		for (InetAddress address : addresses) {
			try {
				validateAddress(cluster, address, host.tlsName, host.port, false);
				setAliases(address, host.tlsName, host.port);
				return;
			}
			catch (Throwable e) {
				// Log exception and continue to next alias.
				if (Log.debugEnabled()) {
					Log.debug("Address " + address + ' ' + host.port + " failed: " + Util.getErrorMessage(e));
				}

				if (exception == null) {
					exception = e;
				}
			}
		}
		// Exception can't be null here because getAddresses() will throw exception
		// if aliases length is zero.
		throw exception;
	}

	private static InetAddress[] getAddresses(Host host) {
		InetAddress[] addresses;

		try {
			addresses = InetAddress.getAllByName(host.name);
		}
		catch (UnknownHostException uhe) {
			throw new AerospikeException.Connection("Invalid host: " + host);
		}

		if (addresses.length == 0) {
			throw new AerospikeException.Connection("Failed to find addresses for " + host);
		}
		return addresses;
	}

	private void validateAddress(Cluster cluster, InetAddress address, String tlsName, int port, boolean detectLoadBalancer)
		throws Exception {

		InetSocketAddress socketAddress = new InetSocketAddress(address, port);
		Connection conn = (cluster.tlsPolicy != null) ?
			new Connection(cluster.tlsPolicy, tlsName, socketAddress, cluster.connectTimeout) :
			new Connection(socketAddress, cluster.connectTimeout);

		try {
			if (cluster.authEnabled) {
				// Login
				LoginCommand admin = new LoginCommand(cluster, conn);
				sessionToken = admin.sessionToken;
				sessionExpiration = admin.sessionExpiration;

				if (cluster.tlsPolicy != null && cluster.tlsPolicy.forLoginOnly) {
					// Switch to using non-TLS socket.
					SwitchClear sc = new SwitchClear(cluster, conn, sessionToken);
					conn.close();
					address = sc.clearAddress;
					socketAddress = sc.clearSocketAddress;
					conn = sc.clearConn;

					// Disable load balancer detection since non-TLS address has already
					// been retrieved via service info command.
					detectLoadBalancer = false;
				}
			}

			List commands = new ArrayList(5);
			commands.add("node");
			commands.add("partition-generation");
			commands.add("features");

			boolean hasClusterName = cluster.clusterName != null && cluster.clusterName.length() > 0;

			if (hasClusterName) {
				commands.add("cluster-name");
			}

			String addressCommand = null;

			if (detectLoadBalancer) {
				if (address.isLoopbackAddress()) {
					// Disable load balancer detection for localhost.
					detectLoadBalancer = false;
				}
				else {
					// Seed may be load balancer with changing address. Determine real address.
					addressCommand = (cluster.tlsPolicy != null)?
						cluster.useServicesAlternate ? "service-tls-alt" : "service-tls-std" :
						cluster.useServicesAlternate ? "service-clear-alt" : "service-clear-std";

					commands.add(addressCommand);
				}
			}

			// Issue commands.
			HashMap map = Info.request(conn, commands);

			// Node returned results.
			this.primaryHost = new Host(address.getHostAddress(), tlsName, port);
			this.primaryAddress = socketAddress;
			this.primaryConn = conn;

			validateNode(map);
			validatePartitionGeneration(map);
			setFeatures(map);

			if (hasClusterName) {
				validateClusterName(cluster, map);
			}

			if (addressCommand != null) {
				setAddress(cluster, map, addressCommand, tlsName);
			}
		}
		catch (Throwable e) {
			conn.close();
			throw e;
		}
	}

	private void validateNode(HashMap map) {
		this.name = map.get("node");

		if (this.name == null) {
			throw new AerospikeException.InvalidNode("Node name is null");
		}
	}

	private void validatePartitionGeneration(HashMap map) {
		String genString = map.get("partition-generation");
		int gen;

		try {
			gen = Integer.parseInt(genString);
		}
		catch (Throwable e) {
			throw new AerospikeException.InvalidNode("Node " + this.name + ' ' + this.primaryHost + " returned invalid partition-generation: " + genString);
		}

		if (gen == -1) {
			throw new AerospikeException.InvalidNode("Node " + this.name + ' ' + this.primaryHost + " is not yet fully initialized");
		}
	}

	private void setFeatures(HashMap map) {
		try {
			String featuresString = map.get("features");
			int begin = 0;
			int end = 0;

			while (end < featuresString.length()) {
				end = featuresString.indexOf(';', begin);

				if (end < 0) {
					end = featuresString.length();
				}

				int len = end - begin;

				if (featuresString.regionMatches(begin, "pscans", 0, len)) {
					this.features |= Node.HAS_PARTITION_SCAN;
				}
				else if (featuresString.regionMatches(begin, "query-show", 0, len)) {
					this.features |= Node.HAS_QUERY_SHOW;
				}
				else if (featuresString.regionMatches(begin, "batch-any", 0, len)) {
					this.features |= Node.HAS_BATCH_ANY;
				}
				else if (featuresString.regionMatches(begin, "pquery", 0, len)) {
					this.features |= Node.HAS_PARTITION_QUERY;
				}
				begin = end + 1;
			}
		}
		catch (Throwable e) {
			// Unexpected exception. Use defaults.
		}

		// This client requires partition scan support. Partition scans were first
		// supported in server version 4.9. Do not allow any server node into the
		// cluster that is running server version < 4.9.
		if ((this.features & Node.HAS_PARTITION_SCAN) == 0) {
			throw new AerospikeException("Node " + this.name + ' ' + this.primaryHost +
					" version < 4.9. This client requires server version >= 4.9");
		}
	}

	private void validateClusterName(Cluster cluster, HashMap map) {
		String id = map.get("cluster-name");

		if (id == null || ! cluster.clusterName.equals(id)) {
			throw new AerospikeException.InvalidNode("Node " + this.name + ' ' + this.primaryHost + ' ' +
					" expected cluster name '" + cluster.clusterName + "' received '" + id + "'");
		}
	}

	private void setAddress(Cluster cluster, HashMap map, String addressCommand, String tlsName) {
		String result = map.get(addressCommand);

		if (result == null || result.length() == 0) {
			// Server does not support service level call (service-clear-std, ...).
			// Load balancer detection is not possible.
			return;
		}

		List hosts = Host.parseServiceHosts(result);
		Host h;

		// Search real hosts for seed.
		for (Host host : hosts) {
			h = host;

			if (cluster.ipMap != null) {
				String alt = cluster.ipMap.get(h.name);

				if (alt != null) {
					h = new Host(alt, h.port);
				}
			}

			if (h.equals(this.primaryHost)) {
				// Found seed which is not a load balancer.
				return;
			}
		}

		// Seed not found, so seed is probably a load balancer.
		// Find first valid real host.
		for (Host host : hosts) {
			try {
				h = host;

				if (cluster.ipMap != null) {
					String alt = cluster.ipMap.get(h.name);

					if (alt != null) {
						h = new Host(alt, h.port);
					}
				}

				InetAddress[] addresses = InetAddress.getAllByName(h.name);

				for (InetAddress address : addresses) {
					try {
						InetSocketAddress socketAddress = new InetSocketAddress(address, h.port);
						Connection conn = (cluster.tlsPolicy != null) ?
							new Connection(cluster.tlsPolicy, tlsName, socketAddress, cluster.connectTimeout) :
							new Connection(socketAddress, cluster.connectTimeout);

						try {
							if (this.sessionToken != null) {
								if (! AdminCommand.authenticate(cluster,  conn, this.sessionToken)) {
									throw new AerospikeException("Authentication failed");
								}
							}

							// Authenticated connection.  Set real host.
							setAliases(address, tlsName, h.port);
							this.primaryHost = new Host(address.getHostAddress(), tlsName, h.port);
							this.primaryAddress = socketAddress;
							this.primaryConn.close();
							this.primaryConn = conn;
							return;
						}
						catch (Throwable e) {
							conn.close();
						}
					}
					catch (Throwable e) {
						// Try next address.
					}
				}
			}
			catch (Throwable e) {
				// Try next host.
			}
		}

		// Failed to find a valid address. IP Address is probably internal on the cloud
		// because the server access-address is not configured.  Log warning and continue
		// with original seed.
		if (Log.infoEnabled()) {
			Log.info("Invalid address " + result + ". access-address is probably not configured on server.");
		}
	}

	private void setAliases(InetAddress address, String tlsName, int port) {
		// Add capacity for current address plus IPV6 address and hostname.
		this.aliases = new ArrayList(3);
		this.aliases.add(new Host(address.getHostAddress(), tlsName, port));
	}

	private static final class SwitchClear {
		private InetAddress clearAddress;
		private InetSocketAddress clearSocketAddress;
		private Connection clearConn;

		// Switch from TLS connection to non-TLS connection.
		private SwitchClear(Cluster cluster, Connection conn, byte[] sessionToken) throws Exception {
			// Obtain non-TLS addresses.
			String command = cluster.useServicesAlternate ? "service-clear-alt" : "service-clear-std";
			String result = Info.request(conn, command);
			List hosts = Host.parseServiceHosts(result);
			Host clearHost;

			// Find first valid non-TLS host.
			for (Host host : hosts) {
				try {
					clearHost = host;

					if (cluster.ipMap != null) {
						String alternativeHost = cluster.ipMap.get(clearHost.name);

						if (alternativeHost != null) {
							clearHost = new Host(alternativeHost, clearHost.port);
						}
					}

					InetAddress[] addresses = InetAddress.getAllByName(clearHost.name);

					for (InetAddress ia : addresses) {
						try {
							clearAddress = ia;
							clearSocketAddress = new InetSocketAddress(clearAddress, clearHost.port);
							clearConn = new Connection(clearSocketAddress, cluster.connectTimeout);

							try {
								if (sessionToken != null) {
									if (! AdminCommand.authenticate(cluster, clearConn, sessionToken)) {
										throw new AerospikeException("Authentication failed");
									}
								}
								return;  // Authenticated clear connection.
							}
							catch (Throwable e) {
								clearConn.close();
							}
						}
						catch (Throwable e) {
							// Try next address.
						}
					}
				}
				catch (Throwable e) {
					// Try next host.
				}
			}
			throw new AerospikeException("Invalid non-TLS address: " + result);
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy