001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2017 MicroBean. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 014 * implied. See the License for the specific language governing 015 * permissions and limitations under the License. 016 */ 017package org.microbean.kubernetes; 018 019import java.io.Closeable; // for javadoc only 020 021import java.net.MalformedURLException; 022import java.net.URL; 023 024import io.fabric8.kubernetes.api.model.Pod; 025import io.fabric8.kubernetes.api.model.PodCondition; 026import io.fabric8.kubernetes.api.model.PodList; 027import io.fabric8.kubernetes.api.model.PodStatus; 028 029import io.fabric8.kubernetes.client.LocalPortForward; 030 031import io.fabric8.kubernetes.client.dsl.Listable; 032 033import io.fabric8.kubernetes.client.dsl.base.OperationSupport; 034 035import io.fabric8.kubernetes.client.dsl.internal.PortForwarderWebsocket; 036 037import okhttp3.OkHttpClient; 038 039/** 040 * A utility class that helps with operations concerning {@link Pod}s. 041 * 042 * @author <a href="https://about.me/lairdnelson" 043 * target="_parent">Laird Nelson</a> 044 * 045 * @see #forwardPort(OkHttpClient, Listable, int) 046 * 047 * @see #isReady(Pod) 048 * 049 * @see Pod 050 */ 051public final class Pods { 052 053 054 /* 055 * Constructors. 056 */ 057 058 /** 059 * Creates a new {@link Pods} instance. 060 */ 061 private Pods() { 062 super(); 063 } 064 065 066 /* 067 * Static methods. 068 */ 069 070 071 /** 072 * Forwards an arbitrary local port to the supplied {@code 073 * remotePort} on the {@linkplain #getFirstReadyPod(Listable) first 074 * ready <code>Pod</code>} in the supplied {@link Listable} using 075 * the supplied {@link OkHttpClient}. 076 * 077 * <p>This method may return {@code null}.</p> 078 * 079 * @param httpClient an {@link OkHttpClient} used to communicate 080 * with Kubernetes; may be {@code null} in which case {@code null} 081 * will be returned 082 * 083 * @param pods a {@link Listable} of {@link PodList}s; <strong>must 084 * also be an instance of {@link OperationSupport}</strong> or 085 * {@code null} will be returned 086 * 087 * @param remotePort the port on the {@link Pod} that is ultimately 088 * located to which traffic should be forwarded 089 * 090 * @return a {@link LocalPortForward} representing the port 091 * forwarding operation, or {@code null}; must be {@linkplain 092 * Closeable#close() closed} if non-{@code null} 093 * 094 * @exception MalformedURLException if a {@link URL} to the 095 * Kubernetes resource representing the {@link Pod} in question 096 * could not be constructed 097 * 098 * @see PortForwarderWebsocket#PortForwarderWebsocket(OkHttpClient) 099 * 100 * @see <a 101 * href="https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-conditions">Pod 102 * conditions documentation</a> 103 */ 104 public static final LocalPortForward forwardPort(final OkHttpClient httpClient, final Listable<? extends PodList> pods, final int remotePort) throws MalformedURLException { 105 return forwardPort(httpClient, pods, remotePort, 0); 106 } 107 108 public static final LocalPortForward forwardPort(final OkHttpClient httpClient, final Listable<? extends PodList> pods, final int remotePort, final int localPort) throws MalformedURLException { 109 LocalPortForward returnValue = null; 110 if (httpClient != null && pods instanceof OperationSupport) { 111 final String urlBase = ((OperationSupport)pods).getNamespacedUrl().toExternalForm(); 112 assert urlBase != null; 113 final Pod readyPod = getFirstReadyPod(pods); 114 if (readyPod != null) { 115 final String name = readyPod.getMetadata().getName(); 116 assert name != null; 117 final URL url = new URL(new StringBuilder(urlBase).append("/").append(name).toString()); 118 PortForwarderWebsocket portForwarderWebsocket = new PortForwarderWebsocket(httpClient); 119 returnValue = localPort <= 0 ? portForwarderWebsocket.forward(url, remotePort) 120 : portForwarderWebsocket.forward(url, remotePort, localPort); 121 } 122 } 123 return returnValue; 124 } 125 126 /** 127 * Returns {@link Boolean#TRUE} if the supplied {@link Pod} is known 128 * to be ready, {@link Boolean#FALSE} if it is known to be not ready 129 * and {@code null} if its readiness status is unknown. 130 * 131 * <p>This method may return {@code null}.</p> 132 * 133 * @param pod the {@link Pod} to check; may be {@code null} in which 134 * case {@code null} will be returned 135 * 136 * @return {@link Boolean#TRUE} if the supplied {@link Pod} is known 137 * to be ready, {@link Boolean#FALSE} if it is known to be not ready 138 * and {@code null} if its readiness status is unknown 139 * 140 * @see #isReady(PodStatus) 141 * 142 * @see <a 143 * href="https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-conditions">Pod 144 * conditions documentation</a> 145 */ 146 public static final Boolean isReady(final Pod pod) { 147 Boolean returnValue = null; 148 if (pod != null) { 149 returnValue = isReady(pod.getStatus()); 150 } 151 return returnValue; 152 } 153 154 /** 155 * Returns {@link Boolean#TRUE} if the supplied {@link PodStatus} is 156 * known to be ready, {@link Boolean#FALSE} if it is known to be not 157 * ready and {@code null} if its readiness status is unknown. 158 * 159 * <p>This method may return {@code null}.</p> 160 * 161 * @param podStatus the {@link PodStatus} to check; may be {@code 162 * null} in which case {@code null} will be returned 163 * 164 * @return {@link Boolean#TRUE} if the supplied {@link PodStatus} is 165 * known to be ready, {@link Boolean#FALSE} if it is known to be not 166 * ready and {@code null} if its readiness status is unknown 167 * 168 * @see #isReady(Iterable) 169 * 170 * @see <a 171 * href="https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-conditions">Pod 172 * conditions documentation</a> 173 */ 174 public static final Boolean isReady(final PodStatus podStatus) { 175 Boolean returnValue = null; 176 if (podStatus != null) { 177 returnValue = isReady(podStatus.getConditions()); 178 } 179 return returnValue; 180 } 181 182 /** 183 * Returns {@link Boolean#TRUE} if the supplied {@link Iterable} of 184 * {@link PodCondition}s contains a {@link PodCondition} whose 185 * {@link PodCondition#getType()} method returns {@code Ready} and 186 * whose {@link PodCondition#getStatus()} method returns {@code 187 * True}. 188 * 189 * <p>If the supplied {@link Iterable} of 190 * {@link PodCondition}s <em>also</em> contains a {@link PodCondition} whose 191 * {@link PodCondition#getType()} method returns {@code Ready} and 192 * whose {@link PodCondition#getStatus()} method returns {@code 193 * False}, then {@code null} is returned.</p> 194 * 195 * <p>If instead the supplied {@link Iterable} of 196 * {@link PodCondition}s contains a {@link PodCondition} whose 197 * {@link PodCondition#getType()} method returns {@code Ready} and 198 * whose {@link PodCondition#getStatus()} method returns {@code 199 * False} and does not <em>also</em> contains a {@link PodCondition} whose 200 * {@link PodCondition#getType()} method returns {@code Ready} and 201 * whose {@link PodCondition#getStatus()} method returns {@code 202 * True}, then {@link Boolean#FALSE} is returned.</p> 203 * 204 * <p>{@code null} is returned in all other cases.</p> 205 * 206 * <p>This method may return {@code null}.</p> 207 * 208 * @param podConditions an {@link Iterable} of {@link 209 * PodCondition}s; may be {@code null} in which case {@code null} 210 * will be returned 211 * 212 * @return {@link Boolean#TRUE} if the supplied {@link Iterable} of 213 * {@link PodCondition}s represents a state of affairs known to 214 * represent the readiness of the {@link PodStatus} they describe; 215 * {@link Boolean#FALSE} if the supplied {@link Iterable} of {@link 216 * PodCondition}s represents a state of affairs known to represent 217 * the unreadiness of the {@link PodStatus} they describe, or {@code 218 * null} in all other cases 219 * 220 * @see <a 221 * href="https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-conditions">Pod 222 * conditions documentation</a> 223 */ 224 public static final Boolean isReady(final Iterable<? extends PodCondition> podConditions) { 225 Boolean returnValue = null; 226 if (podConditions != null) { 227 for (final PodCondition condition : podConditions) { 228 if (condition != null) { 229 final String conditionType = condition.getType(); 230 // See https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-conditions. 231 if ("Ready".equals(conditionType)) { 232 final String conditionStatus = condition.getStatus(); 233 switch (conditionStatus) { 234 case "True": 235 if (Boolean.FALSE.equals(returnValue)) { 236 returnValue = null; 237 } else { 238 returnValue = Boolean.TRUE; 239 } 240 break; 241 case "False": 242 if (Boolean.TRUE.equals(returnValue)) { 243 returnValue = null; 244 } else { 245 returnValue = Boolean.FALSE; 246 } 247 break; 248 case "Unknown": 249 returnValue = null; 250 break; 251 default: 252 throw new IllegalStateException("Unexpected value for PodCondition of type Ready: " + conditionStatus); 253 } 254 } 255 } 256 } 257 } 258 return returnValue; 259 } 260 261 /** 262 * Returns the first {@link Pod} encountered in the supplied {@link 263 * Iterable} of {@link Pod}s for which the {@link #isReady(Pod)} 264 * method returns {@link Boolean#TRUE}, or {@code null}. 265 * 266 * <p>This method may return {@code null}.</p> 267 * 268 * @param pods an {@link Iterable} of {@link Pod}s to check; may be 269 * {@code null} in which case {@code null} will be returned 270 * 271 * @return a {@link Pod} known to be ready, or {@code null} 272 * 273 * @see #isReady(Pod) 274 * 275 * @see <a 276 * href="https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-conditions">Pod 277 * conditions documentation</a> 278 */ 279 public static final Pod getFirstReadyPod(final Iterable<? extends Pod> pods) { 280 Pod returnValue = null; 281 if (pods != null) { 282 for (final Pod pod : pods) { 283 if (Boolean.TRUE.equals(isReady(pod))) { 284 returnValue = pod; 285 break; 286 } 287 } 288 } 289 return returnValue; 290 } 291 292 /** 293 * Returns the first {@link Pod} encountered in the supplied {@link 294 * PodList} of {@link Pod}s for which the {@link #isReady(Pod)} 295 * method returns {@link Boolean#TRUE}, or {@code null}. 296 * 297 * <p>This method may return {@code null}.</p> 298 * 299 * @param podList a {@link PodList} of {@link Pod}s to check; may be 300 * {@code null} in which case {@code null} will be returned 301 * 302 * @return a {@link Pod} known to be ready, or {@code null} 303 * 304 * @see #getFirstReadyPod(Iterable) 305 * 306 * @see #isReady(Pod) 307 * 308 * @see <a 309 * href="https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-conditions">Pod 310 * conditions documentation</a> 311 */ 312 public static final Pod getFirstReadyPod(final PodList podList) { 313 Pod returnValue = null; 314 if (podList != null) { 315 returnValue = getFirstReadyPod(podList.getItems()); 316 } 317 return returnValue; 318 } 319 320 /** 321 * Returns the first {@link Pod} reachable from the supplied {@link 322 * Listable} of {@link PodList}s for which the {@link #isReady(Pod)} 323 * method returns {@link Boolean#TRUE}, or {@code null}. 324 * 325 * <p>This method may return {@code null}.</p> 326 * 327 * @param podsResource a {@link Listable} of {@link PodList}s 328 * representing {@link Pod}s to check; may be {@code null} in which 329 * case {@code null} will be returned 330 * 331 * @return a {@link Pod} known to be ready, or {@code null} 332 * 333 * @see #getFirstReadyPod(PodList) 334 * 335 * @see #isReady(Pod) 336 * 337 * @see <a 338 * href="https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-conditions">Pod 339 * conditions documentation</a> 340 */ 341 public static final Pod getFirstReadyPod(final Listable<? extends PodList> podsResource) { 342 Pod returnValue = null; 343 if (podsResource != null) { 344 returnValue = getFirstReadyPod(podsResource.list()); 345 } 346 return returnValue; 347 } 348 349}