Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.fabric8.jube.local.NodeHelper Maven / Gradle / Ivy
/**
* Copyright 2005-2014 Red Hat, Inc.
*
* Red Hat 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 io.fabric8.jube.local;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.collect.ImmutableSet;
import io.fabric8.jube.KubernetesModel;
import io.fabric8.jube.Statuses;
import io.fabric8.jube.apimaster.ApiMasterService;
import io.fabric8.jube.process.InstallOptions;
import io.fabric8.jube.process.Installation;
import io.fabric8.jube.process.ProcessController;
import io.fabric8.jube.process.ProcessManager;
import io.fabric8.jube.util.ImageMavenCoords;
import io.fabric8.jube.util.InstallHelper;
import io.fabric8.kubernetes.api.KubernetesHelper;
import io.fabric8.kubernetes.api.model.ContainerState;
import io.fabric8.kubernetes.api.model.ContainerStateRunning;
import io.fabric8.kubernetes.api.model.ContainerStatusBuilder;
import io.fabric8.kubernetes.api.model.EnvVar;
import io.fabric8.kubernetes.api.model.PodSpec;
import io.fabric8.kubernetes.api.model.PodTemplateSpec;
import io.fabric8.kubernetes.api.model.ReplicationControllerSpec;
import io.fabric8.kubernetes.api.model.ReplicationControllerStatus;
import io.fabric8.kubernetes.api.model.PodStatus;
import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.ContainerStatus;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.ContainerPort;
import io.fabric8.kubernetes.api.model.ReplicationController;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.utils.Objects;
import io.fabric8.utils.Strings;
import io.hawt.aether.OpenMavenURL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static io.fabric8.kubernetes.api.KubernetesHelper.getName;
import static io.fabric8.kubernetes.api.KubernetesHelper.getPorts;
/**
* A set of helper functions for implementing the local node
*/
public final class NodeHelper {
public static final String KIND_POD = "Pod";
public static final String KIND_REPLICATION_CONTROLLER = "ReplicationController";
public static final String KIND_SERVICE = "SERVICE";
// use 60 second stop timeout by default
private static final int STOP_TIMEOUT = 60;
private static final transient Logger LOG = LoggerFactory.getLogger(NodeHelper.class);
private NodeHelper() {
// utility class
}
/**
* Returns the desired state; lazily creating one if required
*/
public static PodSpec getOrCreatePodSpec(Pod pod) {
Objects.notNull(pod, "pod");
PodSpec desiredState = pod.getSpec();
if (desiredState == null) {
desiredState = new PodSpec();
pod.setSpec(desiredState);
}
return desiredState;
}
/**
* Returns the current state of the given pod; lazily creating one if required
*/
public static PodStatus getOrCreatetStatus(Pod pod) {
Objects.notNull(pod, "pod");
PodStatus currentState = pod.getStatus();
if (currentState == null) {
currentState = new PodStatus();
pod.setStatus(currentState);
}
return currentState;
}
public static PodTemplateSpec getPodTemplateSpec(ReplicationController replicationController) {
if (replicationController != null) {
return getPodTemplateSpec(replicationController.getSpec());
}
return null;
}
public static PodTemplateSpec getPodTemplateSpec(ReplicationControllerSpec replicationControllerSpec) {
PodTemplateSpec podTemplate = null;
if (replicationControllerSpec != null) {
podTemplate = replicationControllerSpec.getTemplate();
}
return podTemplate;
}
public static PodSpec getPodTemplatePodSpec(ReplicationController replicationController) {
if (replicationController != null) {
return getPodTemplatePodSpec(replicationController.getSpec());
}
return null;
}
public static PodSpec getPodTemplatePodSpec(ReplicationControllerSpec replicationControllerSpec) {
PodTemplateSpec podTemplate;
PodSpec podTemplatePodStatus = null;
if (replicationControllerSpec != null) {
podTemplate = replicationControllerSpec.getTemplate();
if (podTemplate != null) {
podTemplatePodStatus = podTemplate.getSpec();
}
}
return podTemplatePodStatus;
}
/**
* Returns the current container map for the current pod state; lazily creating if required
*/
public static List getOrCreateContainerStatuses(Pod pod) {
PodStatus currentState = getOrCreatetStatus(pod);
List containerStatuses = currentState.getContainerStatuses();
if (containerStatuses == null) {
containerStatuses = new ArrayList<>();
currentState.setContainerStatuses(containerStatuses);
}
return containerStatuses;
}
/**
* Returns the containers state, lazily creating any objects if required.
*/
public static ContainerState getOrCreateContainerState(Pod pod, String containerName) {
ContainerStatus containerInfo = getOrCreateContainerInfo(pod, containerName);
ContainerState state = containerInfo.getState();
if (state == null) {
state = new ContainerState();
containerInfo.setState(state);
}
return state;
}
/**
* Returns the container information for the given pod and container name, lazily creating as required
*/
public static ContainerStatus getOrCreateContainerInfo(Pod pod, String containerName) {
List containerStatuses = getOrCreateContainerStatuses(pod);
for (ContainerStatus containerStatus : containerStatuses) {
String containerID = containerStatus.getContainerID();
if (Objects.equal(containerName, containerID)) {
return containerStatus;
}
}
ContainerStatus status = new ContainerStatus();
status.setContainerID(containerName);
containerStatuses.add(status);
return status;
}
/**
* Creates any missing containers; updating the currentState with the new values.
*/
public static String createMissingContainers(final ProcessManager processManager, final KubernetesModel model, final Pod pod,
final PodStatus currentState, List containers) throws Exception {
Map currentContainers = KubernetesHelper.getCurrentContainers(currentState);
for (final Container container : containers) {
// lets update the pod model if we update the ports
podTransaction(model, pod, new Callable() {
@Override
public Void call() throws Exception {
createContainer(processManager, model, container, pod, currentState);
return null;
}
});
}
return null;
}
/**
* Converts a possibly null list of Env objects into a Map of environment variables
*/
public static Map createEnvironmentVariableMap(List envList) {
Map answer = new HashMap<>();
if (envList != null) {
for (EnvVar env : envList) {
String name = env.getName();
String value = env.getValue();
if (Strings.isNotBlank(name)) {
answer.put(name, value);
}
}
}
return answer;
}
public static List createEnvironmentVariables(Map environment) {
List answer = new ArrayList<>();
Set> entries = environment.entrySet();
for (Map.Entry entry : entries) {
String key = entry.getKey();
String value = entry.getValue();
if (Strings.isNotBlank(key)) {
EnvVar env = new EnvVar();
env.setName(key);
env.setValue(value);
answer.add(env);
}
}
return answer;
}
protected static void createContainer(ProcessManager processManager, KubernetesModel model, Container container, Pod pod, PodStatus currentState) throws Exception {
String containerName = container.getName();
String image = container.getImage();
Strings.notEmpty(image);
OpenMavenURL mavenUrl = ImageMavenCoords.dockerImageToMavenURL(image);
Objects.notNull(mavenUrl, "mavenUrl");
LOG.info("Creating new container: {} from url: {}", containerName, mavenUrl);
Map envVarMap = createEnvironmentVariableMap(container.getEnv());
// now lets copy in the service env vars...
appendServiceEnvironmentVariables(envVarMap, model);
LOG.info("Env variables are: {}", envVarMap);
InstallOptions.InstallOptionsBuilder builder = new InstallOptions.InstallOptionsBuilder().
url(mavenUrl).environment(envVarMap);
if (Strings.isNotBlank(containerName)) {
builder = builder.name(containerName).id(containerName);
}
InstallOptions installOptions = builder.build();
try {
Installation installation;
try {
installation = processManager.install(installOptions, null);
} catch (IOException ioe) {
LOG.debug("Cannot find image at {} - trying with default prefix", mavenUrl);
mavenUrl = ImageMavenCoords.dockerImageToMavenURL(image, true);
Objects.notNull(mavenUrl, "mavenUrl");
builder = new InstallOptions.InstallOptionsBuilder().
url(mavenUrl).environment(envVarMap);
if (Strings.isNotBlank(containerName)) {
builder = builder.name(containerName).id(containerName);
}
installOptions = builder.build();
installation = processManager.install(installOptions, null);
}
File installDir = installation.getInstallDir();
ContainerStatus containerInfo = NodeHelper.getOrCreateContainerInfo(pod, containerName);
LOG.info("Installed new process in directory: {}", installDir);
ProcessController controller = installation.getController();
Map environment = controller.getConfig().getEnvironment();
container.setEnv(createEnvironmentVariables(environment));
createInstallationPorts(environment, installation, container);
model.updatePod(getName(pod), pod);
// TODO add a container to the current state
LOG.info("Staring container: {}", containerName);
controller.start();
Long pid = controller.getPid();
boolean alive = pid != null && pid > 0;
LOG.info("Container: {} has pid: {} and is alive: {}", containerName, pid != null ? pid : "", alive);
containerAlive(pod, containerName, alive);
} catch (Exception e) {
setPodTerminated(currentState, e);
System.out.println("ERROR: Failed to create pod: " + getName(pod) + ". " + e);
e.printStackTrace();
LOG.error("Failed to create pod: " + getName(pod) + ". " + e.getMessage(), e);
}
}
protected static List createInstallationPorts(Map environment, Installation installation, Container container) {
try {
List answer = container.getPorts();
if (answer == null) {
answer = new ArrayList<>();
container.setPorts(answer);
}
File installDir = installation.getInstallDir();
Map portMap = InstallHelper.readPortsFromDirectory(installDir);
Set> entries = portMap.entrySet();
for (Map.Entry entry : entries) {
String key = entry.getKey();
String value = entry.getValue();
String containerPort = value;
String envVarName = InstallHelper.portNameToHostEnvVarName(key);
String hostPort = environment.get(envVarName);
if (Strings.isNullOrBlank(hostPort)) {
LOG.warn("Cannot find env var value for $" + envVarName + " so cannot define the host port");
} else {
int containerPortNumber = toPortNumber(value, key, "container");
int hostPortNumber = toPortNumber(hostPort, key, "host");
if (containerPortNumber > 0) {
// lets find a port object for the given number
ContainerPort port = findPortWithContainerPort(answer, containerPortNumber);
if (port == null) {
port = new ContainerPort();
port.setContainerPort(containerPortNumber);
answer.add(port);
}
port.setName(key);
if (hostPortNumber > 0) {
port.setHostPort(hostPortNumber);
}
}
}
}
return answer;
} catch (IOException e) {
LOG.warn("Failed to load ports for installation " + installation + ". " + e.getMessage(), e);
return null;
}
}
public static ContainerPort findPortWithContainerPort(List ports, int containerPortNumber) {
for (ContainerPort port : ports) {
Integer containerPort = port.getContainerPort();
if (containerPort != null && containerPort == containerPortNumber) {
return port;
}
}
return null;
}
protected static int toPortNumber(String text, String key, String description) {
try {
return Integer.parseInt(text);
} catch (NumberFormatException e) {
LOG.warn("Failed to parse port text '" + text + "' for port " + key + " " + description + ". " + e, e);
return -1;
}
}
public static int findHostPortForService(Pod pod, int serviceContainerPort) {
List containers = KubernetesHelper.getContainers(pod);
for (Container container : containers) {
List ports = container.getPorts();
if (ports != null) {
for (ContainerPort port : ports) {
Integer containerPort = port.getContainerPort();
Integer hostPort = port.getHostPort();
if (containerPort != null && containerPort == serviceContainerPort) {
if (hostPort != null) {
return hostPort;
}
}
}
}
}
LOG.warn("Could not find host port for service port: {} pod: {}", serviceContainerPort, pod);
return serviceContainerPort;
}
public static void appendServiceEnvironmentVariables(Map map, KubernetesModel model) {
ImmutableSet> entries = model.getServiceMap().entrySet();
for (Map.Entry entry : entries) {
String id = entry.getKey().toUpperCase().replaceAll("-", "_");
String envVarPrefix = id + "_SERVICE_";
Service service = entry.getValue();
// TODO should we allow different service ports?
String host = ApiMasterService.getHostName();
Set ports = getPorts(service);
for (Integer port : ports) {
String hostEnvVar = envVarPrefix + "HOST";
String portEnvVar = envVarPrefix + "PORT";
map.put(hostEnvVar, host);
map.put(portEnvVar, "" + port);
}
}
}
public static void deleteContainers(ProcessManager processManager, KubernetesModel model, Pod pod, PodStatus currentState, List desiredContainers) throws Exception {
for (Container container : desiredContainers) {
deleteContainer(processManager, model, container, pod, currentState);
}
}
protected static void deleteContainer(ProcessManager processManager, KubernetesModel model, Container container, Pod pod, PodStatus currentState) throws Exception {
String containerName = container.getName();
Installation installation = processManager.getInstallation(containerName);
if (installation == null) {
LOG.info("Cannot delete non existing container: {}", containerName);
return;
}
ProcessController controller = installation.getController();
boolean kill = false;
// try graceful to stop first, then kill afterwards
// as the controller may issue a command that stops asynchronously, we need to check if the pid is alive
// until its graceful shutdown, before we go harder and try to kill it
try {
LOG.info("Stopping container: {}", containerName);
controller.stop();
} catch (Exception e) {
kill = true;
LOG.warn("Error during stopping container: " + containerName + ". This exception is ignored", e);
}
// if the process was alive then kill is not needed
boolean stopped = false;
for (int i = 0; i < STOP_TIMEOUT; i++) {
if (i > 0) {
LOG.info("Waiting for {} seconds to graceful stop container: {}", i, containerName);
}
// check if the process has been stopped graceful
if (!safeCheckIsAlive(installation)) {
stopped = true;
break;
} else {
// wait 1 sec
Thread.sleep(1000);
}
}
if (!stopped) {
LOG.warn("Cannot graceful stop container: {} after {} seconds. Will now forcibly shutdown the container.", containerName, STOP_TIMEOUT);
}
// if we did not stop graceful then do a kill
if (kill || !stopped) {
try {
LOG.info("Killing container: {}", containerName);
controller.kill();
} catch (Exception e) {
LOG.warn("Error during killing container: " + containerName + ". This exception is ignored", e);
}
}
try {
LOG.info("Uninstalling container: {}", containerName);
controller.uninstall();
} catch (Exception e) {
LOG.warn("Error during uninstalling container: " + containerName + ". This exception is ignored", e);
}
try {
LOG.info("Deleting pod: {}", getName(pod));
model.deletePod(getName(pod), KubernetesHelper.getNamespace(pod));
} catch (Exception e) {
// ignore
LOG.warn("Error during deleting pod: " + getName(pod) + ". This exception is ignored", e);
}
LOG.info("Deleted container: {} done", containerName);
}
private static boolean safeCheckIsAlive(Installation installation) {
Long pid = 0L;
try {
pid = installation.getActivePid();
} catch (Exception e) {
// ignore, but force a pid value so we run for the timeout duration
}
return pid != null && pid.longValue() > 0;
}
public static void containerAlive(Pod pod, String id, boolean alive) {
PodStatus currentState = getOrCreatetStatus(pod);
if (alive) {
setPodRunning(currentState);
} else {
// lets check if we're waiting...
List containerStatuses = getOrCreateContainerStatuses(pod);
if (containerStatuses.isEmpty() || isWaiting(containerStatuses)) {
setPodWaiting(currentState);
} else {
setPodTerminated(currentState, "Failed");
}
}
setContainerRunningState(pod, id, alive);
}
protected static boolean isWaiting(List containerStatuses) {
for (ContainerStatus status : containerStatuses) {
ContainerState state = status.getState();
if (state != null && state.getWaiting() != null) {
return true;
}
}
return false;
}
public static void setContainerRunningState(Pod pod, String id, boolean alive) {
ContainerState state = getOrCreateContainerState(pod, id);
if (alive) {
ContainerStateRunning running = new ContainerStateRunning();
state.setRunning(running);
} else {
state.setRunning(null);
}
}
public static Container addOrUpdateDesiredContainer(Pod pod, String containerName, Container container) {
List containers = getOrCreatePodDesiredContainers(pod);
Container oldContainer = findContainer(containers, containerName);
if (oldContainer != null) {
// lets update it just in case something changed...
containers.remove(oldContainer);
}
Container newContainer = new Container();
// TODO we should use bean utils or something to copy properties in case we miss one!
newContainer.setCommand(container.getCommand());
newContainer.setEnv(container.getEnv());
newContainer.setImage(container.getImage());
newContainer.setPorts(container.getPorts());
newContainer.setVolumeMounts(container.getVolumeMounts());
newContainer.setWorkingDir(container.getWorkingDir());
newContainer.getAdditionalProperties().putAll(container.getAdditionalProperties());
newContainer.setName(containerName);
LOG.info("Added new container: {}", containerName);
containers.add(newContainer);
return newContainer;
}
public static List getOrCreatePodDesiredContainers(Pod pod) {
PodSpec podSpec = NodeHelper.getOrCreatePodSpec(pod);
List containers = podSpec.getContainers();
if (containers == null) {
containers = new ArrayList<>();
podSpec.setContainers(containers);
}
return containers;
}
public static Container findContainer(List containers, String name) {
for (Container container : containers) {
if (Objects.equal(container.getName(), name)) {
return container;
}
}
return null;
}
public static ReplicationControllerStatus getOrCreatetStatus(ReplicationController replicationController) {
ReplicationControllerStatus currentState = replicationController.getStatus();
if (currentState == null) {
currentState = new ReplicationControllerStatus();
replicationController.setStatus(currentState);
}
return currentState;
}
public static void deletePod(ProcessManager processManager, KubernetesModel model, String podId, String namespace) throws Exception {
Pod pod = model.deletePod(podId, namespace);
if (pod != null) {
List desiredContainers = NodeHelper.getOrCreatePodDesiredContainers(pod);
NodeHelper.deleteContainers(processManager, model, pod, NodeHelper.getOrCreatetStatus(pod), desiredContainers);
}
}
/**
* Performs a block of code and updates the pod model if its updated
*/
public static void podTransaction(KubernetesModel model, Pod pod, Runnable task) {
String oldJson = getPodJson(pod);
task.run();
String newJson = getPodJson(pod);
// lets only update the model if we've really changed the pod
if (!java.util.Objects.equals(oldJson, newJson)) {
model.updatePod(getName(pod), pod);
}
}
/**
* Performs a block of code and updates the pod model if its updated
*/
public static T podTransaction(KubernetesModel model, Pod pod, Callable task) throws Exception {
String oldJson = getPodJson(pod);
T answer = task.call();
String newJson = getPodJson(pod);
// lets only update the model if we've really changed the pod
if (!java.util.Objects.equals(oldJson, newJson)) {
model.updatePod(getName(pod), pod);
}
return answer;
}
/**
* Performs a block of code and updates the pod model if its updated
*/
public static void excludeFromProcessMonitor(ProcessMonitor monitor, Pod pod, Runnable task) {
String id = getName(pod);
monitor.addExcludedPodId(id);
try {
task.run();
} finally {
monitor.removeExcludedPodId(id);
}
}
/**
* Performs a block of code and updates the pod model if its updated
*/
public static T excludeFromProcessMonitor(ProcessMonitor monitor, Pod pod, Callable task) throws Exception {
String id = getName(pod);
monitor.addExcludedPodId(id);
try {
return task.call();
} finally {
monitor.removeExcludedPodId(id);
}
}
protected static String getPodJson(Pod pod) {
try {
return KubernetesHelper.toJson(pod);
} catch (JsonProcessingException e) {
LOG.warn("Could not convert pod to json: " + e, e);
return null;
}
}
/**
* Returns true if there has been a change in the JSON of the given entity
*/
public static boolean podHasChanged(Pod currentEntity, Pod oldEntity) {
if (currentEntity == null || oldEntity == null) {
return true;
}
String oldJson = getPodJson(oldEntity);
String newJson = getPodJson(currentEntity);
return !java.util.Objects.equals(oldJson, newJson);
}
public static void setPodTerminated(Pod pod, Exception failed) {
PodStatus currentState = getOrCreatetStatus(pod);
setPodTerminated(currentState, failed);
}
public static void setPodTerminated(PodStatus podStatus, Exception exception) {
setPodTerminated(podStatus, exception.getMessage());
}
public static void setPodTerminated(PodStatus podStatus, String message) {
List containerStatuses = podStatus.getContainerStatuses();
if (containerStatuses == null) {
containerStatuses = new ArrayList();
podStatus.setContainerStatuses(containerStatuses);
}
containerStatuses.clear();
ContainerStatus status = new ContainerStatusBuilder().withNewState().
withNewTermination().withMessage(message).withFinishedAt(createAtString()).endTermination().endState().
build();
containerStatuses.add(status);
podStatus.setContainerStatuses(containerStatuses);
}
public static String createAtString() {
return new Date().toString();
}
public static void setPodRunning(PodStatus podStatus) {
List containerStatuses = podStatus.getContainerStatuses();
if (containerStatuses == null) {
containerStatuses = new ArrayList();
podStatus.setContainerStatuses(containerStatuses);
}
containerStatuses.clear();
ContainerStatus status = new ContainerStatusBuilder().withNewState().
withNewRunning().withStartedAt(createAtString()).endRunning().endState().
build();
containerStatuses.add(status);
podStatus.setContainerStatuses(containerStatuses);
}
public static void setPodWaiting(Pod pod) {
PodStatus podStatus = getOrCreatetStatus(pod);
setPodWaiting(podStatus);
}
public static void setPodWaiting(PodStatus podStatus) {
setPodWaiting(podStatus, "Waiting for download");
}
public static void setPodWaiting(PodStatus podStatus, String reason) {
List containerStatuses = podStatus.getContainerStatuses();
if (containerStatuses == null) {
containerStatuses = new ArrayList();
podStatus.setContainerStatuses(containerStatuses);
}
containerStatuses.clear();
ContainerStatus status = new ContainerStatusBuilder().withNewState().
withNewWaiting().withReason(reason).endWaiting().endState().
build();
containerStatuses.add(status);
podStatus.setContainerStatuses(containerStatuses);
}
}