Exemplo n.º 1
0
  /**
   * Determines whether the AMI of the given instance matches the AMI of template and has the
   * required label (if requiredLabel is non-null)
   */
  private boolean checkInstance(
      PrintStream logger,
      Instance existingInstance,
      Label requiredLabel,
      EC2AbstractSlave[] returnNode) {
    logProvision(logger, "checkInstance: " + existingInstance);
    if (StringUtils.isNotBlank(getIamInstanceProfile())) {
      if (existingInstance.getIamInstanceProfile() != null) {
        if (!existingInstance.getIamInstanceProfile().getArn().equals(getIamInstanceProfile())) {
          logProvision(logger, " false - IAM Instance profile does not match");
          return false;
        }
        // Match, fall through
      } else {
        logProvision(logger, " false - Null IAM Instance profile");
        return false;
      }
    }

    if (existingInstance
            .getState()
            .getName()
            .equalsIgnoreCase(InstanceStateName.Terminated.toString())
        || existingInstance
            .getState()
            .getName()
            .equalsIgnoreCase(InstanceStateName.ShuttingDown.toString())) {
      logProvision(logger, " false - Instance is terminated or shutting down");
      return false;
    }
    // See if we know about this and it has capacity
    for (EC2AbstractSlave node : NodeIterator.nodes(EC2AbstractSlave.class)) {
      if (node.getInstanceId().equals(existingInstance.getInstanceId())) {
        logProvision(logger, "Found existing corresponding Jenkins slave: " + node.getInstanceId());
        if (!node.toComputer().isPartiallyIdle()) {
          logProvision(logger, " false - Node is not partially idle");
          return false;
        }
        // REMOVEME - this was added to force provision to work, but might not allow
        // stopped instances to be found - need to investigate further
        else if (false && node.toComputer().isOffline()) {
          logProvision(logger, " false - Node is offline");
          return false;
        } else if (requiredLabel != null && !requiredLabel.matches(node.getAssignedLabels())) {
          logProvision(logger, " false - we need a Node having label " + requiredLabel);
          return false;
        } else {
          logProvision(logger, " true - Node has capacity - can use it");
          returnNode[0] = node;
          return true;
        }
      }
    }
    logProvision(logger, " true - Instance has no node, but can be used");
    return true;
  }
Exemplo n.º 2
0
 protected EC2OndemandSlave newOndemandSlave(Instance inst) throws FormException, IOException {
   return new EC2OndemandSlave(
       inst.getInstanceId(),
       description,
       remoteFS,
       getNumExecutors(),
       labels,
       mode,
       initScript,
       tmpDir,
       remoteAdmin,
       jvmopts,
       stopOnTerminate,
       idleTerminationMinutes,
       inst.getPublicDnsName(),
       inst.getPrivateDnsName(),
       EC2Tag.fromAmazonTags(inst.getTags()),
       parent.name,
       usePrivateDnsName,
       useDedicatedTenancy,
       getLaunchTimeout(),
       amiType);
 }
Exemplo n.º 3
0
  /**
   * Provisions an On-demand EC2 slave by launching a new instance or starting a previously-stopped
   * instance.
   */
  private EC2AbstractSlave provisionOndemand(
      TaskListener listener, Label requiredLabel, EnumSet<ProvisionOptions> provisionOptions)
      throws AmazonClientException, IOException {
    PrintStream logger = listener.getLogger();
    AmazonEC2 ec2 = getParent().connect();

    try {
      logProvisionInfo(logger, "Considering launching " + ami + " for template " + description);

      KeyPair keyPair = getKeyPair(ec2);

      RunInstancesRequest riRequest = new RunInstancesRequest(ami, 1, 1);
      InstanceNetworkInterfaceSpecification net = new InstanceNetworkInterfaceSpecification();

      riRequest.setEbsOptimized(ebsOptimized);

      if (useEphemeralDevices) {
        setupEphemeralDeviceMapping(riRequest);
      } else {
        setupCustomDeviceMapping(riRequest);
      }

      if (stopOnTerminate) {
        riRequest.setInstanceInitiatedShutdownBehavior(ShutdownBehavior.Stop);
        logProvisionInfo(
            logger, "Setting Instance Initiated Shutdown Behavior : ShutdownBehavior.Stop");
      } else {
        riRequest.setInstanceInitiatedShutdownBehavior(ShutdownBehavior.Terminate);
        logProvisionInfo(
            logger, "Setting Instance Initiated Shutdown Behavior : ShutdownBehavior.Terminate");
      }

      List<Filter> diFilters = new ArrayList<Filter>();
      diFilters.add(new Filter("image-id").withValues(ami));

      if (StringUtils.isNotBlank(getZone())) {
        Placement placement = new Placement(getZone());
        if (getUseDedicatedTenancy()) {
          placement.setTenancy("dedicated");
        }
        riRequest.setPlacement(placement);
        diFilters.add(new Filter("availability-zone").withValues(getZone()));
      }

      if (StringUtils.isNotBlank(getSubnetId())) {
        if (getAssociatePublicIp()) {
          net.setSubnetId(getSubnetId());
        } else {
          riRequest.setSubnetId(getSubnetId());
        }

        diFilters.add(new Filter("subnet-id").withValues(getSubnetId()));

        /*
         * If we have a subnet ID then we can only use VPC security groups
         */
        if (!securityGroupSet.isEmpty()) {
          List<String> groupIds = getEc2SecurityGroups(ec2);

          if (!groupIds.isEmpty()) {
            if (getAssociatePublicIp()) {
              net.setGroups(groupIds);
            } else {
              riRequest.setSecurityGroupIds(groupIds);
            }

            diFilters.add(new Filter("instance.group-id").withValues(groupIds));
          }
        }
      } else {
        /* No subnet: we can use standard security groups by name */
        riRequest.setSecurityGroups(securityGroupSet);
        if (!securityGroupSet.isEmpty()) {
          diFilters.add(new Filter("instance.group-name").withValues(securityGroupSet));
        }
      }

      String userDataString = Base64.encodeBase64String(userData.getBytes(StandardCharsets.UTF_8));
      riRequest.setUserData(userDataString);
      riRequest.setKeyName(keyPair.getKeyName());
      diFilters.add(new Filter("key-name").withValues(keyPair.getKeyName()));
      riRequest.setInstanceType(type.toString());
      diFilters.add(new Filter("instance-type").withValues(type.toString()));

      if (getAssociatePublicIp()) {
        net.setAssociatePublicIpAddress(true);
        net.setDeviceIndex(0);
        riRequest.withNetworkInterfaces(net);
      }

      boolean hasCustomTypeTag = false;
      HashSet<Tag> instTags = null;
      if (tags != null && !tags.isEmpty()) {
        instTags = new HashSet<Tag>();
        for (EC2Tag t : tags) {
          instTags.add(new Tag(t.getName(), t.getValue()));
          diFilters.add(new Filter("tag:" + t.getName()).withValues(t.getValue()));
          if (StringUtils.equals(t.getName(), EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE)) {
            hasCustomTypeTag = true;
          }
        }
      }
      if (!hasCustomTypeTag) {
        if (instTags == null) {
          instTags = new HashSet<Tag>();
        }
        // Append template description as well to identify slaves provisioned per template
        instTags.add(
            new Tag(
                EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE,
                EC2Cloud.getSlaveTypeTagValue(EC2Cloud.EC2_SLAVE_TYPE_DEMAND, description)));
      }

      DescribeInstancesRequest diRequest = new DescribeInstancesRequest();
      diRequest.setFilters(diFilters);

      logProvision(logger, "Looking for existing instances with describe-instance: " + diRequest);

      DescribeInstancesResult diResult = ec2.describeInstances(diRequest);
      EC2AbstractSlave[] ec2Node = new EC2AbstractSlave[1];
      Instance existingInstance = null;
      if (!provisionOptions.contains(ProvisionOptions.FORCE_CREATE)) {
        reservationLoop:
        for (Reservation reservation : diResult.getReservations()) {
          for (Instance instance : reservation.getInstances()) {
            if (checkInstance(logger, instance, requiredLabel, ec2Node)) {
              existingInstance = instance;
              logProvision(
                  logger,
                  "Found existing instance: "
                      + existingInstance
                      + ((ec2Node[0] != null) ? (" node: " + ec2Node[0].getInstanceId()) : ""));
              break reservationLoop;
            }
          }
        }
      }

      if (existingInstance == null) {
        if (!provisionOptions.contains(ProvisionOptions.FORCE_CREATE)
            && !provisionOptions.contains(ProvisionOptions.ALLOW_CREATE)) {
          logProvision(logger, "No existing instance found - but cannot create new instance");
          return null;
        }
        if (StringUtils.isNotBlank(getIamInstanceProfile())) {
          riRequest.setIamInstanceProfile(
              new IamInstanceProfileSpecification().withArn(getIamInstanceProfile()));
        }
        // Have to create a new instance
        Instance inst = ec2.runInstances(riRequest).getReservation().getInstances().get(0);

        /* Now that we have our instance, we can set tags on it */
        if (instTags != null) {
          updateRemoteTags(ec2, instTags, "InvalidInstanceID.NotFound", inst.getInstanceId());

          // That was a remote request - we should also update our
          // local instance data.
          inst.setTags(instTags);
        }
        logProvisionInfo(logger, "No existing instance found - created new instance: " + inst);
        return newOndemandSlave(inst);
      }

      if (existingInstance
              .getState()
              .getName()
              .equalsIgnoreCase(InstanceStateName.Stopping.toString())
          || existingInstance
              .getState()
              .getName()
              .equalsIgnoreCase(InstanceStateName.Stopped.toString())) {

        List<String> instances = new ArrayList<String>();
        instances.add(existingInstance.getInstanceId());
        StartInstancesRequest siRequest = new StartInstancesRequest(instances);
        StartInstancesResult siResult = ec2.startInstances(siRequest);

        logProvisionInfo(
            logger,
            "Found stopped instance - starting it: " + existingInstance + " result:" + siResult);
      } else {
        // Should be pending or running at this point, just let it come up
        logProvisionInfo(
            logger,
            "Found existing pending or running: "
                + existingInstance.getState().getName()
                + " instance: "
                + existingInstance);
      }

      if (ec2Node[0] != null) {
        logProvisionInfo(logger, "Using existing slave: " + ec2Node[0].getInstanceId());
        return ec2Node[0];
      }

      // Existing slave not found
      logProvision(logger, "Creating new slave for existing instance: " + existingInstance);
      return newOndemandSlave(existingInstance);

    } catch (FormException e) {
      throw new AssertionError(e); // we should have discovered all
      // configuration issues upfront
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
  }
  @Override
  public List<DiscoveryNode> buildDynamicNodes() {
    List<DiscoveryNode> discoNodes = new ArrayList<>();

    DescribeInstancesResult descInstances;
    try {
      // Query EC2 API based on AZ, instance state, and tag.

      // NOTE: we don't filter by security group during the describe instances request for two
      // reasons:
      // 1. differences in VPCs require different parameters during query (ID vs Name)
      // 2. We want to use two different strategies: (all security groups vs. any security groups)
      descInstances = client.describeInstances(buildDescribeInstancesRequest());
    } catch (AmazonClientException e) {
      logger.info("Exception while retrieving instance list from AWS API: {}", e.getMessage());
      logger.debug("Full exception:", e);
      return discoNodes;
    }

    logger.trace("building dynamic unicast discovery nodes...");
    for (Reservation reservation : descInstances.getReservations()) {
      for (Instance instance : reservation.getInstances()) {
        // lets see if we can filter based on groups
        if (!groups.isEmpty()) {
          List<GroupIdentifier> instanceSecurityGroups = instance.getSecurityGroups();
          ArrayList<String> securityGroupNames = new ArrayList<String>();
          ArrayList<String> securityGroupIds = new ArrayList<String>();
          for (GroupIdentifier sg : instanceSecurityGroups) {
            securityGroupNames.add(sg.getGroupName());
            securityGroupIds.add(sg.getGroupId());
          }
          if (bindAnyGroup) {
            // We check if we can find at least one group name or one group id in groups.
            if (Collections.disjoint(securityGroupNames, groups)
                && Collections.disjoint(securityGroupIds, groups)) {
              logger.trace(
                  "filtering out instance {} based on groups {}, not part of {}",
                  instance.getInstanceId(),
                  instanceSecurityGroups,
                  groups);
              // continue to the next instance
              continue;
            }
          } else {
            // We need tp match all group names or group ids, otherwise we ignore this instance
            if (!(securityGroupNames.containsAll(groups) || securityGroupIds.containsAll(groups))) {
              logger.trace(
                  "filtering out instance {} based on groups {}, does not include all of {}",
                  instance.getInstanceId(),
                  instanceSecurityGroups,
                  groups);
              // continue to the next instance
              continue;
            }
          }
        }

        String address = null;
        switch (hostType) {
          case PRIVATE_DNS:
            address = instance.getPrivateDnsName();
            break;
          case PRIVATE_IP:
            address = instance.getPrivateIpAddress();
            break;
          case PUBLIC_DNS:
            address = instance.getPublicDnsName();
            break;
          case PUBLIC_IP:
            address = instance.getPublicIpAddress();
            break;
        }
        if (address != null) {
          try {
            TransportAddress[] addresses = transportService.addressesFromString(address);
            // we only limit to 1 addresses, makes no sense to ping 100 ports
            for (int i = 0; (i < addresses.length && i < UnicastZenPing.LIMIT_PORTS_COUNT); i++) {
              logger.trace(
                  "adding {}, address {}, transport_address {}",
                  instance.getInstanceId(),
                  address,
                  addresses[i]);
              discoNodes.add(
                  new DiscoveryNode(
                      "#cloud-" + instance.getInstanceId() + "-" + i,
                      addresses[i],
                      version.minimumCompatibilityVersion()));
            }
          } catch (Exception e) {
            logger.warn("failed ot add {}, address {}", e, instance.getInstanceId(), address);
          }
        } else {
          logger.trace(
              "not adding {}, address is null, host_type {}", instance.getInstanceId(), hostType);
        }
      }
    }

    logger.debug("using dynamic discovery nodes {}", discoNodes);

    return discoNodes;
  }