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

io.fabric8.kubernetes.client.dsl.internal.PortForwarderWebsocket Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2015 Red Hat, 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.fabric8.kubernetes.client.dsl.internal;

import io.fabric8.kubernetes.client.LocalPortForward;
import io.fabric8.kubernetes.client.PortForward;
import io.fabric8.kubernetes.client.http.HttpClient;
import io.fabric8.kubernetes.client.http.WebSocket;
import io.fabric8.kubernetes.client.utils.URLUtils;
import io.fabric8.kubernetes.client.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URL;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * A port-forwarder using the websocket protocol.
 * It requires Kubernetes 1.6+ (previous versions support the SPDY protocol only).
 */
public class PortForwarderWebsocket {

  private static final Logger LOG = LoggerFactory.getLogger(PortForwarderWebsocket.class);

  private final HttpClient client;
  private final Executor executor;
  private final long connectTimeoutMills;

  public PortForwarderWebsocket(HttpClient client, Executor executor, long connectTimeoutMillis) {
    this.client = client;
    this.executor = executor;
    this.connectTimeoutMills = connectTimeoutMillis;
  }

  public LocalPortForward forward(final URL resourceBaseUrl, final int port, final InetAddress localHost, final int localPort) {
    try {
      InetSocketAddress inetSocketAddress = createNewInetSocketAddress(localHost, localPort);
      final ServerSocketChannel server = ServerSocketChannel.open().bind(inetSocketAddress);

      final AtomicBoolean alive = new AtomicBoolean(true);
      final CopyOnWriteArrayList handles = new CopyOnWriteArrayList<>();

      final ExecutorService executorService = Executors.newSingleThreadExecutor();

      // Create a handle that can be used to retrieve information and stop the port-forward
      final LocalPortForward localPortForwardHandle = new LocalPortForward() {
        @Override
        public void close() throws IOException {
          alive.set(false);
          try {
            server.close();
          } finally {
            Utils.closeQuietly(handles);
            executorService.shutdownNow();
          }
        }

        @Override
        public boolean isAlive() {
          return alive.get();
        }

        @Override
        public boolean errorOccurred() {
          for (PortForward handle : handles) {
            if (handle.errorOccurred()) {
              return true;
            }
          }
          return false;
        }

        @Override
        public InetAddress getLocalAddress() {
          try {
            return ((InetSocketAddress) server.getLocalAddress()).getAddress();
          } catch (IOException e) {
            throw new IllegalStateException("Cannot determine local address", e);
          }
        }

        @Override
        public int getLocalPort() {
          try {
            return ((InetSocketAddress) server.getLocalAddress()).getPort();
          } catch (IOException e) {
            throw new IllegalStateException("Cannot determine local address", e);
          }
        }

        @Override
        public Collection getClientThrowables() {
          Collection clientThrowables = new ArrayList<>();
          for (PortForward handle : handles) {
            clientThrowables.addAll(handle.getClientThrowables());
          }
          return clientThrowables;
        }

        @Override
        public Collection getServerThrowables() {
          Collection serverThrowables = new ArrayList<>();
          for (PortForward handle : handles) {
            serverThrowables.addAll(handle.getServerThrowables());
          }
          return serverThrowables;
        }
      };

      // Start listening on localhost for new connections.
      // Every new connection will open its own stream on the remote resource.
      executorService.execute(() -> {
        // accept cycle
        while (alive.get()) {
          try {
            SocketChannel socket = server.accept();
            handles.add(forward(resourceBaseUrl, port, socket, socket));
          } catch (IOException e) {
            if (alive.get()) {
              LOG.error("Error while listening for connections", e);
            }
            Utils.closeQuietly(localPortForwardHandle);
          }
        }
      });

      return localPortForwardHandle;
    } catch (IOException e) {
      throw new IllegalStateException("Unable to port forward", e);
    }
  }

  public PortForward forward(URL resourceBaseUrl, int port, final ReadableByteChannel in, final WritableByteChannel out) {
    final PortForwarderWebsocketListener listener = new PortForwarderWebsocketListener(in, out, executor);
    CompletableFuture socket = client
        .newWebSocketBuilder()
        .uri(URI.create(URLUtils.join(resourceBaseUrl.toString(), "portforward?ports=" + port)))
        .connectTimeout(connectTimeoutMills, TimeUnit.MILLISECONDS)
        .subprotocol("v4.channel.k8s.io")
        .buildAsync(listener);

    socket.whenComplete((w, t) -> {
      if (t != null) {
        listener.onError(w, t);
      }
    });

    return new PortForward() {
      private final AtomicBoolean closed = new AtomicBoolean();

      @Override
      public void close() {
        if (!closed.compareAndSet(false, true)) {
          return;
        }
        socket.whenComplete((w, t) -> {
          if (w != null) {
            listener.closeBothWays(w, 1001, "User closing");
          }
        });
      }

      @Override
      public boolean isAlive() {
        return listener.isAlive();
      }

      @Override
      public boolean errorOccurred() {
        return listener.errorOccurred();
      }

      @Override
      public Collection getClientThrowables() {
        return listener.getClientThrowables();
      }

      @Override
      public Collection getServerThrowables() {
        return listener.getServerThrowables();
      }
    };
  }

  InetSocketAddress createNewInetSocketAddress(InetAddress localHost, int localPort) {
    if (localHost == null) {
      return new InetSocketAddress(localPort);
    }
    return new InetSocketAddress(localHost, localPort);
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy