/* * 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.nifi.remote.client; import java.io.IOException; import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import javax.net.ssl.SSLContext; import org.apache.nifi.events.EventReporter; import org.apache.nifi.remote.TransferDirection; import org.apache.nifi.remote.protocol.http.HttpProxy; import org.apache.nifi.remote.util.SiteToSiteRestApiClient; import org.apache.nifi.web.api.dto.ControllerDTO; import org.apache.nifi.web.api.dto.PortDTO; public class SiteInfoProvider { private static final long REMOTE_REFRESH_MILLIS = TimeUnit.MILLISECONDS.convert(10, TimeUnit.MINUTES); private final ReadWriteLock listeningPortRWLock = new ReentrantReadWriteLock(); private final Lock remoteInfoReadLock = listeningPortRWLock.readLock(); private final Lock remoteInfoWriteLock = listeningPortRWLock.writeLock(); private Integer siteToSitePort; private Integer siteToSiteHttpPort; private Boolean siteToSiteSecure; private long remoteRefreshTime; private HttpProxy proxy; private InetAddress localAddress; private final Map<String, String> inputPortMap = new HashMap<>(); // map input port name to identifier private final Map<String, String> outputPortMap = new HashMap<>(); // map output port name to identifier private Set<String> clusterUrls; private URI activeClusterUrl; private SSLContext sslContext; private int connectTimeoutMillis; private int readTimeoutMillis; private ControllerDTO refreshRemoteInfo() throws IOException { final ControllerDTO controller; final URI connectedClusterUrl; try (final SiteToSiteRestApiClient apiClient = createSiteToSiteRestApiClient(sslContext, proxy)) { controller = apiClient.getController(clusterUrls); try { connectedClusterUrl = new URI(apiClient.getBaseUrl()); } catch (URISyntaxException e) { // This should not happen since apiClient has successfully communicated with this URL. throw new RuntimeException("Failed to parse connected cluster URL due to " + e); } } remoteInfoWriteLock.lock(); try { this.siteToSitePort = controller.getRemoteSiteListeningPort(); this.siteToSiteHttpPort = controller.getRemoteSiteHttpListeningPort(); this.siteToSiteSecure = controller.isSiteToSiteSecure(); this.activeClusterUrl = connectedClusterUrl; inputPortMap.clear(); for (final PortDTO inputPort : controller.getInputPorts()) { inputPortMap.put(inputPort.getName(), inputPort.getId()); } outputPortMap.clear(); for (final PortDTO outputPort : controller.getOutputPorts()) { outputPortMap.put(outputPort.getName(), outputPort.getId()); } this.remoteRefreshTime = System.currentTimeMillis(); } finally { remoteInfoWriteLock.unlock(); } return controller; } protected SiteToSiteRestApiClient createSiteToSiteRestApiClient(final SSLContext sslContext, final HttpProxy proxy) { final SiteToSiteRestApiClient apiClient = new SiteToSiteRestApiClient(sslContext, proxy, EventReporter.NO_OP); apiClient.setConnectTimeoutMillis(connectTimeoutMillis); apiClient.setReadTimeoutMillis(readTimeoutMillis); apiClient.setLocalAddress(localAddress); return apiClient; } public boolean isWebInterfaceSecure() { return clusterUrls.stream().anyMatch(url -> url.startsWith("https")); } /** * @return the port that the remote instance is listening on for * RAW Socket site-to-site communication, or <code>null</code> if the remote instance * is not configured to allow site-to-site communications. * * @throws IOException if unable to communicate with the remote instance */ public Integer getSiteToSitePort() throws IOException { Integer listeningPort; remoteInfoReadLock.lock(); try { listeningPort = this.siteToSitePort; if (listeningPort != null && this.remoteRefreshTime > System.currentTimeMillis() - REMOTE_REFRESH_MILLIS) { return listeningPort; } } finally { remoteInfoReadLock.unlock(); } final ControllerDTO controller = refreshRemoteInfo(); listeningPort = controller.getRemoteSiteListeningPort(); return listeningPort; } /** * @return the port that the remote instance is listening on for * HTTP(S) site-to-site communication, or <code>null</code> if the remote instance * is not configured to allow site-to-site communications. * * @throws IOException if unable to communicate with the remote instance */ public Integer getSiteToSiteHttpPort() throws IOException { Integer listeningHttpPort; remoteInfoReadLock.lock(); try { listeningHttpPort = this.siteToSiteHttpPort; if (listeningHttpPort != null && this.remoteRefreshTime > System.currentTimeMillis() - REMOTE_REFRESH_MILLIS) { return listeningHttpPort; } } finally { remoteInfoReadLock.unlock(); } final ControllerDTO controller = refreshRemoteInfo(); listeningHttpPort = controller.getRemoteSiteHttpListeningPort(); return listeningHttpPort; } /** * @return {@code true} if the remote instance is configured for secure * site-to-site communications, {@code false} otherwise * @throws IOException if unable to check if secure */ public boolean isSecure() throws IOException { remoteInfoReadLock.lock(); try { final Boolean secure = this.siteToSiteSecure; if (secure != null && this.remoteRefreshTime > System.currentTimeMillis() - REMOTE_REFRESH_MILLIS) { return secure; } } finally { remoteInfoReadLock.unlock(); } final ControllerDTO controller = refreshRemoteInfo(); final Boolean isSecure = controller.isSiteToSiteSecure(); if (isSecure == null) { throw new IOException("Remote NiFi instance " + clusterUrls + " is not currently configured to accept site-to-site connections"); } return isSecure; } public String getPortIdentifier(final String portName, final TransferDirection transferDirection) throws IOException { if (transferDirection == TransferDirection.RECEIVE) { return getOutputPortIdentifier(portName); } else { return getInputPortIdentifier(portName); } } public String getInputPortIdentifier(final String portName) throws IOException { return getPortIdentifier(portName, inputPortMap); } public String getOutputPortIdentifier(final String portName) throws IOException { return getPortIdentifier(portName, outputPortMap); } private String getPortIdentifier(final String portName, final Map<String, String> portMap) throws IOException { String identifier; remoteInfoReadLock.lock(); try { identifier = portMap.get(portName); } finally { remoteInfoReadLock.unlock(); } if (identifier != null) { return identifier; } refreshRemoteInfo(); remoteInfoReadLock.lock(); try { return portMap.get(portName); } finally { remoteInfoReadLock.unlock(); } } /** * Return an active cluster URL that is known to work. * If it is unknown yet or cache is expired, then remote info will be refreshed. * @return an active cluster URL */ public URI getActiveClusterUrl() throws IOException { URI resultClusterUrl; remoteInfoReadLock.lock(); try { resultClusterUrl = this.activeClusterUrl; if (resultClusterUrl != null && this.remoteRefreshTime > System.currentTimeMillis() - REMOTE_REFRESH_MILLIS) { return resultClusterUrl; } } finally { remoteInfoReadLock.unlock(); } refreshRemoteInfo(); remoteInfoReadLock.lock(); try { return this.activeClusterUrl; } finally { remoteInfoReadLock.unlock(); } } public void setClusterUrls(Set<String> clusterUrls) { this.clusterUrls = clusterUrls; } public Set<String> getClusterUrls() { return clusterUrls; } public void setSslContext(SSLContext sslContext) { this.sslContext = sslContext; } public void setConnectTimeoutMillis(int connectTimeoutMillis) { this.connectTimeoutMillis = connectTimeoutMillis; } public void setReadTimeoutMillis(int readTimeoutMillis) { this.readTimeoutMillis = readTimeoutMillis; } public void setProxy(HttpProxy proxy) { this.proxy = proxy; } public void setLocalAddress(InetAddress localAddress) { this.localAddress = localAddress; } }