<commit_before>/*
 * Copyright 2013 Twitter, Inc. and other contributors.
 * 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 org.jenkinsci.plugins.mesos;

import hudson.EnvVars;
import hudson.Extension;
import hudson.init.InitMilestone;
import hudson.init.Initializer;
import hudson.model.*;
import hudson.slaves.Cloud;
import hudson.slaves.EnvironmentVariablesNodeProperty;
import hudson.slaves.NodeProperty;
import hudson.slaves.NodePropertyDescriptor;
import hudson.slaves.NodeProvisioner.PlannedNode;
import hudson.util.DescribableList;
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.apache.mesos.MesosNativeLibrary;
import org.jenkinsci.plugins.mesos.config.acl.MesosFrameworkToItemMapper;
import org.jenkinsci.plugins.mesos.config.slavedefinitions.MesosSlaveDefinitions;
import org.jenkinsci.plugins.mesos.config.slavedefinitions.MesosSlaveInfo;
import org.jenkinsci.plugins.mesos.config.slavedefinitions.SlaveDefinitionsConfiguration;
import org.jenkinsci.plugins.mesos.monitoring.MesosTaskFailureMonitor;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;

import javax.servlet.ServletException;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

public class MesosCloud extends Cloud {
  private String nativeLibraryPath;
  private String master;
  private String description;
  private String frameworkName;
  private String role;
  private String slavesUser;
  private String principal;
  private String secret;
  private final boolean checkpoint; // Set true to enable checkpointing. False by default.
  private boolean onDemandRegistration; // If set true, this framework disconnects when there are no builds in the queue and re-registers when there are.
  private String jenkinsURL;
  private String grafanaDashboardURL;

  // Find the default values for these variables in
  // src/main/resources/org/jenkinsci/plugins/mesos/MesosCloud/config.jelly.
  private String slaveDefinitionsName;

  private static final Logger LOGGER = Logger.getLogger(MesosCloud.class.getName());

  // We allocate 10% more memory to the Mesos task to account for the JVM overhead.
  private static final double JVM_MEM_OVERHEAD_FACTOR = 0.1;

  private static volatile boolean nativeLibraryLoaded = false;

  /**
   * We want to start the Mesos scheduler as part of the initialization of Jenkins
   * and after the cloud class values have been restored from persistence.If this is
   * the very first time, this method will be NOOP as MesosCloud is not registered yet.
   */

  @Initializer(after=InitMilestone.JOB_LOADED)
  public static void init() {
    Jenkins jenkins = Jenkins.getInstance();
    List<Node> slaves = jenkins.getNodes();

    // Turning the AUTOMATIC_SLAVE_LAUNCH flag off because the below slave removals
    // causes computer launch in other slaves that have not been removed yet.
    // To study how a slave removal updates the entire list, one can refer to
    // Hudson NodeProvisioner class and follow this method chain removeNode() ->
    // setNodes() -> updateComputerList() -> updateComputer().
    Jenkins.AUTOMATIC_SLAVE_LAUNCH = false;
    for (Node n : slaves) {
      //Remove all slaves that were persisted when Jenkins shutdown.
      if (n instanceof MesosSlave) {
        ((MesosSlave)n).terminate();
      }
    }

    // Turn it back on for future real slaves.
    Jenkins.AUTOMATIC_SLAVE_LAUNCH = true;

    for (Cloud c : jenkins.clouds) {
      if( c instanceof MesosCloud) {
        // Register mesos framework on init, if on demand registration is not enabled.
        if (!((MesosCloud) c).isOnDemandRegistration()) {
          ((MesosCloud)c).restartMesos();
        }
      }
    }
  }

  @DataBoundConstructor
  public MesosCloud(
      String nativeLibraryPath,
      String master,
      String description,
      String frameworkName,
      String role,
      String slavesUser,
      String principal,
      String secret,
      String slaveDefinitionsName,
      boolean checkpoint,
      boolean onDemandRegistration,
      String jenkinsURL,
      String grafanaDashboardURL) throws NumberFormatException {
    super("MesosCloud");

    this.nativeLibraryPath = nativeLibraryPath;
    this.master = master;
    this.description = description;
    this.frameworkName = frameworkName;
    this.role = role;
    this.slavesUser = slavesUser;
    this.principal = principal;
    this.secret = secret;
    this.slaveDefinitionsName = slaveDefinitionsName;
    this.checkpoint = checkpoint;
    this.onDemandRegistration = onDemandRegistration;
    this.setJenkinsURL(jenkinsURL);
    this.grafanaDashboardURL = grafanaDashboardURL;
    if(!onDemandRegistration) {
	    JenkinsScheduler.SUPERVISOR_LOCK.lock();
	    try {
	      restartMesos();
	    } finally {
	      JenkinsScheduler.SUPERVISOR_LOCK.unlock();
	    }
    }
  }

  // Since MesosCloud is used as a key to a Hashmap, we need to set equals/hashcode
  // or lookups won't work if any fields are changed.  Use master string as the key since
  // the rest of this code assumes it is unique among the Cloud objects.
  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    MesosCloud that = (MesosCloud) o;

    if (master != null ? !master.equals(that.master) : that.master != null) return false;
    if (frameworkName != null ? !frameworkName.equals(that.frameworkName) : that.frameworkName != null) return false;

    return true;
  }

  @Override
  public int hashCode() {
    return master != null ? master.hashCode() : 0;
  }

  public void restartMesos() {
    restartMesos(false);
  }

  public void restartMesos(boolean forceRestart) {

    if(!nativeLibraryLoaded) {
      // First, we attempt to load the library from the given path.
      // If unsuccessful, we attempt to load using 'MesosNativeLibrary.load()'.
      try {
          MesosNativeLibrary.load(nativeLibraryPath);
      } catch (UnsatisfiedLinkError error) {
          LOGGER.warning("Failed to load native Mesos library from '" + nativeLibraryPath +
                         "': " + error.getMessage());
          MesosNativeLibrary.load();
      }
      nativeLibraryLoaded = true;
    }

    // Default to root URL in Jenkins global configuration.
    String jenkinsRootURL = Jenkins.getInstance().getRootUrl();

    // If 'jenkinsURL' parameter is provided in mesos plugin configuration, then that should take precedence.
    if(StringUtils.isNotBlank(jenkinsURL)) {
      jenkinsRootURL = expandJenkinsUrlWithEnvVars();
    }

    // Restart the scheduler if the master has changed or a scheduler is not up. or it is forced to be
    if (!Mesos.getInstance(this).isSchedulerRunning() || forceRestart) {
      if (forceRestart) {
        LOGGER.info("Force the scheduler to restart");
      } else {
        LOGGER.info("Scheduler was down, restarting the scheduler");
      }

      Mesos.getInstance(this).stopScheduler();
      Mesos.getInstance(this).startScheduler(jenkinsRootURL, this);
      requestAMesosSlaveForEveryBuildableItemInQueue();
    } else {
      Mesos.getInstance(this).updateScheduler(jenkinsRootURL, this);
      LOGGER.info("Mesos master has not changed, leaving the scheduler running");
    }

  }

  public void requestAMesosSlaveForEveryBuildableItemInQueue() {
    Jenkins jenkins = Jenkins.getInstance();
    for(Queue.BuildableItem buildableItem : jenkins.getQueue().getBuildableItems()) {
      Label label = buildableItem.getAssignedLabel();
      if(canProvision(label) && this.isItemForMyFramework(buildableItem)) {
        this.requestNodes(label, 1, getFullNameOfItem(buildableItem));
      }
    }
  }

  private String expandJenkinsUrlWithEnvVars() {
    Jenkins instance = Jenkins.getInstance();
    DescribableList<NodeProperty<?>, NodePropertyDescriptor> globalNodeProperties = instance.getGlobalNodeProperties();
    if (globalNodeProperties == null) {
      return jenkinsURL;
    }
    EnvironmentVariablesNodeProperty environmentVariablesNodeProperty = globalNodeProperties.get(EnvironmentVariablesNodeProperty.class);
    if (environmentVariablesNodeProperty == null) {
      return jenkinsURL;
    }
    EnvVars envVars = environmentVariablesNodeProperty.getEnvVars();
    if (envVars != null) {
      return StringUtils.defaultIfBlank(envVars.expand(this.jenkinsURL),instance.getRootUrl());
    } else {
      return jenkinsURL;
    }
  }

  @Override
  public Collection<PlannedNode> provision(Label label, int excessWorkload) {
    try {
      MesosSlaveInfo mesosSlaveInfo = getSlaveInfo(label);

      if (mesosSlaveInfo != null) {
        if (!mesosSlaveInfo.isUseSlaveOnce()) {
          Queue queue = Queue.getInstance();
          int buildablesInQueueCount = queue.countBuildableItemsFor(label);

          //we need the scheduler to get known requests for the label.
          JenkinsScheduler jenkinsScheduler = (JenkinsScheduler) Mesos.getInstance(this).getScheduler();
          //our "softlimit" for provisioning slaves. Jenkins shouldn't provision more slaves than needet.
          int requestsForLabelCount = jenkinsScheduler.getRequestsMatchingLabel(label).size();

          int legalWorkload = Math.min(excessWorkload, (buildablesInQueueCount - requestsForLabelCount));

          if (legalWorkload > 0) {
            requestNodes(label, legalWorkload, null);
          } else {
            LOGGER.info("Ignore Jenkins provisioning request. There are enough requests to mesos (legalWorkLoad: " + legalWorkload+")");
          }
        }
      }
    } catch (Exception e) {
      LOGGER.fine("Error while jenkins tried to provision a Slave for "+label.getDisplayName());
      e.printStackTrace();
    }

    return new ArrayList<PlannedNode>();
  }

  public void requestNodes(Label label, int excessWorkload, String linkedItem) {
    List<MesosSlaveInfo> slaveInfos = getSlaveInfos();
    final MesosSlaveInfo slaveInfo = getSlaveInfo(slaveInfos, label);

    try {
      while (excessWorkload > 0 && !Jenkins.getInstance().isQuietingDown())  {
        // Start the scheduler if it's not already running.
        if (onDemandRegistration) {
          JenkinsScheduler.SUPERVISOR_LOCK.lock();
          try {
            LOGGER.fine("Checking if scheduler is running");
            if (!Mesos.getInstance(this).isSchedulerRunning()) {
              restartMesos();
            }
          } finally {
            JenkinsScheduler.SUPERVISOR_LOCK.unlock();
          }
        }
        final int numExecutors = Math.min(excessWorkload, slaveInfo.getMaxExecutors());
        excessWorkload -= numExecutors;
        LOGGER.info("Provisioning Jenkins Slave on Mesos with " + numExecutors +
                    " executors. Remaining excess workload: " + excessWorkload + " executors)");


        sendSlaveRequest(numExecutors, slaveInfo, linkedItem);
      }
    } catch (Exception e) {
      LOGGER.log(Level.WARNING, "Failed to create instances on Mesos", e);
    }
  }

  private void sendSlaveRequest(int numExecutors, MesosSlaveInfo slaveInfo, String linkedItem) throws Descriptor.FormException, IOException {
    String name = slaveInfo.getLabelString() + "-" + UUID.randomUUID().toString();
    double cpus = slaveInfo.getSlaveCpus() + (numExecutors * slaveInfo.getExecutorCpus());
    int memory = (int)((slaveInfo.getSlaveMem() + (numExecutors * slaveInfo.getExecutorMem())) * (1 + JVM_MEM_OVERHEAD_FACTOR));


    Mesos.JenkinsSlave jenkinsSlave = new Mesos.JenkinsSlave(name,slaveInfo.getLabelString(), numExecutors, linkedItem);
    Mesos.SlaveRequest slaveRequest = new Mesos.SlaveRequest(jenkinsSlave, cpus, memory, role, slaveInfo);
    Mesos mesos = Mesos.getInstance(this);

    mesos.startJenkinsSlave(slaveRequest, new Mesos.SlaveResult() {
      @Override
      public void running(Mesos.JenkinsSlave slave) {

      }

      @Override
      public void finished(Mesos.JenkinsSlave slave) {
        //yeah, the task finishe. so we can remove the slave from Jenkins instance
        LOGGER.info(String.format("remove finished Node %s from Jenkins", slave.getName()));
        removeSlaveFromJenkins(slave);
      }

      @Override
      public void failed(Mesos.JenkinsSlave slave) {
        try {
          MesosTaskFailureMonitor.getInstance().addFailedSlave(slave);
        } catch (Exception e) {
          LOGGER.fine("Error while getting MesosTaskFailureMonitor " + e.getMessage());
          e.printStackTrace();
        }
      }
    });
  }

  private void removeSlaveFromJenkins(Mesos.JenkinsSlave slave) {
    Jenkins jenkins = Jenkins.getInstance();
    Node n = jenkins.getNode(slave.getName());
    if(n != null) {
      Computer computer = n.toComputer();
      if(computer instanceof MesosComputer) {
        MesosComputer mesosComputer = (MesosComputer) computer;
        mesosComputer.deleteSlave();
      }
    }
  }

  public List<MesosSlaveInfo> getSlaveInfos() {
    return SlaveDefinitionsConfiguration.getDescriptorImplStatic().getSlaveInfos(slaveDefinitionsName);
  }

  public boolean isItemForMyFramework(Queue.BuildableItem buildableItem) {
    return canProvision(buildableItem.getAssignedLabel()) && isItemForMyFramework(getFullNameOfItem(buildableItem));
  }

  public boolean isItemForMyFramework(String buildableItem) {
      //MesosFrameworkToItemMapper mesosFrameworkToItemMapper = new MesosFrameworkToItemMapper();
    MesosFrameworkToItemMapper.DescriptorImpl descriptor =
        (MesosFrameworkToItemMapper.DescriptorImpl)Jenkins.getInstance().getDescriptorOrDie(MesosFrameworkToItemMapper.class);
    String foundFramework = descriptor.findFrameworkName(buildableItem);
    return StringUtils.equals(frameworkName, foundFramework);
  }

  public String getFullNameOfItem(Queue.BuildableItem buildableItem) {
    if(buildableItem == null || !(buildableItem.task instanceof Item)) {
      throw new IllegalArgumentException(Messages.MesosCloud_InvalidItem(buildableItem));
    }

    return ((Item) buildableItem.task).getFullName();
  }

  @Override
  public boolean canProvision(Label label) {
    // Provisioning is simply creating a task for a jenkins slave.
    // We can provision a Mesos slave as long as the job's label matches any
    // item in the list of configured Mesos labels.
    // TODO(vinod): The framework may not have the resources necessary
    // to start a task when it comes time to launch the slave.
    List<MesosSlaveInfo> slaveInfos = getSlaveInfos();

    if (label != null && slaveInfos != null) {
      for (MesosSlaveInfo slaveInfo : slaveInfos) {
        if (label.matches(Label.parse(slaveInfo.getLabelString()))) {
          return true;
        }
      }
    }
    return false;
  }
<commit_msg>Add Jenkins SubTask Support for Pipeline Jobs<commit_after>/*
 * Copyright 2013 Twitter, Inc. and other contributors.
 * 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 org.jenkinsci.plugins.mesos;

import hudson.EnvVars;
import hudson.Extension;
import hudson.init.InitMilestone;
import hudson.init.Initializer;
import hudson.model.*;
import hudson.model.queue.SubTask;
import hudson.slaves.Cloud;
import hudson.slaves.EnvironmentVariablesNodeProperty;
import hudson.slaves.NodeProperty;
import hudson.slaves.NodePropertyDescriptor;
import hudson.slaves.NodeProvisioner.PlannedNode;
import hudson.util.DescribableList;
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.apache.mesos.MesosNativeLibrary;
import org.jenkinsci.plugins.mesos.config.acl.MesosFrameworkToItemMapper;
import org.jenkinsci.plugins.mesos.config.slavedefinitions.MesosSlaveDefinitions;
import org.jenkinsci.plugins.mesos.config.slavedefinitions.MesosSlaveInfo;
import org.jenkinsci.plugins.mesos.config.slavedefinitions.SlaveDefinitionsConfiguration;
import org.jenkinsci.plugins.mesos.monitoring.MesosTaskFailureMonitor;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;

import javax.servlet.ServletException;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

public class MesosCloud extends Cloud {
  private String nativeLibraryPath;
  private String master;
  private String description;
  private String frameworkName;
  private String role;
  private String slavesUser;
  private String principal;
  private String secret;
  private final boolean checkpoint; // Set true to enable checkpointing. False by default.
  private boolean onDemandRegistration; // If set true, this framework disconnects when there are no builds in the queue and re-registers when there are.
  private String jenkinsURL;
  private String grafanaDashboardURL;

  // Find the default values for these variables in
  // src/main/resources/org/jenkinsci/plugins/mesos/MesosCloud/config.jelly.
  private String slaveDefinitionsName;

  private static final Logger LOGGER = Logger.getLogger(MesosCloud.class.getName());

  // We allocate 10% more memory to the Mesos task to account for the JVM overhead.
  private static final double JVM_MEM_OVERHEAD_FACTOR = 0.1;

  private static volatile boolean nativeLibraryLoaded = false;

  /**
   * We want to start the Mesos scheduler as part of the initialization of Jenkins
   * and after the cloud class values have been restored from persistence.If this is
   * the very first time, this method will be NOOP as MesosCloud is not registered yet.
   */

  @Initializer(after=InitMilestone.JOB_LOADED)
  public static void init() {
    Jenkins jenkins = Jenkins.getInstance();
    List<Node> slaves = jenkins.getNodes();

    // Turning the AUTOMATIC_SLAVE_LAUNCH flag off because the below slave removals
    // causes computer launch in other slaves that have not been removed yet.
    // To study how a slave removal updates the entire list, one can refer to
    // Hudson NodeProvisioner class and follow this method chain removeNode() ->
    // setNodes() -> updateComputerList() -> updateComputer().
    Jenkins.AUTOMATIC_SLAVE_LAUNCH = false;
    for (Node n : slaves) {
      //Remove all slaves that were persisted when Jenkins shutdown.
      if (n instanceof MesosSlave) {
        ((MesosSlave)n).terminate();
      }
    }

    // Turn it back on for future real slaves.
    Jenkins.AUTOMATIC_SLAVE_LAUNCH = true;

    for (Cloud c : jenkins.clouds) {
      if( c instanceof MesosCloud) {
        // Register mesos framework on init, if on demand registration is not enabled.
        if (!((MesosCloud) c).isOnDemandRegistration()) {
          ((MesosCloud)c).restartMesos();
        }
      }
    }
  }

  @DataBoundConstructor
  public MesosCloud(
      String nativeLibraryPath,
      String master,
      String description,
      String frameworkName,
      String role,
      String slavesUser,
      String principal,
      String secret,
      String slaveDefinitionsName,
      boolean checkpoint,
      boolean onDemandRegistration,
      String jenkinsURL,
      String grafanaDashboardURL) throws NumberFormatException {
    super("MesosCloud");

    this.nativeLibraryPath = nativeLibraryPath;
    this.master = master;
    this.description = description;
    this.frameworkName = frameworkName;
    this.role = role;
    this.slavesUser = slavesUser;
    this.principal = principal;
    this.secret = secret;
    this.slaveDefinitionsName = slaveDefinitionsName;
    this.checkpoint = checkpoint;
    this.onDemandRegistration = onDemandRegistration;
    this.setJenkinsURL(jenkinsURL);
    this.grafanaDashboardURL = grafanaDashboardURL;
    if(!onDemandRegistration) {
	    JenkinsScheduler.SUPERVISOR_LOCK.lock();
	    try {
	      restartMesos();
	    } finally {
	      JenkinsScheduler.SUPERVISOR_LOCK.unlock();
	    }
    }
  }

  // Since MesosCloud is used as a key to a Hashmap, we need to set equals/hashcode
  // or lookups won't work if any fields are changed.  Use master string as the key since
  // the rest of this code assumes it is unique among the Cloud objects.
  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    MesosCloud that = (MesosCloud) o;

    if (master != null ? !master.equals(that.master) : that.master != null) return false;
    if (frameworkName != null ? !frameworkName.equals(that.frameworkName) : that.frameworkName != null) return false;

    return true;
  }

  @Override
  public int hashCode() {
    return master != null ? master.hashCode() : 0;
  }

  public void restartMesos() {
    restartMesos(false);
  }

  public void restartMesos(boolean forceRestart) {

    if(!nativeLibraryLoaded) {
      // First, we attempt to load the library from the given path.
      // If unsuccessful, we attempt to load using 'MesosNativeLibrary.load()'.
      try {
          MesosNativeLibrary.load(nativeLibraryPath);
      } catch (UnsatisfiedLinkError error) {
          LOGGER.warning("Failed to load native Mesos library from '" + nativeLibraryPath +
                         "': " + error.getMessage());
          MesosNativeLibrary.load();
      }
      nativeLibraryLoaded = true;
    }

    // Default to root URL in Jenkins global configuration.
    String jenkinsRootURL = Jenkins.getInstance().getRootUrl();

    // If 'jenkinsURL' parameter is provided in mesos plugin configuration, then that should take precedence.
    if(StringUtils.isNotBlank(jenkinsURL)) {
      jenkinsRootURL = expandJenkinsUrlWithEnvVars();
    }

    // Restart the scheduler if the master has changed or a scheduler is not up. or it is forced to be
    if (!Mesos.getInstance(this).isSchedulerRunning() || forceRestart) {
      if (forceRestart) {
        LOGGER.info("Force the scheduler to restart");
      } else {
        LOGGER.info("Scheduler was down, restarting the scheduler");
      }

      Mesos.getInstance(this).stopScheduler();
      Mesos.getInstance(this).startScheduler(jenkinsRootURL, this);
      requestAMesosSlaveForEveryBuildableItemInQueue();
    } else {
      Mesos.getInstance(this).updateScheduler(jenkinsRootURL, this);
      LOGGER.info("Mesos master has not changed, leaving the scheduler running");
    }

  }

  public void requestAMesosSlaveForEveryBuildableItemInQueue() {
    Jenkins jenkins = Jenkins.getInstance();
    for(Queue.BuildableItem buildableItem : jenkins.getQueue().getBuildableItems()) {
      Label label = buildableItem.getAssignedLabel();
      if(canProvision(label) && this.isItemForMyFramework(buildableItem)) {
        this.requestNodes(label, 1, getFullNameOfItem(buildableItem));
      }
    }
  }

  private String expandJenkinsUrlWithEnvVars() {
    Jenkins instance = Jenkins.getInstance();
    DescribableList<NodeProperty<?>, NodePropertyDescriptor> globalNodeProperties = instance.getGlobalNodeProperties();
    if (globalNodeProperties == null) {
      return jenkinsURL;
    }
    EnvironmentVariablesNodeProperty environmentVariablesNodeProperty = globalNodeProperties.get(EnvironmentVariablesNodeProperty.class);
    if (environmentVariablesNodeProperty == null) {
      return jenkinsURL;
    }
    EnvVars envVars = environmentVariablesNodeProperty.getEnvVars();
    if (envVars != null) {
      return StringUtils.defaultIfBlank(envVars.expand(this.jenkinsURL),instance.getRootUrl());
    } else {
      return jenkinsURL;
    }
  }

  @Override
  public Collection<PlannedNode> provision(Label label, int excessWorkload) {
    try {
      MesosSlaveInfo mesosSlaveInfo = getSlaveInfo(label);

      if (mesosSlaveInfo != null) {
        if (!mesosSlaveInfo.isUseSlaveOnce()) {
          Queue queue = Queue.getInstance();
          int buildablesInQueueCount = queue.countBuildableItemsFor(label);

          //we need the scheduler to get known requests for the label.
          JenkinsScheduler jenkinsScheduler = (JenkinsScheduler) Mesos.getInstance(this).getScheduler();
          //our "softlimit" for provisioning slaves. Jenkins shouldn't provision more slaves than needet.
          int requestsForLabelCount = jenkinsScheduler.getRequestsMatchingLabel(label).size();

          int legalWorkload = Math.min(excessWorkload, (buildablesInQueueCount - requestsForLabelCount));

          if (legalWorkload > 0) {
            requestNodes(label, legalWorkload, null);
          } else {
            LOGGER.info("Ignore Jenkins provisioning request. There are enough requests to mesos (legalWorkLoad: " + legalWorkload+")");
          }
        }
      }
    } catch (Exception e) {
      LOGGER.fine("Error while jenkins tried to provision a Slave for "+label.getDisplayName());
      e.printStackTrace();
    }

    return new ArrayList<PlannedNode>();
  }

  public void requestNodes(Label label, int excessWorkload, String linkedItem) {
    List<MesosSlaveInfo> slaveInfos = getSlaveInfos();
    final MesosSlaveInfo slaveInfo = getSlaveInfo(slaveInfos, label);

    try {
      while (excessWorkload > 0 && !Jenkins.getInstance().isQuietingDown())  {
        // Start the scheduler if it's not already running.
        if (onDemandRegistration) {
          JenkinsScheduler.SUPERVISOR_LOCK.lock();
          try {
            LOGGER.fine("Checking if scheduler is running");
            if (!Mesos.getInstance(this).isSchedulerRunning()) {
              restartMesos();
            }
          } finally {
            JenkinsScheduler.SUPERVISOR_LOCK.unlock();
          }
        }
        final int numExecutors = Math.min(excessWorkload, slaveInfo.getMaxExecutors());
        excessWorkload -= numExecutors;
        LOGGER.info("Provisioning Jenkins Slave on Mesos with " + numExecutors +
                    " executors. Remaining excess workload: " + excessWorkload + " executors)");


        sendSlaveRequest(numExecutors, slaveInfo, linkedItem);
      }
    } catch (Exception e) {
      LOGGER.log(Level.WARNING, "Failed to create instances on Mesos", e);
    }
  }

  private void sendSlaveRequest(int numExecutors, MesosSlaveInfo slaveInfo, String linkedItem) throws Descriptor.FormException, IOException {
    String name = slaveInfo.getLabelString() + "-" + UUID.randomUUID().toString();
    double cpus = slaveInfo.getSlaveCpus() + (numExecutors * slaveInfo.getExecutorCpus());
    int memory = (int)((slaveInfo.getSlaveMem() + (numExecutors * slaveInfo.getExecutorMem())) * (1 + JVM_MEM_OVERHEAD_FACTOR));


    Mesos.JenkinsSlave jenkinsSlave = new Mesos.JenkinsSlave(name,slaveInfo.getLabelString(), numExecutors, linkedItem);
    Mesos.SlaveRequest slaveRequest = new Mesos.SlaveRequest(jenkinsSlave, cpus, memory, role, slaveInfo);
    Mesos mesos = Mesos.getInstance(this);

    mesos.startJenkinsSlave(slaveRequest, new Mesos.SlaveResult() {
      @Override
      public void running(Mesos.JenkinsSlave slave) {

      }

      @Override
      public void finished(Mesos.JenkinsSlave slave) {
        //yeah, the task finishe. so we can remove the slave from Jenkins instance
        LOGGER.info(String.format("remove finished Node %s from Jenkins", slave.getName()));
        removeSlaveFromJenkins(slave);
      }

      @Override
      public void failed(Mesos.JenkinsSlave slave) {
        try {
          MesosTaskFailureMonitor.getInstance().addFailedSlave(slave);
        } catch (Exception e) {
          LOGGER.fine("Error while getting MesosTaskFailureMonitor " + e.getMessage());
          e.printStackTrace();
        }
      }
    });
  }

  private void removeSlaveFromJenkins(Mesos.JenkinsSlave slave) {
    Jenkins jenkins = Jenkins.getInstance();
    Node n = jenkins.getNode(slave.getName());
    if(n != null) {
      Computer computer = n.toComputer();
      if(computer instanceof MesosComputer) {
        MesosComputer mesosComputer = (MesosComputer) computer;
        mesosComputer.deleteSlave();
      }
    }
  }

  public List<MesosSlaveInfo> getSlaveInfos() {
    return SlaveDefinitionsConfiguration.getDescriptorImplStatic().getSlaveInfos(slaveDefinitionsName);
  }

  public boolean isItemForMyFramework(Queue.BuildableItem buildableItem) {
    return canProvision(buildableItem.getAssignedLabel()) && isItemForMyFramework(getFullNameOfItem(buildableItem));
  }

  public boolean isItemForMyFramework(String buildableItem) {
      //MesosFrameworkToItemMapper mesosFrameworkToItemMapper = new MesosFrameworkToItemMapper();
    MesosFrameworkToItemMapper.DescriptorImpl descriptor =
        (MesosFrameworkToItemMapper.DescriptorImpl)Jenkins.getInstance().getDescriptorOrDie(MesosFrameworkToItemMapper.class);
    String foundFramework = descriptor.findFrameworkName(buildableItem);
    return StringUtils.equals(frameworkName, foundFramework);
  }

  public String getFullNameOfItem(Queue.BuildableItem buildableItem) {

    if (buildableItem != null) {
      Queue.Task task = buildableItem.task;

      if (task instanceof Item) {
        return ((Item) task).getFullName();
      } else if (task instanceof SubTask) {
        return ((Item) task.getOwnerTask()).getFullName();
      }
    }

    throw new IllegalArgumentException(Messages.MesosCloud_InvalidItem(buildableItem));
  }

  @Override
  public boolean canProvision(Label label) {
    // Provisioning is simply creating a task for a jenkins slave.
    // We can provision a Mesos slave as long as the job's label matches any
    // item in the list of configured Mesos labels.
    // TODO(vinod): The framework may not have the resources necessary
    // to start a task when it comes time to launch the slave.
    List<MesosSlaveInfo> slaveInfos = getSlaveInfos();

    if (label != null && slaveInfos != null) {
      for (MesosSlaveInfo slaveInfo : slaveInfos) {
        if (label.matches(Label.parse(slaveInfo.getLabelString()))) {
          return true;
        }
      }
    }
    return false;
  }

{'fraction_non_alphanumeric': 0.08985969387755102, 'fraction_numerical': 0.0010841836734693877, 'mean_word_length': 3.520830330113882, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1459755', 'n_tokens_mistral': 9091, 'n_tokens_neox': 8853, 'n_words': 2353}