/*
* Licensed to Crate under one or more contributor license agreements.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership. Crate 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.
*
* However, if you have executed another commercial license agreement
* with Crate these terms will supersede the license and you may use the
* software solely pursuant to the terms of the relevant commercial
* agreement.
*/
package io.crate.discovery;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.Singleton;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.discovery.zen.ping.unicast.UnicastHostsProvider;
import org.elasticsearch.transport.TransportService;
import org.xbill.DNS.*;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Singleton
public class SrvUnicastHostsProvider extends AbstractComponent implements UnicastHostsProvider {
public static final Setting<String> DISCOVERY_SRV_QUERY = Setting.simpleString(
"discovery.srv.query", Setting.Property.NodeScope);
public static final Setting<String> DISCOVERY_SRV_RESOLVER = Setting.simpleString(
"discovery.srv.resolver", Setting.Property.NodeScope);
private final TransportService transportService;
private final Version version;
private final String query;
protected final Resolver resolver;
@Inject
public SrvUnicastHostsProvider(Settings settings, TransportService transportService) {
super(settings);
this.transportService = transportService;
this.version = Version.CURRENT;
this.query = DISCOVERY_SRV_QUERY.get(settings);
this.resolver = resolver(settings);
}
@Nullable
private Resolver resolver(Settings settings) {
String hostname = null;
int port = -1;
String resolverAddress = DISCOVERY_SRV_RESOLVER.get(settings);
if (!Strings.isNullOrEmpty(resolverAddress)) {
String[] parts = resolverAddress.split(":");
if (parts.length > 0) {
hostname = parts[0];
if (parts.length > 1) {
try {
port = Integer.parseInt(parts[1]);
} catch (Exception e) {
logger.warn("Resolver port '{}' is not an integer. Using default port 53", parts[1]);
}
}
}
}
try {
Resolver resolver = new SimpleResolver(hostname);
if (port > 0) {
resolver.setPort(port);
}
return resolver;
} catch (UnknownHostException e) {
logger.warn("Could not create resolver. Using default resolver.", e);
}
return null;
}
@Override
public List<DiscoveryNode> buildDynamicNodes() {
if (query == null) {
logger.error("DNS query must not be null. Please set '{}'", DISCOVERY_SRV_QUERY);
return Collections.emptyList();
}
try {
Record[] records = lookupRecords();
logger.trace("Building dynamic unicast discovery nodes...");
if (records == null || records.length == 0) {
logger.debug("No nodes found");
} else {
List<DiscoveryNode> discoNodes = parseRecords(records);
logger.info("Using dynamic discovery nodes {}", discoNodes);
return discoNodes;
}
} catch (TextParseException e) {
logger.error("Unable to parse DNS query '{}'", query);
logger.error("DNS lookup exception:", e);
}
return Collections.emptyList();
}
protected Record[] lookupRecords() throws TextParseException {
Lookup lookup = new Lookup(query, Type.SRV);
if (this.resolver != null) {
lookup.setResolver(this.resolver);
}
return lookup.run();
}
protected List<DiscoveryNode> parseRecords(Record[] records) {
List<DiscoveryNode> discoNodes = new ArrayList<>(records.length);
for (Record record : records) {
SRVRecord srv = (SRVRecord) record;
String hostname = srv.getTarget().toString().replaceFirst("\\.$", "");
int port = srv.getPort();
String address = hostname + ":" + port;
try {
TransportAddress[] addresses = transportService.addressesFromString(address, 1);
for (TransportAddress transportAddress : addresses) {
logger.trace("adding {}, transport_address {}", address, transportAddress);
discoNodes.add(new DiscoveryNode(
"#srv-" + address, transportAddress, version.minimumCompatibilityVersion()));
}
} catch (Exception e) {
logger.warn("failed to add {}, address {}", e, address);
}
}
return discoNodes;
}
}