package org.csanchez.jenkins.plugins.kubernetes; import java.io.IOException; import java.util.Collections; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.lang.StringUtils; import org.jenkinsci.plugins.durabletask.executors.OnceRetentionStrategy; import org.jvnet.localizer.Localizable; import org.jvnet.localizer.ResourceBundleHolder; import org.kohsuke.stapler.DataBoundConstructor; import hudson.Extension; import hudson.model.Computer; import hudson.model.Descriptor; import hudson.model.Label; import hudson.model.Node; import hudson.model.TaskListener; import hudson.slaves.AbstractCloudSlave; import hudson.slaves.Cloud; import hudson.slaves.JNLPLauncher; import hudson.slaves.OfflineCause; import hudson.slaves.RetentionStrategy; import io.fabric8.kubernetes.api.model.DoneablePod; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.dsl.ClientPodResource; import jenkins.model.Jenkins; /** * @author Carlos Sanchez carlos@apache.org */ public class KubernetesSlave extends AbstractCloudSlave { private static final Logger LOGGER = Logger.getLogger(KubernetesSlave.class.getName()); private static final long serialVersionUID = -8642936855413034232L; /** * The resource bundle reference */ private final static ResourceBundleHolder HOLDER = ResourceBundleHolder.get(Messages.class); private final String cloudName; public KubernetesSlave(PodTemplate template, String nodeDescription, KubernetesCloud cloud, String labelStr) throws Descriptor.FormException, IOException { this(template, nodeDescription, cloud, labelStr, new OnceRetentionStrategy(cloud.getRetentionTimeout())); } @Deprecated public KubernetesSlave(PodTemplate template, String nodeDescription, KubernetesCloud cloud, Label label) throws Descriptor.FormException, IOException { this(template, nodeDescription, cloud, label.toString(), new OnceRetentionStrategy(cloud.getRetentionTimeout())) ; } @Deprecated public KubernetesSlave(PodTemplate template, String nodeDescription, KubernetesCloud cloud, String labelStr, RetentionStrategy rs) throws Descriptor.FormException, IOException { this(template, nodeDescription, cloud.name, labelStr, rs); } @DataBoundConstructor public KubernetesSlave(PodTemplate template, String nodeDescription, String cloudName, String labelStr, RetentionStrategy rs) throws Descriptor.FormException, IOException { super(getSlaveName(template), nodeDescription, template.getRemoteFs(), 1, Node.Mode.NORMAL, labelStr == null ? null : labelStr, new JNLPLauncher(), rs, template.getNodeProperties()); // this.pod = pod; this.cloudName = cloudName; } static String getSlaveName(PodTemplate template) { String hex = Long.toHexString(System.nanoTime()); String name = template.getName(); if (StringUtils.isEmpty(name)) { return hex; } // no spaces name = template.getName().replace(" ", "-").toLowerCase(); // keep it under 256 chars name = name.substring(0, Math.min(name.length(), 256 - hex.length())); return String.format("%s-%s", name, hex); } @Override public KubernetesComputer createComputer() { return new KubernetesComputer(this); } @Override protected void _terminate(TaskListener listener) throws IOException, InterruptedException { LOGGER.log(Level.INFO, "Terminating Kubernetes instance for slave {0}", name); Computer computer = toComputer(); if (computer == null) { String msg = String.format("Computer for slave is null: %s", name); LOGGER.log(Level.SEVERE, msg); listener.fatalError(msg); return; } if (cloudName == null) { String msg = String.format("Cloud name is not set for slave, can't terminate: %s", name); LOGGER.log(Level.SEVERE, msg); listener.fatalError(msg); return; } try { Cloud cloud = Jenkins.getInstance().getCloud(cloudName); if (cloud == null) { String msg = String.format("Slave cloud no longer exists: %s", cloudName); LOGGER.log(Level.WARNING, msg); listener.fatalError(msg); return; } if (!(cloud instanceof KubernetesCloud)) { String msg = String.format("Slave cloud is not a KubernetesCloud, something is very wrong: %s", cloudName); LOGGER.log(Level.SEVERE, msg); listener.fatalError(msg); return; } KubernetesClient client = ((KubernetesCloud) cloud).connect(); ClientPodResource<Pod, DoneablePod> pods = client.pods().withName(name); pods.delete(); String msg = String.format("Terminated Kubernetes instance for slave %s", name); LOGGER.log(Level.INFO, msg); listener.getLogger().println(msg); computer.disconnect(OfflineCause.create(new Localizable(HOLDER, "offline"))); LOGGER.log(Level.INFO, "Disconnected computer {0}", name); } catch (Exception e) { LOGGER.log(Level.SEVERE, "Failed to terminate pod for slave " + name, e); } } @Override public String toString() { return String.format("KubernetesSlave name: %s", name); } @Extension public static final class DescriptorImpl extends SlaveDescriptor { @Override public String getDisplayName() { return "Kubernetes Slave"; }; @Override public boolean isInstantiable() { return false; } } }