123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665 |
- package hudson.plugins.ec2.one;
- import hudson.Extension;
- import hudson.Util;
- import hudson.model.Describable;
- import hudson.model.TaskListener;
- import hudson.model.Descriptor;
- import hudson.model.Descriptor.FormException;
- import hudson.model.Hudson;
- import hudson.model.Label;
- import hudson.model.Node;
- import hudson.model.labels.LabelAtom;
- import hudson.plugins.ec2.EC2Computer;
- import hudson.plugins.ec2.EC2Slave;
- import hudson.util.FormValidation;
- import java.io.IOException;
- import java.io.PrintStream;
- import java.net.URL;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Collections;
- import java.util.HashSet;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.Set;
- import javax.servlet.ServletException;
- import org.apache.commons.codec.binary.Base64;
- import org.apache.commons.lang.StringUtils;
- import org.kohsuke.stapler.DataBoundConstructor;
- import org.kohsuke.stapler.QueryParameter;
- import com.amazonaws.AmazonClientException;
- import com.amazonaws.services.ec2.AmazonEC2;
- import com.amazonaws.services.ec2.model.CreateTagsRequest;
- import com.amazonaws.services.ec2.model.DescribeImagesRequest;
- import com.amazonaws.services.ec2.model.DescribeInstancesRequest;
- import com.amazonaws.services.ec2.model.DescribeInstancesResult;
- import com.amazonaws.services.ec2.model.DescribeSecurityGroupsRequest;
- import com.amazonaws.services.ec2.model.DescribeSecurityGroupsResult;
- import com.amazonaws.services.ec2.model.DescribeSubnetsRequest;
- import com.amazonaws.services.ec2.model.DescribeSubnetsResult;
- import com.amazonaws.services.ec2.model.Filter;
- import com.amazonaws.services.ec2.model.Image;
- import com.amazonaws.services.ec2.model.Instance;
- import com.amazonaws.services.ec2.model.InstanceStateName;
- import com.amazonaws.services.ec2.model.KeyPair;
- import com.amazonaws.services.ec2.model.Placement;
- import com.amazonaws.services.ec2.model.Reservation;
- import com.amazonaws.services.ec2.model.RunInstancesRequest;
- import com.amazonaws.services.ec2.model.SecurityGroup;
- import com.amazonaws.services.ec2.model.StartInstancesRequest;
- import com.amazonaws.services.ec2.model.StartInstancesResult;
- import com.amazonaws.services.ec2.model.Tag;
- /**
- * Template of {@link EC2Slave} to launch.
- *
- * @author Kohsuke Kawaguchi
- */
- public class OneSlaveTemplate implements Describable<OneSlaveTemplate> {
- public final String ami;
- public final String description;
- public final String zone;
- public final String securityGroups;
- public final String remoteFS;
- public final String sshPort;
- public final OneInstanceType type;
- public final String labels;
- public final String initScript;
- public final String userData;
- public final String numExecutors;
- public final String remoteAdmin;
- public final String rootCommandPrefix;
- public final String jvmopts;
- public final String subnetId;
- public final String idleTerminationMinutes;
- public final int instanceCap;
- public final boolean stopOnTerminate;
- private final List<OneEC2Tag> tags;
- public final boolean usePrivateDnsName;
- protected transient OneEC2Cloud parent;
- private transient/* almost final */Set<LabelAtom> labelSet;
- private transient/* almost final */Set<String> securityGroupSet;
- @DataBoundConstructor
- public OneSlaveTemplate(final String ami, final String zone,
- final String securityGroups, final String remoteFS,
- final String sshPort, final OneInstanceType type,
- final String labelString, final String description,
- final String initScript, final String userData,
- final String numExecutors, final String remoteAdmin,
- final String rootCommandPrefix, final String jvmopts,
- final boolean stopOnTerminate, final String subnetId,
- final List<OneEC2Tag> tags,
- final String idleTerminationMinutes,
- final boolean usePrivateDnsName, final String instanceCapStr) {
- this.ami = ami;
- this.zone = zone;
- this.securityGroups = securityGroups;
- this.remoteFS = remoteFS;
- this.sshPort = sshPort;
- this.type = type;
- labels = Util.fixNull(labelString);
- this.description = description;
- this.initScript = initScript;
- this.userData = userData;
- this.numExecutors = Util.fixNull(numExecutors).trim();
- this.remoteAdmin = remoteAdmin;
- this.rootCommandPrefix = rootCommandPrefix;
- this.jvmopts = jvmopts;
- this.stopOnTerminate = stopOnTerminate;
- this.subnetId = subnetId;
- this.tags = tags;
- this.idleTerminationMinutes = idleTerminationMinutes;
- this.usePrivateDnsName = usePrivateDnsName;
- if (null == instanceCapStr || instanceCapStr.equals("")) {
- instanceCap = Integer.MAX_VALUE;
- } else {
- instanceCap = Integer.parseInt(instanceCapStr);
- }
- readResolve(); // initialize
- }
- public OneEC2Cloud getParent() {
- return parent;
- }
- public String getLabelString() {
- return labels;
- }
- public String getDisplayName() {
- return description + " (" + ami + ")";
- }
- String getZone() {
- return zone;
- }
- public String getSecurityGroupString() {
- return securityGroups;
- }
- public Set<String> getSecurityGroupSet() {
- return securityGroupSet;
- }
- public Set<String> parseSecurityGroups() {
- if (securityGroups == null || "".equals(securityGroups.trim())) {
- return Collections.emptySet();
- } else {
- return new HashSet<String>(Arrays.asList(securityGroups
- .split("\\s*,\\s*")));
- }
- }
- public int getNumExecutors() {
- try {
- return Integer.parseInt(numExecutors);
- } catch (NumberFormatException e) {
- return OneEC2Slave.toNumExecutors(type);
- }
- }
- public int getSshPort() {
- try {
- return Integer.parseInt(sshPort);
- } catch (NumberFormatException e) {
- return 22;
- }
- }
- public String getRemoteAdmin() {
- return remoteAdmin;
- }
- public String getRootCommandPrefix() {
- return rootCommandPrefix;
- }
- public String getSubnetId() {
- return subnetId;
- }
- public List<OneEC2Tag> getTags() {
- if (null == tags) {
- return null;
- }
- return Collections.unmodifiableList(tags);
- }
- public String getidleTerminationMinutes() {
- return idleTerminationMinutes;
- }
- public Set<LabelAtom> getLabelSet() {
- return labelSet;
- }
- public int getInstanceCap() {
- return instanceCap;
- }
- public String getInstanceCapStr() {
- if (instanceCap == Integer.MAX_VALUE) {
- return "";
- } else {
- return String.valueOf(instanceCap);
- }
- }
- /**
- * Does this contain the given label?
- *
- * @param l
- * can be null to indicate "don't care".
- */
- public boolean containsLabel(final Label l) {
- return l == null || labelSet.contains(l);
- }
- /**
- * Provisions a new EC2 slave.
- *
- * @return always non-null. This needs to be then added to
- * {@link Hudson#addNode(Node)}.
- */
- public OneEC2Slave provision(final TaskListener listener)
- throws AmazonClientException, IOException {
- PrintStream logger = listener.getLogger();
- AmazonEC2 ec2 = getParent().connect();
- try {
- logger.println("Launching " + ami);
- KeyPair keyPair =
- (null != parent.getPrivateKey()) ? parent.getPrivateKey()
- .find(ec2) : null;
- RunInstancesRequest riRequest =
- new RunInstancesRequest(ami, 1, 1);
- List<Filter> diFilters = new ArrayList<Filter>();
- diFilters.add(new Filter("image-id").withValues(ami));
- if (StringUtils.isNotBlank(getZone())) {
- Placement placement = new Placement(getZone());
- riRequest.setPlacement(placement);
- diFilters.add(new Filter("availability-zone")
- .withValues(getZone()));
- }
- if (StringUtils.isNotBlank(getSubnetId())) {
- 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> group_ids = new ArrayList<String>();
- DescribeSecurityGroupsRequest group_req =
- new DescribeSecurityGroupsRequest();
- group_req.withFilters(new Filter("group-name")
- .withValues(securityGroupSet));
- DescribeSecurityGroupsResult group_result =
- ec2.describeSecurityGroups(group_req);
- for (SecurityGroup group : group_result
- .getSecurityGroups()) {
- if (group.getVpcId() != null
- && !group.getVpcId().isEmpty()) {
- List<Filter> filters = new ArrayList<Filter>();
- filters.add(new Filter("vpc-id")
- .withValues(group.getVpcId()));
- filters.add(new Filter("state")
- .withValues("available"));
- filters.add(new Filter("subnet-id")
- .withValues(getSubnetId()));
- DescribeSubnetsRequest subnet_req =
- new DescribeSubnetsRequest();
- subnet_req.withFilters(filters);
- DescribeSubnetsResult subnet_result =
- ec2.describeSubnets(subnet_req);
- List subnets = subnet_result.getSubnets();
- if (subnets != null && !subnets.isEmpty()) {
- group_ids.add(group.getGroupId());
- }
- }
- }
- if (securityGroupSet.size() != group_ids.size()) {
- throw new AmazonClientException(
- "Security groups must all be VPC security groups to work in a VPC context");
- }
- if (!group_ids.isEmpty()) {
- riRequest.setSecurityGroupIds(group_ids);
- diFilters.add(new Filter("instance.group-id")
- .withValues(group_ids));
- }
- }
- } else {
- /* No subnet: we can use standard security groups by name */
- riRequest.setSecurityGroups(securityGroupSet);
- if (securityGroupSet.size() > 0) {
- diFilters.add(new Filter("group-name")
- .withValues(securityGroupSet));
- }
- }
- if (null != userData) {
- String userDataString =
- Base64.encodeBase64String(userData.getBytes());
- riRequest.setUserData(userDataString);
- }
- if (null != keyPair) {
- 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()));
- HashSet<Tag> inst_tags = null;
- if (tags != null && !tags.isEmpty()) {
- inst_tags = new HashSet<Tag>();
- for (OneEC2Tag t : tags) {
- diFilters.add(new Filter("tag:" + t.getName())
- .withValues(t.getValue()));
- }
- }
- DescribeInstancesRequest diRequest =
- new DescribeInstancesRequest();
- diFilters.add(new Filter("instance-state-name").withValues(
- InstanceStateName.Stopped.toString(),
- InstanceStateName.Stopping.toString()));
- diRequest.setFilters(diFilters);
- logger.println("Looking for existing instances: " + diRequest);
- DescribeInstancesResult diResult =
- ec2.describeInstances(diRequest);
- filterReservations(diResult.getReservations(), diFilters);
- if (diResult.getReservations().size() == 0) {
- // 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 (inst_tags != null) {
- CreateTagsRequest tag_request =
- new CreateTagsRequest();
- tag_request.withResources(inst.getInstanceId())
- .setTags(inst_tags);
- ec2.createTags(tag_request);
- // That was a remote request - we should also update our
- // local instance data.
- inst.setTags(inst_tags);
- }
- logger.println("No existing instance found - created: "
- + inst);
- return newSlave(inst);
- }
- Instance inst =
- diResult.getReservations().get(0).getInstances().get(0);
- logger.println("Found existing stopped instance: " + inst);
- List<String> instances = new ArrayList<String>();
- instances.add(inst.getInstanceId());
- StartInstancesRequest siRequest =
- new StartInstancesRequest(instances);
- StartInstancesResult siResult = ec2.startInstances(siRequest);
- logger.println("Starting existing instance: " + inst
- + " result:" + siResult);
- List<Node> nodes = Hudson.getInstance().getNodes();
- for (int i = 0, len = nodes.size(); i < len; i++) {
- if (!(nodes.get(i) instanceof EC2Slave)) {
- continue;
- }
- OneEC2Slave ec2Node = (OneEC2Slave) nodes.get(i);
- if (ec2Node.getInstanceId().equals(inst.getInstanceId())) {
- logger.println("Found existing corresponding: "
- + ec2Node);
- return ec2Node;
- }
- }
- // Existing slave not found
- logger.println("Creating new slave for existing instance: "
- + inst);
- return newSlave(inst);
- } catch (FormException e) {
- throw new AssertionError(); // we should have discovered all
- // configuration issues upfront
- }
- }
- private OneEC2Slave newSlave(final Instance inst)
- throws FormException, IOException {
- return new OneEC2Slave(inst.getInstanceId(), description,
- remoteFS, getSshPort(), getNumExecutors(), labels, initScript,
- remoteAdmin, rootCommandPrefix, jvmopts, stopOnTerminate,
- idleTerminationMinutes, inst.getPublicDnsName(),
- inst.getPrivateDnsName(), OneEC2Tag.fromAmazonTags(inst
- .getTags()), usePrivateDnsName);
- }
- /**
- * Provisions a new EC2 slave based on the currently running instance on
- * EC2, instead of starting a new one.
- */
- public OneEC2Slave attach(final String instanceId,
- final TaskListener listener)
- throws AmazonClientException, IOException {
- PrintStream logger = listener.getLogger();
- AmazonEC2 ec2 = getParent().connect();
- try {
- logger.println("Attaching to " + instanceId);
- DescribeInstancesRequest request =
- new DescribeInstancesRequest();
- request.setInstanceIds(Collections.singletonList(instanceId));
- Instance inst =
- ec2.describeInstances(request).getReservations().get(0)
- .getInstances().get(0);
- return newSlave(inst);
- } catch (FormException e) {
- throw new AssertionError(); // we should have discovered all
- // configuration issues upfront
- }
- }
- /**
- * Initializes data structure that we don't persist.
- */
- protected Object readResolve() {
- labelSet = Label.parse(labels);
- securityGroupSet = parseSecurityGroups();
- return this;
- }
- public Descriptor<OneSlaveTemplate> getDescriptor() {
- return Hudson.getInstance().getDescriptor(getClass());
- }
- @Extension
- public static final class DescriptorImpl extends
- Descriptor<OneSlaveTemplate> {
- @Override
- public String getDisplayName() {
- return null;
- }
- /**
- * Since this shares much of the configuration with {@link EC2Computer},
- * check its help page, too.
- */
- @Override
- public String getHelpFile(final String fieldName) {
- String p = super.getHelpFile(fieldName);
- if (p == null) {
- p =
- Hudson.getInstance().getDescriptor(OneEC2Slave.class)
- .getHelpFile(fieldName);
- }
- return p;
- }
- /***
- * Check that the AMI requested is available in the cloud and can be
- * used.
- */
- public FormValidation doValidateAmi(
- @QueryParameter final String url,
- @QueryParameter final String accessId,
- @QueryParameter final String secretKey,
- final @QueryParameter String ami)
- throws IOException, ServletException {
- AmazonEC2 ec2 =
- OneEC2Cloud.connect(accessId, secretKey, new URL(url));
- if (ec2 != null) {
- try {
- List<String> images = new LinkedList<String>();
- images.add(ami);
- List<String> owners = new LinkedList<String>();
- List<String> users = new LinkedList<String>();
- DescribeImagesRequest request =
- new DescribeImagesRequest();
- request.setImageIds(images);
- request.setOwners(owners);
- request.setExecutableUsers(users);
- List<Image> img =
- ec2.describeImages(request).getImages();
- if (img == null || img.isEmpty()) {
- // de-registered AMI causes an empty list to be
- // returned. so be defensive
- // against other possibilities
- return FormValidation
- .error("No such AMI, or not usable with this accessId: "
- + ami);
- }
- return FormValidation.ok(img.get(0).getImageLocation()
- + " by " + img.get(0).getImageOwnerAlias());
- } catch (AmazonClientException e) {
- return FormValidation.error(e.getMessage());
- }
- } else {
- return FormValidation.ok(); // can't test
- }
- }
- public FormValidation doCheckIdleTerminationMinutes(
- @QueryParameter final String value) {
- if (value == null || value.trim() == "") {
- return FormValidation.ok();
- }
- try {
- int val = Integer.parseInt(value);
- if (val >= 0) {
- return FormValidation.ok();
- }
- } catch (NumberFormatException nfe) {
- }
- return FormValidation
- .error("Idle Termination time must be a non-negative integer (or null)");
- }
- public FormValidation doCheckInstanceCapStr(
- @QueryParameter final String value) {
- if (value == null || value.trim() == "") {
- return FormValidation.ok();
- }
- try {
- int val = Integer.parseInt(value);
- if (val >= 0) {
- return FormValidation.ok();
- }
- } catch (NumberFormatException nfe) {
- }
- return FormValidation
- .error("InstanceCap must be a non-negative integer (or null)");
- }
- }
- private void filterReservations(final List<Reservation> reservations,
- final List<Filter> filters) {
- for (Filter f : filters) {
- filterReservationsBy(reservations, f.getName(), f.getValues());
- }
- }
- private void filterReservationsBy(
- final List<Reservation> reservations, final String name,
- final List<String> values) {
- List<Reservation> emptyReservations = new ArrayList<Reservation>();
- for (Reservation reservation : reservations) {
- filterReservationBy(reservation, name, values);
- if (reservation.getInstances().size() == 0) {
- emptyReservations.add(reservation);
- }
- }
- reservations.removeAll(emptyReservations);
- }
- /**
- * @param reservation
- * @param name
- * @param values
- */
- private void filterReservationBy(final Reservation reservation,
- final String name, final List<String> values) {
- List<Instance> instances = new ArrayList<Instance>();
- for (Instance inst : reservation.getInstances()) {
- if (name.equalsIgnoreCase("image-id")) {
- String data = inst.getImageId();
- for (String value : values) {
- if (value.equalsIgnoreCase(data)) {
- instances.add(inst);
- break;
- }
- }
- }
- if (name.equalsIgnoreCase("instance-type")) {
- String data = inst.getInstanceType();
- for (String value : values) {
- if (value.equalsIgnoreCase(data)) {
- instances.add(inst);
- break;
- }
- }
- }
- if (name.equalsIgnoreCase("instance-state-name")) {
- String data = inst.getState().getName();
- for (String value : values) {
- if (value.equalsIgnoreCase(data)) {
- instances.add(inst);
- break;
- }
- }
- }
- if (name.equalsIgnoreCase("key-name")) {
- String data = inst.getKeyName();
- for (String value : values) {
- if (value.equalsIgnoreCase(data)) {
- instances.add(inst);
- break;
- }
- }
- }
- // TODO: Include more filters
- // TODO: Improve filtering mecanism
- }
- reservation.getInstances().retainAll(instances);
- }
- }
|