/*******************************************************************************
*
* Copyright (c) 2004-2009 Oracle Corporation.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*
* Kohsuke Kawaguchi, Stephen Connolly, Thomas J. Black
*
*
*******************************************************************************/
package hudson.model;
import hudson.BulkChange;
import hudson.DescriptorExtensionList;
import hudson.Util;
import hudson.XmlFile;
import hudson.model.Descriptor.FormException;
import hudson.model.listeners.SaveableListener;
import hudson.node_monitors.NodeMonitor;
import hudson.slaves.NodeDescriptor;
import hudson.util.DescribableList;
import hudson.util.FormValidation;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import javax.servlet.ServletException;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import java.io.File;
import java.io.IOException;
import java.util.AbstractList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.eclipse.hudson.security.team.Team;
import org.eclipse.hudson.security.team.TeamManager;
import org.eclipse.hudson.security.team.TeamManager.TeamNotFoundException;
/**
* Serves as the top of {@link Computer}s in the URL hierarchy. <p> Getter
* methods are prefixed with '_' to avoid collision with computer names.
*
* @author Kohsuke Kawaguchi
*/
@ExportedBean
public final class ComputerSet extends AbstractModelObject {
/**
* This is the owner that persists {@link #monitors}.
*/
private static final Saveable MONITORS_OWNER = new Saveable() {
public void save() throws IOException {
getConfigFile().write(monitors);
SaveableListener.fireOnChange(this, getConfigFile());
}
};
private static final DescribableList<NodeMonitor, Descriptor<NodeMonitor>> monitors = new DescribableList<NodeMonitor, Descriptor<NodeMonitor>>(MONITORS_OWNER);
@Exported
public String getDisplayName() {
return Messages.ComputerSet_DisplayName();
}
/**
* @deprecated as of 1.301 Use {@link #getMonitors()}.
*/
public static List<NodeMonitor> get_monitors() {
return monitors.toList();
}
@Exported(name = "computer", inline = true)
public Computer[] get_all() {
return Hudson.getInstance().getComputers();
}
/**
* Exposing {@link NodeMonitor#all()} for Jelly binding.
*/
public DescriptorExtensionList<NodeMonitor, Descriptor<NodeMonitor>> getNodeMonitorDescriptors() {
return NodeMonitor.all();
}
public static DescribableList<NodeMonitor, Descriptor<NodeMonitor>> getMonitors() {
return monitors;
}
/**
* Returns a subset pf {@link #getMonitors()} that are
* {@linkplain NodeMonitor#isIgnored() not ignored}.
*/
public static Map<Descriptor<NodeMonitor>, NodeMonitor> getNonIgnoredMonitors() {
Map<Descriptor<NodeMonitor>, NodeMonitor> r = new HashMap<Descriptor<NodeMonitor>, NodeMonitor>();
for (NodeMonitor m : monitors) {
if (!m.isIgnored()) {
r.put(m.getDescriptor(), m);
}
}
return r;
}
/**
* Gets all the slave names.
*/
public List<String> get_slaveNames() {
return new AbstractList<String>() {
final List<Node> nodes = Hudson.getInstance().getNodes();
public String get(int index) {
return nodes.get(index).getNodeName();
}
public int size() {
return nodes.size();
}
};
}
/**
* Number of total {@link Executor}s that belong to this label that are
* functioning. <p> This excludes executors that belong to offline nodes.
*/
@Exported
public int getTotalExecutors() {
int r = 0;
for (Computer c : get_all()) {
if (c.isOnline()) {
r += c.countExecutors();
}
}
return r;
}
/**
* Number of busy {@link Executor}s that are carrying out some work right
* now.
*/
@Exported
public int getBusyExecutors() {
int r = 0;
for (Computer c : get_all()) {
if (c.isOnline()) {
r += c.countBusy();
}
}
return r;
}
/**
* {@code getTotalExecutors()-getBusyExecutors()}, plus executors that are
* being brought online.
*/
public int getIdleExecutors() {
int r = 0;
for (Computer c : get_all()) {
if (c.isOnline() || c.isConnecting()) {
r += c.countIdle();
}
}
return r;
}
public String getSearchUrl() {
return "/computers/";
}
public Computer getDynamic(String token, StaplerRequest req, StaplerResponse rsp) {
return Hudson.getInstance().getComputer(token);
}
public void do_launchAll(StaplerRequest req, StaplerResponse rsp) throws IOException {
Hudson.getInstance().checkPermission(Hudson.ADMINISTER);
for (Computer c : get_all()) {
if (c.isLaunchSupported()) {
c.connect(true);
}
}
rsp.sendRedirect(".");
}
/**
* Triggers the schedule update now.
*
* TODO: ajax on the client side to wait until the update completion might
* be nice.
*/
public void doUpdateNow(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
Hudson.getInstance().checkPermission(Hudson.ADMINISTER);
for (NodeMonitor nodeMonitor : NodeMonitor.getAll()) {
Thread t = nodeMonitor.triggerUpdate();
t.setName(nodeMonitor.getColumnCaption());
}
rsp.forwardToPreviousPage(req);
}
/**
* First check point in creating a new slave.
*/
public synchronized void doCreateItem(StaplerRequest req, StaplerResponse rsp,
@QueryParameter String name, @QueryParameter String mode,
@QueryParameter String from) throws IOException, ServletException {
final Hudson app = Hudson.getInstance();
if (app.getTeamManager().isTeamManagementEnabled()) {
app.checkPermission(Computer.CREATE);
} else {
app.checkPermission(Hudson.ADMINISTER); // TODO: new permission?
}
if (mode != null && mode.equals("copy")) {
name = checkName(name);
Node src = app.getNode(from);
if (src == null) {
rsp.setStatus(SC_BAD_REQUEST);
if (Util.fixEmpty(from) == null) {
sendError(Messages.ComputerSet_SpecifySlaveToCopy(), req, rsp);
} else {
sendError(Messages.ComputerSet_NoSuchSlave(from), req, rsp);
}
return;
}
// copy through XStream
String xml = Hudson.XSTREAM.toXML(src);
Node result = (Node) Hudson.XSTREAM.fromXML(xml);
result.setNodeName(name);
result.holdOffLaunchUntilSave = true;
app.addNode(result);
addToTeam(result.getNodeName(), null);
// send the browser to the config page
rsp.sendRedirect2(result.getNodeName() + "/configure");
} else {
// proceed to step 2
if (mode == null) {
rsp.sendError(SC_BAD_REQUEST);
return;
}
NodeDescriptor d = NodeDescriptor.all().find(mode);
d.handleNewNodePage(this, name, req, rsp);
}
}
/**
* Really creates a new slave.
*/
public synchronized void doDoCreateItem(StaplerRequest req, StaplerResponse rsp,
@QueryParameter String name,
@QueryParameter String type) throws IOException, ServletException, FormException {
final Hudson app = Hudson.getInstance();
if (app.getTeamManager().isTeamManagementEnabled()) {
app.checkPermission(Computer.CREATE);
} else {
app.checkPermission(Hudson.ADMINISTER); // TODO: new permission?
}
checkName(name);
Node result = NodeDescriptor.all().find(type).newInstance(req, req.getSubmittedForm());
app.addNode(result);
addToTeam(result.getNodeName(), null);
// take the user back to the slave list top page
rsp.sendRedirect2(".");
}
/**
* Makes sure that the given name is good as a slave name.
*
* @return trimmed name if valid; throws ParseException if not
*/
public String checkName(String name) throws Failure {
if (name == null) {
throw new Failure("Query parameter 'name' is required");
}
name = name.trim();
Hudson.checkGoodName(name);
if (Hudson.getInstance().getNode(name) != null) {
throw new Failure(Messages.ComputerSet_SlaveAlreadyExists(name));
}
// looks good
return name;
}
/**
* Makes sure that the given name is good as a slave name.
*/
public FormValidation doCheckName(@QueryParameter String value) throws IOException, ServletException {
TeamManager teamManager = Hudson.getInstance().getTeamManager();
if (teamManager.isTeamManagementEnabled()) {
Hudson.getInstance().checkPermission(Computer.CREATE);
} else {
Hudson.getInstance().checkPermission(Hudson.ADMINISTER);
}
if (Util.fixEmpty(value) == null) {
return FormValidation.ok();
}
try {
checkName(value);
return FormValidation.ok();
} catch (Failure e) {
return FormValidation.error(e.getMessage());
}
}
/**
* Accepts submission from the configuration page.
*/
public synchronized void doConfigSubmit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException {
BulkChange bc = new BulkChange(MONITORS_OWNER);
try {
Hudson.getInstance().checkPermission(Hudson.ADMINISTER);
monitors.rebuild(req, req.getSubmittedForm(), getNodeMonitorDescriptors());
// add in the rest of instances are ignored instances
for (Descriptor<NodeMonitor> d : NodeMonitor.all()) {
if (monitors.get(d) == null) {
NodeMonitor i = createDefaultInstance(d, true);
if (i != null) {
monitors.add(i);
}
}
}
rsp.sendRedirect2(".");
} finally {
bc.commit();
}
}
/**
* {@link NodeMonitor}s are persisted in this file.
*/
private static XmlFile getConfigFile() {
return new XmlFile(new File(Hudson.getInstance().getRootDir(), "nodeMonitors.xml"));
}
public Api getApi() {
return new Api(this);
}
/**
* Just to force the execution of the static initializer.
*/
public static void initialize() {
}
private static final Logger LOGGER = Logger.getLogger(ComputerSet.class.getName());
static {
try {
DescribableList<NodeMonitor, Descriptor<NodeMonitor>> r = new DescribableList<NodeMonitor, Descriptor<NodeMonitor>>(Saveable.NOOP);
// load persisted monitors
XmlFile xf = getConfigFile();
if (xf.exists()) {
DescribableList<NodeMonitor, Descriptor<NodeMonitor>> persisted =
(DescribableList<NodeMonitor, Descriptor<NodeMonitor>>) xf.read();
r.replaceBy(persisted.toList());
}
// if we have any new monitors, let's add them
for (Descriptor<NodeMonitor> d : NodeMonitor.all()) {
if (r.get(d) == null) {
NodeMonitor i = createDefaultInstance(d, false);
if (i != null) {
r.add(i);
}
}
}
monitors.replaceBy(r.toList());
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Failed to instanciate NodeMonitors", e);
}
}
private static NodeMonitor createDefaultInstance(Descriptor<NodeMonitor> d, boolean ignored) {
try {
NodeMonitor nm = d.clazz.newInstance();
nm.setIgnored(ignored);
return nm;
} catch (InstantiationException e) {
LOGGER.log(Level.SEVERE, "Failed to instanciate " + d.clazz, e);
} catch (IllegalAccessException e) {
LOGGER.log(Level.SEVERE, "Failed to instanciate " + d.clazz, e);
}
return null;
}
private void addToTeam(String name, String teamName) throws IOException {
TeamManager teamManager = Hudson.getInstance().getTeamManager();
if (!teamManager.isTeamManagementEnabled()) {
if (teamName != null) {
throw new IOException("Team management is not enabled");
}
}
Team team = null;
if (teamName == null) {
try {
team = teamManager.findCurrentUserTeamForNewNode();
} catch (TeamNotFoundException ex) {
// Shouldn't happen, as user is already confirmed for Computer.CREATE
}
} else {
try {
team = teamManager.findTeam(teamName);
} catch (TeamNotFoundException e) {
throw new IOException("Team " + teamName + " does not exist");
}
}
try {
teamManager.addNode(team, name);
} catch (TeamNotFoundException ex) {
throw new IOException("Team " + teamName + " does not exist");
}
}
}