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

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

/**
 * 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.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.KubernetesResourceList;
import io.fabric8.kubernetes.api.model.ListOptions;
import io.fabric8.kubernetes.api.model.Status;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.Watcher;
import io.fabric8.kubernetes.client.dsl.base.BaseOperation;
import io.fabric8.kubernetes.client.dsl.base.OperationSupport;
import io.fabric8.kubernetes.client.http.HttpClient;
import io.fabric8.kubernetes.client.http.HttpResponse;
import io.fabric8.kubernetes.client.http.WebSocket;
import io.fabric8.kubernetes.client.http.WebSocket.Builder;
import io.fabric8.kubernetes.client.http.WebSocketHandshakeException;
import io.fabric8.kubernetes.client.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

import static java.net.HttpURLConnection.HTTP_OK;
import static java.net.HttpURLConnection.HTTP_UNAVAILABLE;

/**
 * Manages a WebSocket and listener per request
 */
public class WatchConnectionManager> extends AbstractWatchManager {
  
  private static final Logger logger = LoggerFactory.getLogger(WatchConnectionManager.class);
  
  protected WatcherWebSocketListener listener;
  private CompletableFuture websocketFuture;
  private WebSocket websocket;

  private volatile boolean ready;
  
  static void closeWebSocket(WebSocket webSocket) {
    if (webSocket != null) {
      logger.debug("Closing websocket {}", webSocket);
      try {
        if (!webSocket.sendClose(1000, null)) {
          logger.debug("Websocket already closed {}", webSocket);
        }
      } catch (IllegalStateException e) {
        logger.error("invalid code for websocket: {} {}", e.getClass(), e.getMessage());
      }
    }
  }
  
  public WatchConnectionManager(final HttpClient client, final BaseOperation baseOperation, final ListOptions listOptions, final Watcher watcher, final int reconnectInterval, final int reconnectLimit, long websocketTimeout, int maxIntervalExponent) throws MalformedURLException {
    super(watcher, baseOperation, listOptions, reconnectLimit, reconnectInterval, maxIntervalExponent, () -> client.newBuilder()
            .readTimeout(websocketTimeout, TimeUnit.MILLISECONDS)
            .build());
  }
  
  public WatchConnectionManager(final HttpClient client, final BaseOperation baseOperation, final ListOptions listOptions, final Watcher watcher, final int reconnectInterval, final int reconnectLimit, long websocketTimeout) throws MalformedURLException {
    // Default max 32x slowdown from base interval
    this(client, baseOperation, listOptions, watcher, reconnectInterval, reconnectLimit, websocketTimeout, 5);
  }
  
  @Override
  protected synchronized void closeRequest() {
    closeWebSocket(websocket);
    if (this.websocketFuture != null) {
      this.websocketFuture.whenComplete((w, t) -> {
        if (w != null) {
          closeWebSocket(w);
        }
      });
      websocketFuture = null;
    }
  }

  public void waitUntilReady() {
    Utils.waitUntilReadyOrFail(websocketFuture, -1, TimeUnit.SECONDS);
    ready = true;
    this.websocket = websocketFuture.getNow(null);
  }

  synchronized WatcherWebSocketListener getListener() {
    return listener;
  }
  
  @Override
  protected void run(URL url, Map headers) {
    this.listener = new WatcherWebSocketListener<>(this);
    Builder builder = client.newWebSocketBuilder();
    headers.forEach(builder::header);
    builder.uri(URI.create(url.toString()));
    
    this.websocketFuture = builder.buildAsync(this.listener).handle((w, t) -> {
      if (t != null) {
        if (t instanceof WebSocketHandshakeException) {
          WebSocketHandshakeException wshe = (WebSocketHandshakeException)t;
          HttpResponse response = wshe.getResponse();
          final int code = response.code();
          // We do not expect a 200 in response to the websocket connection. If it occurs, we throw
          // an exception and try the watch via a persistent HTTP Get.
          // Newer Kubernetes might also return 503 Service Unavailable in case WebSockets are not supported
          Status status = OperationSupport.createStatus(response);
          if (HTTP_OK == code || HTTP_UNAVAILABLE == code) {
            throw OperationSupport.requestFailure(client.newHttpRequestBuilder().url(url).build(), status, "Received " + code + " on websocket");
          } 
          logger.warn("Exec Failure: HTTP {}, Status: {} - {}", code, status.getCode(), status.getMessage());
          t = OperationSupport.requestFailure(client.newHttpRequestBuilder().url(url).build(), status);
        }
        if (ready) {
          // if we're not ready yet, that means we're waiting on the future and there's
          // no need to invoke the reconnect logic
          listener.onError(w, t);
        }
        throw KubernetesClientException.launderThrowable(t);
      }
      return w;
    });
  }
  
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy