You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@vcl.apache.org by jf...@apache.org on 2014/09/11 18:01:49 UTC

svn commit: r1624325 [10/13] - in /vcl/trunk/web: ./ .ht-inc/ .ht-inc/authmethods/ css/ js/ js/resources/

Modified: vcl/trunk/web/.ht-inc/utils.php
URL: http://svn.apache.org/viewvc/vcl/trunk/web/.ht-inc/utils.php?rev=1624325&r1=1624324&r2=1624325&view=diff
==============================================================================
--- vcl/trunk/web/.ht-inc/utils.php (original)
+++ vcl/trunk/web/.ht-inc/utils.php Thu Sep 11 16:01:48 2014
@@ -20,6 +20,7 @@ require_once(".ht-inc/secrets.php");
 @include_once("itecsauth/itecsauth.php");
 require_once(".ht-inc/authentication.php");
 require_once(".ht-inc/phpseclib/Crypt/AES.php");
+require_once(".ht-inc/spyc-0.5/spyc.php");
 if(file_exists(".ht-inc/vcldocs.php"))
 	require_once(".ht-inc/vcldocs.php");
 
@@ -35,14 +36,10 @@ define("ARG_STRING", 1 << 1);
 define("ARG_MULTINUMERIC", 1 << 2);
 /// used for processInputVar, means the input variable should be an array of strings
 define("ARG_MULTISTRING", 1 << 3);
-/// define semaphore key
-define("SEMKEY", 192365819256598);
 
 /// global array used to hold request information between calling isAvailable
 /// and addRequest
 $requestInfo = array();
-#$requestInfo["freeComputerids"] = array();
-#$requestInfo["imageids"] = array();
 
 /// global array to cache arrays of node parents for getNodeParents
 $nodeparents = array();
@@ -62,20 +59,25 @@ $printedHTMLheader = 0;
 ////////////////////////////////////////////////////////////////////////////////
 function initGlobals() {
 	global $mode, $user, $remoteIP, $authed, $oldmode, $semid;
-	global $semislocked, $days, $phpVer, $keys, $pemkey, $AUTHERROR;
+	global $days, $phpVer, $keys, $pemkey, $AUTHERROR;
 	global $passwdArray, $skin, $contdata, $lastmode, $inContinuation;
-	global $totalQueries, $ERRORS, $queryTimes, $actions;
+	global $ERRORS, $actions;
 	global $affilValFunc, $addUserFunc, $updateUserFunc, $addUserFuncArgs;
+	global $uniqid;
 
 	define("SECINDAY", 86400);
 	define("SECINWEEK", 604800);
 	define("SECINMONTH", 2678400);
 	define("SECINYEAR", 31536000);
+	# TODO validate security of this
+	if(array_key_exists("PATH_INFO", $_SERVER)) {
+		$pathdata = explode("/", $_SERVER["PATH_INFO"]);
+		$tmp = explode('.', $pathdata[1]);
+		$_GET["mode"] = $tmp[0];
+	}
 	$mode = processInputVar("mode", ARG_STRING, 'main');
-	$totalQueries = 0;
 	$inContinuation = 0;
 	$contdata = array();
-	$queryTimes = array();
 	$contuserid = '';
 	$continuation = processInputVar('continuation', ARG_STRING);
 	if(! empty($continuation)) {
@@ -101,6 +103,7 @@ function initGlobals() {
 	$days = array(_('Sunday'), _('Monday'), _('Tuesday'), _('Wednesday'), _('Thursday'), _('Friday'), _('Saturday'));
 	$phpVerArr = explode('.', phpversion());
 	$phpVer = $phpVerArr[0];
+	$uniqid = uniqid($_SERVER['HTTP_HOST'] . "-" . getmypid() . "-");
 
 	$passwdArray = array('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
 	                     'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
@@ -185,10 +188,6 @@ function initGlobals() {
 			$mode = "auth";
 		}
 		if($mode == 'xmlrpccall' || $mode == 'xmlrpcaffiliations') {
-			// get the semaphore id
-			if(! ($semid = sem_get(SEMKEY, 1, 0666, 1)))
-				abort(2);
-			$semislocked = 0;
 			require_once(".ht-inc/xmlrpcWrappers.php");
 			require_once(".ht-inc/requests.php");
 			require_once(".ht-inc/serverprofiles.php");
@@ -257,29 +256,11 @@ function initGlobals() {
 			$updateUserFunc[$id] = create_function('', 'return NULL;');
 	}
 
-	// get the semaphore id
-	if(! ($semid = sem_get(SEMKEY, 1, 0666, 1)))
-		abort(2);
-	$semislocked = 0;
-
 	# include appropriate files
 	switch($actions['pages'][$mode]) {
 		case 'blockAllocations':
 			require_once(".ht-inc/blockallocations.php");
 			break;
-		case 'manageComputers':
-			require_once(".ht-inc/computers.php");
-			break;
-		case 'managementNodes':
-			require_once(".ht-inc/managementnodes.php");
-			break;
-		case 'manageImages':
-			require_once(".ht-inc/images.php");
-			require_once(".ht-inc/requests.php");
-			break;
-		case 'manageSchedules':
-			require_once(".ht-inc/schedules.php");
-			break;
 		case 'help':
 			require_once(".ht-inc/help.php");
 			break;
@@ -305,6 +286,20 @@ function initGlobals() {
 		case 'dashboard':
 			require_once(".ht-inc/dashboard.php");
 			break;
+		case 'siteconfig':
+			require_once(".ht-inc/siteconfig.php");
+			break;
+		case 'resource':
+		case 'config':
+		case 'image':
+		case 'computer':
+		case 'managementnode':
+		case 'schedule':
+			require_once(".ht-inc/resource.php");
+			break;
+		case 'storebackend':
+			require_once(".ht-inc/storebackend.php");
+			break;
 		case 'serverProfiles':
 			require_once(".ht-inc/serverprofiles.php");
 			require_once(".ht-inc/requests.php");
@@ -316,6 +311,21 @@ function initGlobals() {
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
+/// \fn __autoload($class)
+///
+/// \param $class - name of a class
+///
+/// \brief handles loading class implementation file for a specified class
+///
+////////////////////////////////////////////////////////////////////////////////
+function __autoload($class) {
+	$class = strtolower($class);
+	require_once(".ht-inc/resource.php");
+	require_once(".ht-inc/$class.php");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
 /// \fn checkAccess()
 ///
 /// \brief gets the user's access level to the locker from the ptsowner_admin
@@ -346,6 +356,11 @@ function checkAccess() {
 				exit;
 			}
 		}
+		if(! array_key_exists('HTTP_X_PASS', $_SERVER) || strlen($_SERVER['HTTP_X_PASS']) == 0) {
+			printXMLRPCerror(3);   # access denied
+			dbDisconnect();
+			exit;
+		}
 		$xmlpass = $_SERVER['HTTP_X_PASS'];
 		if(get_magic_quotes_gpc())
 			$xmlpass = stripslashes($xmlpass);
@@ -439,16 +454,16 @@ function checkAccess() {
 					exit;
 				}
 			}
-            elseif($authMechs[$authtype]['type'] == 'redirect'){
-                $affilid = $authMechs[$authtype]['affiliationid'];
-                if(!(isset($apiValidateFunc) && is_array($apiValidateFunc) &&
-                        array_key_exists($affilid, $apiValidateFunc) && 
-                        $apiValidateFunc[$affilid]($xmluser, $xmlpass))){
-                    printXMLRPCerror(3);    # access denied
-                    dbDisconnect();
-                    exit;
-                }
-            }
+			elseif($authMechs[$authtype]['type'] == 'redirect') {
+				$affilid = $authMechs[$authtype]['affiliationid'];
+				if(!(isset($apiValidateFunc) && is_array($apiValidateFunc) &&
+				   array_key_exists($affilid, $apiValidateFunc) && 
+				   $apiValidateFunc[$affilid]($xmluser, $xmlpass))) {
+					printXMLRPCerror(3);    # access denied
+					dbDisconnect();
+					exit;
+				}
+			}
 			else {
 				printXMLRPCerror(6);    # unable to auth passed in X-User
 				dbDisconnect();
@@ -491,16 +506,6 @@ function checkAccess() {
 			if(! $inContinuation) {
 				# check that user has access to this area
 				switch($mode) {
-					case 'viewRequests':
-						$requests = getUserRequests("all", $user["id"]);
-						if(! in_array("imageCheckOut", $user["privileges"]) &&
-							! in_array("imageAdmin", $user["privileges"]) &&
-						   ! count($requests)) {
-							$mode = "";
-							$actionFunction = "main";
-							return;
-						}
-						break;
 					case 'viewGroups':
 						if(! in_array("groupAdmin", $user["privileges"])) {
 							$mode = "";
@@ -508,34 +513,6 @@ function checkAccess() {
 							return;
 						}
 						break;
-					case 'selectImageOption':
-						if(! in_array("imageAdmin", $user["privileges"])) {
-							$mode = "";
-							$actionFunction = "main";
-							return;
-						}
-						break;
-					case 'viewSchedules':
-						if(! in_array("scheduleAdmin", $user["privileges"])) {
-							$mode = "";
-							$actionFunction = "main";
-							return;
-						}
-						break;
-					case 'selectComputers':
-						if(! in_array("computerAdmin", $user["privileges"])) {
-							$mode = "";
-							$actionFunction = "main";
-							return;
-						}
-						break;
-					case 'selectMgmtnodeOption':
-						if(! in_array("mgmtNodeAdmin", $user["privileges"])) {
-							$mode = "";
-							$actionFunction = "main";
-							return;
-						}
-						break;
 					case 'serverProfiles':
 						if(! in_array("serverProfileAdmin", $user["privileges"]) &&
 						   ! in_array("serverCheckOut", $user["privileges"])) {
@@ -756,6 +733,7 @@ function clearPrivCache() {
 	$_SESSION['userhaspriv'] = array();
 	$_SESSION['compstateflow'] = array();
 	$_SESSION['usersessiondata'] = array();
+	$_SESSION['variables'] = array();
 	unset($_SESSION['user']);
 	unset($_SESSION['locales']);
 }
@@ -808,6 +786,8 @@ function setupSession() {
 		$_SESSION['compstateflow'] = array();
 	if(! array_key_exists('usersessiondata', $_SESSION))
 		$_SESSION['usersessiondata'] = array();
+	if(! array_key_exists('variables', $_SESSION))
+		$_SESSION['variables'] = array();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -899,7 +879,8 @@ function abort($errcode, $query="") {
 		$message = "";
 		if($errcode >= 100 && $errcode < 400) {
 			$message .= mysql_error($mysql_link_vcl) . "\n";
-			$message .= mysql_error($mysql_link_acct) . "\n";
+			if($ENABLE_ITECSAUTH)
+				$message .= mysql_error($mysql_link_acct) . "\n";
 			$message .= $query . "\n";
 		}
 		$message .= "ERROR($errcode): " . $ERRORS["$errcode"] . "\n";
@@ -926,10 +907,10 @@ function abort($errcode, $query="") {
 		print HELPEMAIL . "</a> " . _("for further assistance.  Please include the ");
 		print _("steps you took that led up to this problem in your email message.");
 	}
+	// release semaphore lock
+	cleanSemaphore();
 	dbDisconnect();
 	printHTMLFooter();
-	// release semaphore lock
-	semUnlock();
 	exit;
 }
 
@@ -1115,10 +1096,9 @@ function dbDisconnect() {
 ////////////////////////////////////////////////////////////////////////////////
 function doQuery($query, $errcode=101, $db="vcl", $nolog=0) {
 	global $mysql_link_vcl, $mysql_link_acct, $user, $mode, $ENABLE_ITECSAUTH;
-	global $totalQueries, $queryTimes;
-	$totalQueries++;
 	if($db == "vcl") {
-		if((! $nolog) && preg_match('/^(UPDATE|INSERT|DELETE)/', $query)) {
+		if((! $nolog) && preg_match('/^(UPDATE|INSERT|DELETE)/', $query) &&
+		   strpos($query, 'UPDATE continuations SET expiretime = ') === FALSE) {
 			$logquery = str_replace("'", "\'", $query);
 			$logquery = str_replace('"', '\"', $logquery);
 			if(isset($user['id']))
@@ -1137,7 +1117,12 @@ function doQuery($query, $errcode=101, $
 			   .        "'$logquery')";
 			mysql_query($q, $mysql_link_vcl);
 		}
-		$qh = mysql_query($query, $mysql_link_vcl) or abort($errcode, $query);
+		for($i = 0; ! ($qh = mysql_query($query, $mysql_link_vcl)) && $i < 3; $i++) {
+			if(mysql_errno() == '1213') # DEADLOCK, sleep and retry
+				usleep(50);
+			else
+				abort($errcode, $query);
+		}
 	}
 	elseif($db == "accounts") {
 		if($ENABLE_ITECSAUTH)
@@ -1172,7 +1157,8 @@ function dbLastInsertID() {
 ///
 ////////////////////////////////////////////////////////////////////////////////
 function getOSList() {
-	$qh = doQuery("SELECT id, name, prettyname, type FROM OS", "115");
+	$query = "SELECT id, name, prettyname, type, installtype FROM OS ORDER BY prettyname";
+	$qh = doQuery($query, "115");
 	$oslist = array();
 	while($row = mysql_fetch_assoc($qh))
 		$oslist[$row['id']] = $row;
@@ -1197,6 +1183,8 @@ function getOSList() {
 /// \b osid - osid for the os on the image\n
 /// \b os - os the image contains\n
 /// \b installtype - method used to install image\n
+/// \b ostypeid - id of the OS type in the image\n
+/// \b ostype - name of the OS type in the image\n
 /// \b minram - minimum amount of RAM needed for image\n
 /// \b minprocnumber - minimum number of processors needed for image\n
 /// \b minprocspeed - minimum speed of processor(s) needed for image\n
@@ -1255,14 +1243,17 @@ function getImages($includedeleted=0, $i
 	       .        "CONCAT(u.unityid, '@', a.name) AS user, "
 	       .        "i.datecreated, "
 	       .        "DATE_FORMAT(i.datecreated, '%c/%d/%y %l:%i %p') AS prettydate, "
+	       .        "i.deleted, "
+	       .        "i.datedeleted, "
 	       .        "i.production, "
 	       .        "i.imagename "
 	       . "FROM imagerevision i, "
 	       .      "affiliation a, "
 	       .      "user u "
-	       . "WHERE i.deleted = 0 AND "
-	       .       "i.userid = u.id AND "
-	       .       "u.affiliationid = a.id";
+	       . "WHERE i.userid = u.id AND ";
+	if(! $includedeleted)
+		$query .=   "i.deleted = 0 AND ";
+	$query .=      "u.affiliationid = a.id";
 	$qh = doQuery($query, 101);
 	while($row = mysql_fetch_assoc($qh)) {
 		$id = $row['imageid'];
@@ -1281,6 +1272,8 @@ function getImages($includedeleted=0, $i
 	       .        "i.OSid AS osid, "
 	       .        "o.name AS os, "
 	       .        "o.installtype, "
+	       .        "ot.id AS ostypeid, "
+	       .        "ot.name AS ostype, "
 	       .        "i.minram AS minram, "
 	       .        "i.minprocnumber AS minprocnumber, "
 	       .        "i.minprocspeed AS minprocspeed, "
@@ -1297,6 +1290,7 @@ function getImages($includedeleted=0, $i
 	       . "FROM image i, "
 	       .      "platform p, "
 	       .      "OS o, "
+	       .      "OStype ot, "
 	       .      "resource r, "
 	       .      "resourcetype t, "
 	       .      "user u, "
@@ -1306,6 +1300,7 @@ function getImages($includedeleted=0, $i
 	       .       "t.name = 'image' AND "
 	       .       "r.subid = i.id AND "
 	       .       "i.OSid = o.id AND "
+	       .       "o.type = ot.name AND "
 	       .       "i.ownerid = u.id AND "
 	       .       "u.affiliationid = a.id ";
 	if(! $includedeleted)
@@ -1313,7 +1308,11 @@ function getImages($includedeleted=0, $i
    $query .= "ORDER BY i.prettyname";
 	$qh = doQuery($query, 120);
 	while($row = mysql_fetch_assoc($qh)) {
+		if(is_null($row['maxconcurrent']))
+			$row['maxconcurrent'] = 0;
 		$imagelist[$includedeleted][$row["id"]] = $row;
+		$imagelist[$includedeleted][$row["id"]]['checkuser'] = 1;
+		$imagelist[$includedeleted][$row["id"]]['rootaccess'] = 1;
 		if($row["imagemetaid"] != NULL) {
 			if(array_key_exists($row['imagemetaid'], $allmetadata)) {
 				$metaid = $row['imagemetaid'];
@@ -1331,7 +1330,7 @@ function getImages($includedeleted=0, $i
 				}
 			}
 			else
-				$row["imagemetaid"] = NULL;
+				$imagelist[$includedeleted][$row["id"]]["imagemetaid"] = NULL;
 		}
 		if(array_key_exists($row['id'], $allrevisiondata))
 			$imagelist[$includedeleted][$row['id']]['imagerevision'] = $allrevisiondata[$row['id']];
@@ -1344,6 +1343,131 @@ function getImages($includedeleted=0, $i
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
+/// \fn getServerProfiles($id)
+///
+/// \param $id - (optional) if specified, only return data for specified profile
+///
+/// \return an array where each key is a profile id whose value is an array with
+/// these values:\n
+/// \b name - profile name\n
+/// \b description - profile description\n
+/// \b imageid - id of image associated with profile\n
+/// \b image - pretty name of image associated with profile\n
+/// \b ownerid - user id of owner of profile\n
+/// \b owner - unityid of owner of profile\n
+/// \b fixedIP - IP address to be used with deployed profile\n
+/// \b fixedMAC - MAC address to be used with deployed profile\n
+/// \b admingroupid - id of admin user group associated with profile\n
+/// \b admingroup - name of admin user group associated with profile\n
+/// \b logingroupid - id of login user group associated with profile\n
+/// \b logingroup - name of login user group associated with profile\n
+/// \b monitored - whether or not deployed profile should be monitored\n
+/// \b resourceid - resource id of profile
+///
+/// \brief gets information about server profiles
+///
+////////////////////////////////////////////////////////////////////////////////
+function getServerProfiles($id=0) {
+	$key = getKey(array('getServerProfiles', $id));
+	if(array_key_exists($key, $_SESSION['usersessiondata']))
+		return $_SESSION['usersessiondata'][$key];
+
+	$fixeddata = array();
+	$query = "SELECT name, value FROM variable WHERE name LIKE 'fixedIPsp%'";
+	$qh = doQuery($query);
+	while($row = mysql_fetch_assoc($qh)) {
+		$spid = str_replace('fixedIPsp', '', $row['name']);
+		$fixeddata[$spid] = Spyc::YAMLLoad($row['value']);
+	}
+
+	$query = "SELECT s.id, "
+	       .        "s.name, "
+	       .        "s.description, "
+	       .        "s.imageid, "
+	       .        "i.prettyname AS image, "
+	       .        "s.ownerid, "
+	       .        "CONCAT(u.unityid, '@', a.name) AS owner, "
+	       .        "s.fixedIP, "
+	       .        "s.fixedMAC, "
+	       .        "s.admingroupid, "
+	       .        "CONCAT(ga.name, '@', aa.name) AS admingroup, "
+	       .        "s.logingroupid, "
+	       .        "CONCAT(gl.name, '@', al.name) AS logingroup, "
+	       .        "s.monitored, "
+	       .        "r.id AS resourceid "
+	       . "FROM serverprofile s "
+	       . "LEFT JOIN image i ON (i.id = s.imageid) "
+	       . "LEFT JOIN user u ON (u.id = s.ownerid) "
+	       . "LEFT JOIN affiliation a ON (a.id = u.affiliationid) "
+	       . "LEFT JOIN usergroup ga ON (ga.id = s.admingroupid) "
+	       . "LEFT JOIN affiliation aa ON (aa.id = ga.affiliationid) "
+	       . "LEFT JOIN usergroup gl ON (gl.id = s.logingroupid) "
+	       . "LEFT JOIN affiliation al ON (al.id = gl.affiliationid) "
+	       . "LEFT JOIN resource r ON (r.subid = s.id) "
+	       . "WHERE r.resourcetypeid = 17 ";
+	if($id != 0)
+		$query .= "AND s.id = $id";
+	else
+		$query .= "ORDER BY name";
+	$qh = doQuery($query, 101);
+	$profiles = array();
+	while($row = mysql_fetch_assoc($qh)) {
+		$profiles[$row['id']] = $row;
+		if(array_key_exists($row['id'], $fixeddata)) {
+			$profiles[$row['id']]['netmask'] = $fixeddata[$row['id']]['netmask'];
+			$profiles[$row['id']]['router'] = $fixeddata[$row['id']]['router'];
+			$profiles[$row['id']]['dns'] = implode(',', $fixeddata[$row['id']]['dns']);
+		}
+		else {
+			$profiles[$row['id']]['netmask'] = '';
+			$profiles[$row['id']]['router'] = '';
+			$profiles[$row['id']]['dns'] = '';
+		}
+	}
+	$_SESSION['usersessiondata'][$key] = $profiles;
+	return $profiles;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn getServerProfileImages($userid)
+///
+/// \param $userid - id from user table
+///
+/// \return array where the key is the id of the image and the value is the
+/// prettyname of the image
+///
+/// \brief builds an array of images that user has access to via server profiles
+///
+////////////////////////////////////////////////////////////////////////////////
+function getServerProfileImages($userid) {
+	$key = getKey(array('getServerProfileImages', $userid));
+	if(array_key_exists($key, $_SESSION['usersessiondata']))
+		return $_SESSION['usersessiondata'][$key];
+	$resources = getUserResources(array('serverCheckOut', 'serverProfileAdmin'),
+	                              array('available', 'administer'));
+	$ids = array_keys($resources['serverprofile']);
+	$inids = implode(',', $ids);
+	if(empty($inids)) {
+		$_SESSION['usersessiondata'][$key] = array();
+		return array();
+	}
+	$query = "SELECT i.id, "
+	       .        "i.prettyname AS image "
+	       . "FROM serverprofile s, "
+	       .      "image i "
+	       . "WHERE s.imageid = i.id AND "
+	       .       "s.id IN ($inids)";
+	$qh = doQuery($query, 101);
+	$profiles = array();
+	while($row = mysql_fetch_assoc($qh))
+		$profiles[$row['id']] = $row['image'];
+	$_SESSION['usersessiondata'][$key] = $profiles;
+	return $profiles;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
 /// \fn getImageRevisions($imageid, $incdeleted)
 ///
 /// \param $imageid - id of an image
@@ -1420,10 +1544,12 @@ function getImageNotes($imageid) {
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn getImageConnectMethods($imageid, $revisionid)
+/// \fn getImageConnectMethods($imageid, $revisionid, $nostatic=0)
 ///
 /// \param $imageid - id of an image
 /// \param $revisionid - (optional, default=0) revision id of image
+/// \param $nostatic - (optional, default=0) pass 1 to keep from using the
+/// static variable defined in the function
 ///
 /// \return an array of connect methods enabled for specified image where the
 /// key is the id of the connect method and the value is the description
@@ -1431,18 +1557,20 @@ function getImageNotes($imageid) {
 /// \brief builds an array of connect methods enabled for the image
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function getImageConnectMethods($imageid, $revisionid=0) {
-	$key = getKey(array('getImageConnectMethods', $imageid, $revisionid));
+function getImageConnectMethods($imageid, $revisionid=0, $nostatic=0) {
+	$key = getKey(array('getImageConnectMethods', (int)$imageid, (int)$revisionid));
 	if(array_key_exists($key, $_SESSION['usersessiondata']))
 		return $_SESSION['usersessiondata'][$key];
 	if($revisionid == 0)
-		$revisionid = getProductionRevisionid($imageid);
+		$revisionid = getProductionRevisionid($imageid, $nostatic);
 	if($revisionid == '') {
 		$_SESSION['usersessiondata'][$key] = array();
 		return array();
 	}
 
 	static $allmethods = array();
+	if($nostatic)
+		$allmethods = array();
 	if(empty($allmethods)) {
 		$query = "SELECT DISTINCT c.id, "
 		      .                  "c.description, "
@@ -1622,9 +1750,11 @@ function checkClearImageMeta($imagemetai
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn getProductionRevisionid($imageid)
+/// \fn getProductionRevisionid($imageid, $nostatic=0)
 ///
 /// \param $imageid
+/// \param $nostatic - (optional, default=0) pass 1 to keep from using the
+/// static variable defined in the function
 ///
 /// \return the production revision id for $imageid
 ///
@@ -1632,8 +1762,10 @@ function checkClearImageMeta($imagemetai
 /// table
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function getProductionRevisionid($imageid) {
+function getProductionRevisionid($imageid, $nostatic=0) {
 	static $alldata = array();
+	if($nostatic)
+		$alldata = array();
 	if(! empty($alldata))
 		if(array_key_exists($imageid, $alldata))
 			return $alldata[$imageid];
@@ -1709,6 +1841,8 @@ function removeNoCheckout($images) {
 function getUserResources($userprivs, $resourceprivs=array("available"),
                           $onlygroups=0, $includedeleted=0, $userid=0) {
 	global $user;
+	if(in_array('managementnodeAdmin', $userprivs))
+		$userprivs[] = 'mgmtnodeAdmin';
 	$key = getKey(array($userprivs, $resourceprivs, $onlygroups, $includedeleted, $userid));
 	if(array_key_exists($key, $_SESSION['userresources']))
 		return $_SESSION['userresources'][$key];
@@ -1840,6 +1974,9 @@ function getUserResources($userprivs, $r
 		   getResourcesFromGroups($resourcegroups[$type], $type, $includedeleted);
 	}
 	addOwnedResources($resources, $includedeleted, $userid);
+	$noimageid = getImageId('noimage');
+	if(array_key_exists($noimageid, $resources['image']))
+		unset($resources['image'][$noimageid]);
 	$_SESSION['userresources'][$key] = $resources;
 	return $resources;
 }
@@ -2082,23 +2219,25 @@ function addUserResources(&$nodeprivs, $
 ////////////////////////////////////////////////////////////////////////////////
 function addOwnedResources(&$resources, $includedeleted, $userid) {
 	foreach(array_keys($resources) as $type) {
-		if($type == "image")
-			$field = "prettyname";
-		elseif($type == "computer")
-			$field = "hostname";
-		elseif($type == "schedule")
-			$field = "name";
-		elseif($type == "managementnode")
-			$field = "hostname";
-		elseif($type == "serverprofile")
-			$field = "name";
-		else
-			continue;
+		# TODO make this get name field from classes
+		switch($type) {
+			case "image":
+				$field = 'prettyname';
+				break;
+			case "computer":
+			case "managementnode":
+				$field = 'hostname';
+				break;
+			default:
+				$field = 'name';
+				break;
+		}
 		$query = "SELECT id, "
 		       .        "$field "
 		       . "FROM $type "
 		       . "WHERE ownerid = $userid";
-		if(! $includedeleted && ($type == "image" || $type == "computer"))
+		if(! $includedeleted &&
+		   ($type == "image" || $type == "computer" || $type == 'config'))
 			$query .= " AND deleted = 0";
 		$qh = doQuery($query, 101);
 		while($row = mysql_fetch_assoc($qh)) {
@@ -2157,18 +2296,18 @@ function addOwnedResourceGroups(&$resour
 ////////////////////////////////////////////////////////////////////////////////
 function getResourcesFromGroups($groups, $type, $includedeleted) {
 	$return = array();
-	if($type == "image")
-		$field = "prettyname";
-	elseif($type == "computer")
-		$field = "hostname";
-	elseif($type == "schedule")
-		$field = "name";
-	elseif($type == "managementnode")
-		$field = "hostname";
-	elseif($type == "serverprofile")
-		$field = "name";
-	else
-		return array();
+	switch($type) {
+		case "image":
+			$field = 'prettyname';
+			break;
+		case "computer":
+		case "managementnode":
+			$field = 'hostname';
+			break;
+		default:
+			$field = 'name';
+			break;
+	}
 
 	$groups = implode("','", $groups);
 	$inlist = "'$groups'";
@@ -2186,7 +2325,8 @@ function getResourcesFromGroups($groups,
 	       .       "g.name IN ($inlist) AND "
 	       .       "g.resourcetypeid = rt.id AND "
 	       .       "rt.name = '$type'";
-	if(! $includedeleted && ($type == "image" || $type == "computer")) {
+	if(! $includedeleted &&
+	   ($type == "image" || $type == "computer" || $type == 'config')) {
 		$query .= "AND deleted = 0 ";
 	}
 	/*if($type == "image")
@@ -2354,17 +2494,18 @@ function decryptData($data) {
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-//
-//  \fn encryptDataAsymmetric($data, $public_key)
-//
-//  \param $data - a string
-//
-//  \param $public_key - either a filename for a public key or the public key itself
-//
-//  \return hex-encoded, encrypted form of $data
-//
-//  \brief generate public key encrypted data
-//
+///
+/// \fn encryptDataAsymmetric($data, $public_key)
+///
+/// \param $data - a string
+///
+/// \param $public_key - either a filename for a public key or the public key
+/// itself
+///
+/// \return hex-encoded, encrypted form of $data
+///
+/// \brief generate public key encrypted data
+///
 ////////////////////////////////////////////////////////////////////////////////
 function encryptDataAsymmetric($data, $public_key){
 	if(file_exists($public_key)){
@@ -3176,7 +3317,7 @@ function processInputVar($vartag, $type,
 		if(! is_string($return)) {
 			print "ERROR (code:3)<br>\n";
 			printHTMLFooter();
-			semUnlock();
+			cleanSemaphore();
 			exit();
 		}
 		#print "before - $return<br>\n";
@@ -3198,7 +3339,7 @@ function processInputVar($vartag, $type,
 			if(! is_string($value)) {
 				print "ERROR (code:3)<br>\n";
 				printHTMLFooter();
-				semUnlock();
+				cleanSemaphore();
 				exit();
 			}
 		}
@@ -3406,6 +3547,8 @@ function getUserInfo($id, $noupdate=0, $
 	       .        "u.mapserial AS mapserial, "
 	       .        "u.showallgroups, "
 	       .        "u.lastupdated AS lastupdated, "
+	       .        "u.usepublickeys, "
+	       .        "u.sshpublickeys, "
 	       .        "af.shibonly "
 	       . "FROM user u, "
 	       .      "IMtype i, "
@@ -3419,6 +3562,7 @@ function getUserInfo($id, $noupdate=0, $
 
 	$qh = doQuery($query, "105");
 	if($user = mysql_fetch_assoc($qh)) {
+		$user['sshpublickeys'] = htmlspecialchars($user['sshpublickeys']);
 		if((datetimeToUnix($user["lastupdated"]) > time() - SECINDAY) ||
 		   $user['unityid'] == 'vclreload' ||
 		   $user['affiliation'] == 'Local' ||
@@ -3723,9 +3867,10 @@ function getOverallUserPrivs($userid) {
 	       .                         "WHERE ownerid = $userid))";
 	$qh = doQuery($query, 107);
 	$privileges = array();
-	while($row = mysql_fetch_row($qh)) {
-		array_push($privileges, $row[0]);
-	}
+	while($row = mysql_fetch_row($qh))
+		$privileges[] = $row[0];
+	if(in_array("mgmtNodeAdmin", $privileges))
+		$privileges[] = 'managementnodeAdmin';
 	return $privileges;
 }
 
@@ -3765,14 +3910,17 @@ function getBlockAllocationIDs($user) {
 ////////////////////////////////////////////////////////////////////////////////
 ///
 /// \fn isAvailable($images, $imageid, $imagerevisionid, $start, $end,
-///                 $requestid, $userid, $ignoreprivileges, $forimaging, $ip,
-///                 $mac, $skipconcurrentcheck)
+///                 $holdcomps, $requestid, $userid, $ignoreprivileges,
+///                 $forimaging, $ip, $mac, $skipconcurrentcheck)
 ///
 /// \param $images - array as returned from getImages
 /// \param $imageid - imageid from the image table
 /// \param $imagerevisionid - id of revision of image from imagerevision table
 /// \param $start - unix timestamp for start of reservation
 /// \param $end - unix timestamp for end of reservation
+/// \param $holdcomps - bool - 1 to lock computers for later adding to
+/// reservation, 0 not to; use 0 when just checking for estimated availability,
+/// use 1 when finding computers to actually reserve
 /// \param $requestid - (optional) a requestid; if checking for an available
 /// timeslot to update a request, pass the request id that will be updated;
 /// otherwise, don't pass this argument
@@ -3790,7 +3938,8 @@ function getBlockAllocationIDs($user) {
 /// \param $skipconcurrentcheck (optional, default=0) - set to 1 to skip check
 /// for concurrent use of image; useful for setting up reload reservations
 ///
-/// \return -3 if unavailable due to an ip/mac conflict
+/// \return -4 if unavailable due to an ip/mac conflict with another machine
+///         -3 if unavailable due to an ip/mac conflict with another reservation
 ///         -2 if specified time period is during a maintenance window
 ///         -1 if $imageid is limited in the number of concurrent reservations
 ///         available, and the limit has been reached
@@ -3801,16 +3950,25 @@ function getBlockAllocationIDs($user) {
 ///
 ////////////////////////////////////////////////////////////////////////////////
 function isAvailable($images, $imageid, $imagerevisionid, $start, $end,
-                     $requestid=0, $userid=0, $ignoreprivileges=0,
+                     $holdcomps, $requestid=0, $userid=0, $ignoreprivileges=0,
                      $forimaging=0, $ip='', $mac='', $skipconcurrentcheck=0) {
-	global $requestInfo;
+	global $requestInfo, $user;
 	$requestInfo["start"] = $start;
 	$requestInfo["end"] = $end;
 	$requestInfo["imageid"] = $imageid;
+	$requestInfo["ipwarning"] = 0;
 	$allocatedcompids = array(0);
 
+	if(! is_array($imagerevisionid))
+		$imagerevisionid = array($imageid => array($imagerevisionid));
+	elseif(empty($imagerevisionid))
+		$imagerevisionid = array($imageid => array(getProductionRevisionid($imageid)));
+
 	if(schCheckMaintenance($start, $end))
-		return -2;
+		return debugIsAvailable(-2, 1, $start, $end, $imagerevisionid);
+
+	if(! array_key_exists($imageid, $images))
+		return debugIsAvailable(0, 20, $start, $end, $imagerevisionid);
 
 	if($requestInfo["start"] <= time()) {
 		$now = 1;
@@ -3826,26 +3984,29 @@ function isAvailable($images, $imageid, 
 	$requestInfo["computers"] = array();
 	$requestInfo["computers"][0] = 0;
 	$requestInfo["images"][0] = $imageid;
+	$requestInfo["imagerevisions"][0] = $imagerevisionid[$imageid][0];
 
-	# loop to check for available computers for all needed images
+	# build array of subimages
+	# TODO handle mininstance
 	if(! $forimaging && $images[$imageid]["imagemetaid"] != NULL) {
 		$count = 1;
 		foreach($images[$imageid]["subimages"] as $imgid) {
 			$requestInfo['computers'][$count] = 0;
 			$requestInfo['images'][$count] = $imgid;
+			if(array_key_exists($imgid, $imagerevisionid) &&
+			   array_key_exists($count, $imagerevisionid[$imgid]))
+				$requestInfo['imagerevisions'][$count] = $imagerevisionid[$imgid][$count];
+			else
+				$requestInfo['imagerevisions'][$count] = getProductionRevisionid($imgid);
 			$count++;
 		}
 	}
 
-	// get semaphore lock
-	if(! semLock())
-		abort(3);
-
 	$startstamp = unixToDatetime($start);
 	$endstamp = unixToDatetime($end + 900);
 
-	# check for overlapping use of mac or ip
 	if(! empty($mac) || ! empty($ip)) {
+		# check for overlapping use of mac or ip
 		$query = "SELECT rq.id "
 		       . "FROM reservation rs, "
 		       .      "request rq, "
@@ -3862,8 +4023,17 @@ function isAvailable($images, $imageid, 
 		$query .= "LIMIT 1";
 		$qh = doQuery($query, 101);
 		if(mysql_num_rows($qh)) {
-			semUnlock();
-			return -3;
+			return debugIsAvailable(-3, 2, $start, $end, $imagerevisionid);
+		}
+
+		# check for IP being used by a management node
+		$query = "SELECT id "
+		       . "FROM managementnode "
+		       . "WHERE IPaddress = '$ip' AND "
+		       .       "stateid != 1";
+		$qh = doQuery($query, 101);
+		if(mysql_num_rows($qh)) {
+			return debugIsAvailable(-4, 16, $start, $end, $imagerevisionid);
 		}
 	}
 
@@ -3874,10 +4044,19 @@ function isAvailable($images, $imageid, 
 	$ignorestates = "'maintenance','vmhostinuse','hpc','failed'";
 	if($now)
 		$ignorestates .= ",'reloading','reload','timeout','inuse'";
+
 	foreach($requestInfo["images"] as $key => $imageid) {
 		# check for max concurrent usage of image
 		if(! $skipconcurrentcheck && 
 		   $images[$imageid]['maxconcurrent'] != NULL) {
+			if($userid == 0)
+				$usersgroups = $user['groups'];
+			else {
+				$testuser = getUserInfo($userid, 0, 1);
+				if(is_null($testuser))
+					return debugIsAvailable(0, 17, $start, $end, $imagerevisionid);
+				$usersgroups = $testuser['groups'];
+			}
 			$decforedit = 0;
 			$compids = array();
 			$reloadid = getUserlistID('vclreload@Local');
@@ -3899,6 +4078,7 @@ function isAvailable($images, $imageid, 
 			}
 			$usagecnt = count($compids);
 			$allids = implode("','", $compids);
+			$ignoregroups = implode("','", array_keys($usersgroups));
 			$query = "SELECT COUNT(bc.imageid) AS currentusage "
 			       . "FROM blockComputers bc, "
 			       .      "blockRequest br, "
@@ -3907,34 +4087,55 @@ function isAvailable($images, $imageid, 
 			       .       "bt.blockRequestid = br.id AND "
 			       .       "bc.imageid = $imageid AND "
 			       .       "bc.computerid NOT IN ('$allids') AND "
+			       .       "br.groupid NOT IN ('$ignoregroups') AND "
 			       .       "'$startstamp' < (bt.end + INTERVAL 900 SECOND) AND "
 			       .       "'$endstamp' > bt.start AND "
 			       .       "bt.skip != 1 AND "
 			       .       "br.status != 'deleted'";
 			$qh = doQuery($query);
 			if(! $row = mysql_fetch_assoc($qh)) {
-				semUnlock();
-				return 0;
+				cleanSemaphore();
+				return debugIsAvailable(0, 3, $start, $end, $imagerevisionid);
 			}
 			if(($usagecnt + $row['currentusage'] - $decforedit) >= $images[$imageid]['maxconcurrent']) {
-				semUnlock();
-				return -1;
+				cleanSemaphore();
+				return debugIsAvailable(-1, 4, $start, $end, $imagerevisionid);
 			}
 		}
 
 		$platformid = getImagePlatform($imageid);
 		if(is_null($platformid)) {
-			semUnlock();
-			return 0;
+			cleanSemaphore();
+			return debugIsAvailable(0, 5, $start, $end, $imagerevisionid);
 		}
 
 		# get computers $imageid maps to
-		$tmp = getMappedResources($imageid, "image", "computer");
-		if(! count($tmp)) {
-			semUnlock();
-			return 0;
+		$compids = getMappedResources($imageid, "image", "computer");
+		if(! count($compids)) {
+			cleanSemaphore();
+			return debugIsAvailable(0, 6, $start, $end, $imagerevisionid);
+		}
+		$mappedcomputers = implode(',', $compids);
+
+		// if $ip specified, only look at computers under management nodes that can
+		#   handle that network
+		if($ip != '') {
+			$mappedmns = getMnsFromImage($imageid);
+			$mnnets = checkAvailableNetworks($ip);
+			$intersect = array_intersect($mappedmns, $mnnets);
+			$tmpcompids = array();
+			foreach($intersect as $mnid) {
+				$tmp2 = getMappedResources($mnid, 'managementnode', 'computer');
+				$tmpcompids = array_merge($tmpcompids, $tmp2);
+			}
+			$tmpcompids = array_unique($tmpcompids);
+			$newcompids = array_intersect($compids, $tmpcompids);
+			if(! count($newcompids)) {
+				cleanSemaphore();
+				return debugIsAvailable(0, 18, $start, $end, $imagerevisionid);
+			}
+			$mappedcomputers = implode(',', $newcompids);
 		}
-		$mappedcomputers = implode(',', $tmp);
 
 		#get computers for available schedules and platforms
 		$computerids = array();
@@ -3960,8 +4161,8 @@ function isAvailable($images, $imageid, 
 			$qh = doQuery($query, 128);
 			$row = mysql_fetch_row($qh);
 			if(! in_array($row[0], $scheduleids)) {
-				semUnlock();
-				return 0;
+				cleanSemaphore();
+				return debugIsAvailable(0, 7, $start, $end, $imagerevisionid, $computerids, $currentids, $blockids);
 			}
 			// set $virtual to 0 so that it is defined later but skips the additional code
 			$virtual = 0;
@@ -3975,9 +4176,10 @@ function isAvailable($images, $imageid, 
 			       . "WHERE i.id = $imageid";
 			$qh = doQuery($query, 101);
 			if(! ($row = mysql_fetch_assoc($qh))) {
-				semUnlock();
-				return 0;
+				cleanSemaphore();
+				return debugIsAvailable(0, 8, $start, $end, $imagerevisionid, $computerids, $currentids, $blockids);
 			}
+			# TODO might need to check for other strings for KVM, OpenStack, etc
 			if(preg_match('/(vmware)/', $row['installtype']))
 				$virtual = 1;
 			else
@@ -4006,6 +4208,7 @@ function isAvailable($images, $imageid, 
 			       . "LEFT JOIN OSinstalltype oi ON (oi.name = o.installtype) "
 			       . "LEFT JOIN provisioningOSinstalltype poi ON (poi.OSinstalltypeid = oi.id) "
 			       . "LEFT JOIN computer c ON (poi.provisioningid = c.provisioningid) "
+			       . "LEFT JOIN semaphore se ON (c.id = se.computerid) "
 			       . "WHERE i.id = $imageid AND "
 			       .       "c.scheduleid IN ($schedules) AND "
 			       .       "c.platformid = $platformid AND "
@@ -4020,16 +4223,17 @@ function isAvailable($images, $imageid, 
 			if(! $ignoreprivileges)
 				$query .=   "c.id IN ($usercomputers) AND ";
 			$query .=      "c.id IN ($mappedcomputers) AND "
-			       .       "c.id NOT IN ($alloccompids) "
-			       . "ORDER BY (c.procspeed * c.procnumber) DESC, "
-			       .          "RAM DESC, "
-			       .          "network DESC";
+			       .       "c.id NOT IN ($alloccompids) AND "
+			       .       "(se.expires IS NULL OR se.expires < NOW()) "
+			       . "ORDER BY RAM, "
+			       .          "(c.procspeed * c.procnumber), "
+			       .          "network";
 
 			$qh = doQuery($query, 129);
 			while($row = mysql_fetch_assoc($qh)) {
 				array_push($computerids, $row['id']);
 				if($row['currentimageid'] == $imageid &&
-				   $row['imagerevisionid'] == $imagerevisionid) {
+				   $row['imagerevisionid'] == $requestInfo['imagerevisions'][$key]) {
 					array_push($currentids, $row['id']);
 				}
 			}
@@ -4039,6 +4243,10 @@ function isAvailable($images, $imageid, 
 			$blockids = $blockdata['compids'];
 		}
 
+		# return 0 if no computers available
+		if(empty($computerids) && empty($blockids))
+			return debugIsAvailable(0, 21, $start, $end, $imagerevisionid, $computerids, $currentids, $blockids, array(), $virtual);
+
 		#remove computers from list that are already scheduled
 		$usedComputerids = array();
 		$query = "SELECT DISTINCT rs.computerid "
@@ -4060,15 +4268,19 @@ function isAvailable($images, $imageid, 
 
 		// if modifying a reservation and $computerids is now empty, return 0
 		if($requestid && empty($computerids)) {
-			semUnlock();
-			return 0;
+			cleanSemaphore();
+			return debugIsAvailable(0, 9, $start, $end, $imagerevisionid, $computerids, $currentids, $blockids, array(), $virtual);
 		}
 
+		# return 0 if no computers available
+		if(empty($computerids) && empty($currentids) && empty($blockids))
+			return debugIsAvailable(0, 19, $start, $end, $imagerevisionid, $computerids, $currentids, $blockids, array(), $virtual);
+
 		# remove computers from list that are allocated to block allocations
 		if($altRemoveBlockCheck) {
 			if(editRequestBlockCheck($computerids[0], $imageid, $start, $end)) {
-				semUnlock();
-				return 0;
+				cleanSemaphore();
+				return debugIsAvailable(0, 10, $start, $end, $imagerevisionid, $computerids, $currentids, $blockids, array(), $virtual);
 			}
 		}
 		elseif(! count($blockids)) {  # && ! $altRemoveBlockCheck
@@ -4104,7 +4316,7 @@ function isAvailable($images, $imageid, 
 				       . "LEFT JOIN computer c2 ON (v.id = c2.vmhostid) "
 				       . "LEFT JOIN image i ON (c2.currentimageid = i.id) "
 				       . "WHERE c.stateid = 20 "
-				       . "GROUP BY c.id";
+				       . "GROUP BY v.id";
 				doQuery($query, 101);
 			}
 
@@ -4116,9 +4328,9 @@ function isAvailable($images, $imageid, 
 			       . "LEFT JOIN image i ON (c.currentimageid = i.id) "
 			       . "WHERE c.id IN ($inids) AND "
 			       .       "(v.allocRAM - i.minram + {$images[$imageid]['minram']}) < v.RAM "
-			       . "ORDER BY (c.procspeed * c.procnumber) DESC, "
-			       .          "c.RAM DESC, "
-			       .          "c.network DESC";
+			       . "ORDER BY c.RAM, "
+			       .          "(c.procspeed * c.procnumber), "
+			       .          "c.network";
 			$qh = doQuery($query, 101);
 			$newcompids = array();
 			while($row = mysql_fetch_assoc($qh))
@@ -4126,6 +4338,48 @@ function isAvailable($images, $imageid, 
 			$computerids = $newcompids;
 		}
 
+		# check for use of specified IP address, have to wait until here
+		#   because there may be a computer already assigned the IP that
+		#   can be used for this reservation
+		if(! empty($ip) && $now) {
+			$allcompids = array_merge($computerids, $blockids);
+			if(empty($allcompids))
+				return debugIsAvailable(0, 13, $start, $end, $imagerevisionid, $computerids, $currentids, $blockids, array(), $virtual);
+			$inids = implode(',', $allcompids);
+			$query = "SELECT id "
+			       . "FROM computer "
+			       . "WHERE id NOT IN ($inids) AND "
+			       .       "deleted = 0 AND "
+			       .       "stateid != 1 AND "
+			       .       "IPaddress = '$ip' AND "
+			       .       "(type != 'virtualmachine' OR "
+			       .       "vmhostid IS NOT NULL)";
+			$qh = doQuery($query);
+			if(mysql_num_rows($qh)) {
+				if($now)
+					return debugIsAvailable(-4, 18, $start, $end, $imagerevisionid, $computerids, $currentids, $blockids, array(), $virtual);
+				$requestInfo['ipwarning'] = 1;
+			}
+			$query = "SELECT id "
+			       . "FROM computer "
+			       . "WHERE id in ($inids) AND "
+			       .       "IPaddress = '$ip'";
+			if($requestid)
+				$query .= " AND id != $compid"; # TODO test this
+			$qh = doQuery($query);
+			$cnt = mysql_num_rows($qh);
+			if($cnt > 1) {
+				if($now)
+					return debugIsAvailable(-4, 19, $start, $end, $imagerevisionid, $computerids, $currentids, $blockids, array(), $virtual);
+				$requestInfo['ipwarning'] = 1;
+			}
+			elseif($cnt == 1) {
+				$row = mysql_fetch_assoc($qh);
+				$computerids = array($row['id']);
+				$blockids = array();
+			}
+		}
+
 		# remove any recently reserved computers that could have been an
 		#   undetected failure
 		$failedids = getPossibleRecentFailures($userid, $imageid);
@@ -4152,14 +4406,17 @@ function isAvailable($images, $imageid, 
 		}
 
 		# allocate a computer
+		$_imgrevid = $requestInfo['imagerevisions'][$key];
 		$comparr = allocComputer($blockids, $currentids, $computerids,
-		                         $startstamp, $nowfuture);
+		                         $startstamp, $endstamp, $nowfuture, $imageid, $_imgrevid,
+		                         $holdcomps, $requestid);
 		if(empty($comparr) && $shortened)
 			$comparr = allocComputer($origblockids, $origcurrentids,
-			                         $origcomputerids, $startstamp, $nowfuture);
+			                         $origcomputerids, $startstamp, $endstamp, $nowfuture,
+			                         $imageid, $_imgrevid, $holdcomps, $requestid);
 		if(empty($comparr)) {
-			semUnlock();
-			return 0;
+			cleanSemaphore();
+			return debugIsAvailable(0, 11, $start, $end, $imagerevisionid, $computerids, $currentids, $blockids, $failedids, $virtual);
 		}
 
 		$requestInfo["computers"][$key] = $comparr['compid'];
@@ -4171,7 +4428,110 @@ function isAvailable($images, $imageid, 
 		array_push($allocatedcompids, $comparr['compid']);
 	}
 
-	return 1;
+	return debugIsAvailable(1, 12, $start, $end, $imagerevisionid, $computerids, $currentids, $blockids, array(), $virtual);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn debugIsAvailable($rc, $loc, $start, $end, $imagerevisionid,
+///                      $compids=array(), $currentids=array(),
+///                      $blockids=array(), $failedids=array(), $virtual='')
+///
+/// \param $rc - return code
+/// \param $loc - location from isAvailable function
+/// \param $start - as passed to isAvailable function
+/// \param $end - as passed to isAvailable function
+/// \param $imagerevisionid - as passed to isAvailable function
+/// \param $compids - $computerids from isAvailable
+/// \param $currentids - $currentids from isAvailable
+/// \param $blockids - $blockids from isAvailable
+/// \param $failedids - $failedids from isAvailable
+/// \param $virtual - $virtual from isAvailable
+///
+/// \return $rc as passed in
+///
+/// \brief prints debug line about why isAvailable returned specified return
+/// code in javascript console if user has 'View Debug Information' user group
+/// permission and has set the debug flag; most data passed in is currently
+/// unused, but allows for a single place to add code for debugging
+///
+////////////////////////////////////////////////////////////////////////////////
+function debugIsAvailable($rc, $loc, $start, $end, $imagerevisionid,
+	                       $compids=array(), $currentids=array(),
+	                       $blockids=array(), $failedids=array(), $virtual='') {
+	global $user, $mode;
+	$debug = getContinuationVar('debug', 0);
+	if(! $debug ||
+	   $mode != 'AJupdateWaitTime' ||
+	   ! checkUserHasPerm('View Debug Information'))
+		return $rc;
+	switch($loc) {
+		case "1":
+			$msg = "site maintenance is scheduled for the requested time";
+			break;
+		case "20":
+			$msg = "invalid image id submitted - not found in images available to the user";
+			break;
+		case "2":
+			$msg = "an overlapping server reservation has the same fixed IP or MAC address";
+			break;
+		case "16":
+			$msg = "the requested fixed IP address is currently in use by a management node";
+			break;
+		case "17":
+			$msg = "failed to look up information about the specified user";
+			break;
+		case "3":
+			$msg = "failed to get image usage count for block allocations";
+			break;
+		case "4":
+			$msg = "max concurrent usage of image exceeded (includes those set aside for block allocations)";
+			break;
+		case "5":
+			$msg = "failed to get platform of image";
+			break;
+		case "6":
+			$msg = "image is not mapped to any computers";
+			break;
+		case "18":
+			$msg = "no available computers under a management node that can handle the specified IP address";
+			break;
+		case "7":
+			$msg = "the schedule of the currently reserved computer does not allow for the requested time";
+			break;
+		case "8":
+			$msg = "failed to get OSinstalltype for image";
+			break;
+		case "21":
+			$msg = "no computers with a matching schedule and platform, and in an available state, and available to the user, and mapped to the image, and matching image resource requirements";
+			break;
+		case "9":
+			$msg = "not able to change existing reservation time for currently reserved computer";
+			break;
+		case "19":
+			$msg = "no computers available (after removing scheduled computers/before performing virtual host resource checks)";
+			break;
+		case "10":
+			$msg = "modification time overlaps with time computer is set aside for block allocation";
+			break;
+		case "13":
+			$msg = "no computers available (after virtual host resource checks/before performing overlapping IP address check)";
+			break;
+		case "18":
+			$msg = "requested IP address in use by another computer";
+			break;
+		case "19":
+			$msg = "at least 2 computers have the requested IP address assigned to them";
+			break;
+		case "11":
+			$msg = "unable to get either a management node or semaphore for available computer";
+			break;
+		case "12":
+			$msg = "successfully found a computer";
+			break;
+	}
+	print "console.log('$msg');";
+	return $rc;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -4274,14 +4634,20 @@ function schCheckMaintenance($start, $en
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn allocComputer($blockids, $currentids, $computerids, $start,
-///                   $nowfuture)
+/// \fn allocComputer($blockids, $currentids, $computerids, $start, $end,
+///                   $nowfuture, $imageid, $imagerevisionid, $holdcomps,
+///                   $requestid)
 ///
 /// \param $blockids - array of computer ids
 /// \param $currentids - array of computer ids
 /// \param $computerids - array of computer ids
 /// \param $start - start time in datetime format
+/// \param $start - end time in datetime format
 /// \param $nowfuture - "now" or "future"
+/// \param $holdcomps - bool - 1 to lock computers for later adding to
+/// reservation, 0 not to; use 0 when just checking for estimated availability,
+/// use 1 when finding computers to actually reserve
+/// \param $requestid - id of request if called for editing an existing one
 ///
 /// \return empty array if failed to allocate a computer; array with these keys
 /// on success:\n
@@ -4294,8 +4660,10 @@ function schCheckMaintenance($start, $en
 /// tries to allocate a management node for it
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function allocComputer($blockids, $currentids, $computerids, $start,
-                       $nowfuture) {
+function allocComputer($blockids, $currentids, $computerids, $start, $end,
+                       $nowfuture, $imageid, $imagerevisionid, $holdcomps,
+                       $requestid) {
+	global $requestInfo;
 	$ret = array();
 	if(SCHEDULER_ALLOCATE_RANDOM_COMPUTER) {
 		shuffle($blockids);
@@ -4306,6 +4674,8 @@ function allocComputer($blockids, $curre
 		$mgmtnodeid = findManagementNode($compid, $start, $nowfuture);
 		if($mgmtnodeid == 0)
 			continue;
+		if($holdcomps && ! getSemaphore($imageid, $imagerevisionid, $mgmtnodeid, $compid, $start, $end, $requestid))
+			continue;
 		$ret['compid'] = $compid;
 		$ret['mgmtid'] = $mgmtnodeid;
 		$ret['loaded'] = 1;
@@ -4316,6 +4686,8 @@ function allocComputer($blockids, $curre
 		$mgmtnodeid = findManagementNode($compid, $start, $nowfuture);
 		if($mgmtnodeid == 0)
 			continue;
+		if($holdcomps && ! getSemaphore($imageid, $imagerevisionid, $mgmtnodeid, $compid, $start, $end, $requestid))
+			continue;
 		$ret['compid'] = $compid;
 		$ret['mgmtid'] = $mgmtnodeid;
 		$ret['loaded'] = 1;
@@ -4326,6 +4698,8 @@ function allocComputer($blockids, $curre
 		$mgmtnodeid = findManagementNode($compid, $start, $nowfuture);
 		if($mgmtnodeid == 0)
 			continue;
+		if($holdcomps && ! getSemaphore($imageid, $imagerevisionid, $mgmtnodeid, $compid, $start, $end, $requestid))
+			continue;
 		$ret['compid'] = $compid;
 		$ret['mgmtid'] = $mgmtnodeid;
 		$ret['loaded'] = 0;
@@ -4337,6 +4711,103 @@ function allocComputer($blockids, $curre
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
+/// \fn getSemaphore($imageid, $imagerevisionid, $mgmtnodeid, $compid, $start,
+///                  $end, $requestid=0)
+///
+/// \param $imageid - id of image
+/// \param $imagerevisionid - id of image revision
+/// \param $mgmtnodeid - id of management node
+/// \param $compid - id of computer
+/// \param $start - start of reservation in datetime format
+/// \param $end - end of reservation in datetime format
+/// \param $requestid - (optional) if passed, ignores checking for conflict
+/// in request table for matching id
+///
+/// \return 0 on failure, 1 on success
+///
+/// \brief tries to get a semaphore for the requested computer
+///
+////////////////////////////////////////////////////////////////////////////////
+function getSemaphore($imageid, $imagerevisionid, $mgmtnodeid, $compid, $start,
+                      $end, $requestid=0) {
+	global $mysql_link_vcl, $uniqid;
+	$query = "INSERT INTO semaphore "
+	       . "SELECT c.id, "
+	       .        "$imageid, "
+	       .        "$imagerevisionid, "
+	       .        "$mgmtnodeid, "
+	       .        "NOW() + INTERVAL " . SEMTIMEOUT . " SECOND, "
+	       .        "'$uniqid' "
+			 . "FROM computer c "
+	       . "LEFT JOIN semaphore s ON (c.id = s.computerid) "
+	       . "WHERE c.id = $compid AND "
+	       .       "(s.expires IS NULL OR s.expires < NOW()) "
+	       . "LIMIT 1";
+	doQuery($query);
+	$rc = mysql_affected_rows($mysql_link_vcl);
+
+	# check to see if another process allocated this one
+	if($rc) {
+		$query = "SELECT rq.id "
+		       . "FROM request rq, "
+		       .      "reservation rs "
+		       . "WHERE rs.requestid = rq.id AND "
+		       .       "rs.computerid = $compid AND "
+		       .       "rq.start < '$end' AND "
+		       .       "rq.end > '$start' AND "
+		       .       "rq.stateid NOT IN (1, 5, 12)";
+		if($requestid)
+			$query .= " AND rq.id != $requestid";
+		$qh = doQuery($query);
+		$rc2 = mysql_num_rows($qh);
+		if($rc2) {
+			$query = "DELETE FROM semaphore "
+			       . "WHERE computerid = $compid AND "
+			       .       "procid = '$uniqid'";
+			doQuery($query);
+			return 0;
+		}
+	}
+	return $rc;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn retryGetSemaphore($imageid, $imagerevisionid, $mgmtnodeid, $compid,
+///                       $start, $end, $requestid=0, $tries=5, $delay=200000)
+///
+/// \param $imageid - id of image
+/// \param $imagerevisionid - id of image revision
+/// \param $mgmtnodeid - id of management node
+/// \param $compid - id of computer
+/// \param $start - start of reservation in datetime format
+/// \param $end - end of reservation in datetime format
+/// \param $requestid - (optional) if passed, ignores checking for conflict
+/// \param $tries - (optional, default=5) number of attempts to make for getting
+/// a semaphore
+/// \param $delay - (optional, default=200000) microseconds to wait between
+/// tries
+///
+/// \return 0 on failure, 1 on success
+///
+/// \brief makes multiple attempts to get a semaphore for a computer; useful
+/// when needing to do an operation on a specific computer
+///
+////////////////////////////////////////////////////////////////////////////////
+function retryGetSemaphore($imageid, $imagerevisionid, $mgmtnodeid, $compid,
+                           $start, $end, $requestid=0, $tries=5, $delay=200000) {
+	for($i = 0; $i < $tries; $i++) {
+		if(getSemaphore($imageid, $imagerevisionid, $mgmtnodeid, $compid, $start, $end, $requestid)) {
+			return 1;
+		}
+		else
+			usleep($delay);
+	}
+	return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
 /// \fn getPossibleRecentFailures($userid, $imageid)
 ///
 /// \param $userid - check log data for this user; if $userid = 0, check for
@@ -4373,8 +4844,7 @@ function getPossibleRecentFailures($user
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn getMappedResources($resourcesubid, $resourcetype1,
-///                                 $resourcetype2)
+/// \fn getMappedResources($resourcesubid, $resourcetype1, $resourcetype2)
 ///
 /// \param $resourcesubid - id of a resource from its table (ie an imageid)
 /// \param $resourcetype1 - type of $resourcesubid (name or id)
@@ -4606,7 +5076,7 @@ function getMaxOverlap($userid) {
 ///
 ////////////////////////////////////////////////////////////////////////////////
 function addRequest($forimaging=0, $revisionid=array()) {
-	global $requestInfo, $user;
+	global $requestInfo, $user, $uniqid, $mysql_link_vcl;
 	$startstamp = unixToDatetime($requestInfo["start"]);
 	$endstamp = unixToDatetime($requestInfo["end"]);
 	$now = time();
@@ -4628,18 +5098,6 @@ function addRequest($forimaging=0, $revi
 	}
 	$logid = $row[0];
 
-	$query = "INSERT INTO changelog "
-	       .        "(logid, "
-	       .        "start, "
-	       .        "end, "
-	       .        "timestamp) "
-	       . "VALUES "
-	       .        "($logid, "
-	       .        "'$start', "
-	       .        "'$endstamp', "
-	       .        "NOW())";
-	doQuery($query, 136);
-
 	# add single entry to request table
 	$query = "INSERT INTO request "
 	       .        "(stateid, "
@@ -4690,24 +5148,51 @@ function addRequest($forimaging=0, $revi
 		else
 			$blockdata = array();
 
-		$query = "INSERT INTO reservation "
-		       .        "(requestid, "
-		       .        "computerid, "
-		       .        "imageid, "
-		       .        "imagerevisionid, "
-		       .        "managementnodeid) "
-		       . "VALUES "
-		       .       "($requestid, "
-		       .       "$computerid, "
-		       .       "$imageid, "
-		       .       "$imagerevisionid, "
-		       .       "$mgmtnodeid)";
-		doQuery($query, 133);
 		addSublogEntry($logid, $imageid, $imagerevisionid, $computerid,
 		               $mgmtnodeid, $fromblock, $blockdata);
 	}
+
+	$query = "INSERT INTO reservation "
+	       .        "(requestid, "
+	       .        "computerid, "
+	       .        "imageid, "
+	       .        "imagerevisionid, "
+	       .        "managementnodeid) "
+	       . "SELECT $requestid, "
+	       .        "computerid, "
+	       .        "imageid, "
+	       .        "imagerevisionid, "
+	       .        "managementnodeid " 
+	       . "FROM semaphore "
+	       . "WHERE expires > NOW() AND "
+	       .       "procid = '$uniqid'";
+	doQuery($query);
+	$cnt = mysql_affected_rows($mysql_link_vcl);
+	if($cnt == 0) {
+		# reached this point SEMTIMEOUT seconds after getting semaphore, clean up and abort
+		$query = "DELETE FROM request WHERE id = $requestid";
+		doQuery($query);
+		$query = "UPDATE log SET wasavailable = 0 WHERE id = $logid";
+		doQuery($query);
+		$query = "DELETE FROM sublog WHERE logid = $logid";
+		doQuery($query);
+		abort(400);
+	}
+	else {
+		$query = "INSERT INTO changelog "
+		       .        "(logid, "
+		       .        "start, "
+		       .        "end, "
+		       .        "timestamp) "
+		       . "VALUES "
+		       .        "($logid, "
+		       .        "'$start', "
+		       .        "'$endstamp', "
+		       .        "NOW())";
+		doQuery($query, 136);
+	}
 	// release semaphore lock
-	semUnlock();
+	cleanSemaphore();
 
 	return $requestid;
 }
@@ -5033,7 +5518,11 @@ function updateRequest($requestid) {
 		       .     "managementnodeid = $mgmtnodeid "
 		       . "WHERE requestid = $requestid AND "
 		       .       "imageid = $imgid AND "
-		       .       "computerid = $oldCompid";
+		       .       "computerid = $oldCompid "
+				 . "LIMIT 1"; # without this, it can update one row to have the
+		                    # same computer as another row; then, the later row
+		                    # could be updated, which would end up setting both
+		                    # rows to the same computer
 		doQuery($query, 147);
 		addChangeLogEntry($logid, NULL, $endstamp, $startstamp, $computerid, NULL, 
 		                  1);
@@ -5120,12 +5609,17 @@ function deleteRequest($request) {
 	}
 
 	if($request['serverrequest']) {
-		$query = "DELETE FROM serverrequest WHERE requestid = {$request['id']}";
-		$qh = doQuery($query, 152);
+		$query = "SELECT id FROM serverrequest WHERE requestid = {$request['id']}";
+		$qh = doQuery($query);
+		if($row = mysql_fetch_assoc($qh)) {
+			$query = "DELETE FROM serverrequest WHERE requestid = {$request['id']}";
+			doQuery($query, 152);
+			deleteVariable("fixedIPsr{$row['id']}");
+		}
 	}
 
 	$query = "DELETE FROM request WHERE id = {$request['id']}";
-	$qh = doQuery($query, 153);
+	doQuery($query, 153);
 
 	$query = "DELETE FROM reservation WHERE requestid = {$request['id']}";
 	doQuery($query, 154);
@@ -5145,7 +5639,8 @@ function deleteRequest($request) {
 /// reservations were found on $compid
 ///
 /// \brief attempts to move reservations off of a $compid - if $compid is not
-/// given, removes all reservations from the computer with the least number
+/// given, removes all reservations from the computer with the least number;
+/// NOTE - cleanSemaphore should be called after this by the calling function
 ///
 ////////////////////////////////////////////////////////////////////////////////
 function moveReservationsOffComputer($compid=0, $count=0) {
@@ -5198,10 +5693,14 @@ function moveReservationsOffComputer($co
 		return -1;
 	$images = getImages();
 	$allmovable = 1;
+	# TODO eventually, for clusters, there will probably be restrictions on how the computers
+	# relate to each other; at that point, this will need to be updated to make sure the computer
+	# a reservation is reassigned to meets the same restrictions
 	foreach($resInfo as $res) {
+		// pass forimaging = 1 so that isAvailable only looks at one computer
 		$rc = isAvailable($images, $res["imageid"], $res['imagerevisionid'], 
 		      datetimeToUnix($res["start"]), datetimeToUnix($res["end"]), 0,
-		      $res["userid"]);
+		      0, $res["userid"], 0, 1);
 		if($rc < 1) {
 			$allmovable = 0;
 			break;
@@ -5211,8 +5710,8 @@ function moveReservationsOffComputer($co
 		return 0;
 	foreach($resInfo as $res) {
 		$rc = isAvailable($images, $res["imageid"], $res['imagerevisionid'],
-		      datetimeToUnix($res["start"]), datetimeToUnix($res["end"]), 0, 
-		      $res["userid"]);
+		      datetimeToUnix($res["start"]), datetimeToUnix($res["end"]), 1, 
+		      0, $res["userid"], 0, 1);
 		if($rc > 0) {
 			$newcompid = array_shift($requestInfo["computers"]);
 			# get mgmt node for computer
@@ -5227,7 +5726,8 @@ function moveReservationsOffComputer($co
 			addChangeLogEntry($res['logid'], NULL, NULL, NULL, $newcompid);
 			# update sublog entry
 			$query = "UPDATE sublog "
-			       . "SET computerid = $newcompid "
+			       . "SET computerid = $newcompid, "
+			       .     "managementnodeid = $mgmtnodeid "
 			       . "WHERE logid = {$res['logid']} AND "
 			       .       "computerid = $compid";
 			doQuery($query, 101);
@@ -5240,23 +5740,71 @@ function moveReservationsOffComputer($co
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn getCompFinalReservationTime($compid)
+/// \fn moveReservationsOffVMs($compid, $sem)
+///
+/// \param $compid - id of host computer from which to move
+/// reservations
+/// \param $sem - (optional) array of data to be used when getting a semaphore
+/// for a VM after reservations have been moved off of it; must include these
+/// keys: imageid, revid, mnid, start (datetime), end (datetime)
+///
+/// \return 0 if failed to move reservations, 1 if succeeded, -1 if no
+/// reservations were found on $compid
+///
+/// \brief attempts to move reservations off of any VMs assigned to a $compid
+/// NOTE - cleanSemaphore should be called after this by the calling function
+///
+////////////////////////////////////////////////////////////////////////////////
+function moveReservationsOffVMs($compid, $sem=0) {
+	if(! is_array($sem)) {
+		$sem = array();
+		$resources = getUserResources(array("imageAdmin", "imageCheckOut"));
+		$tmp = array_keys($resources['image']);
+		$sem['imageid'] = $tmp[0];
+		$sem['revid'] = getProductionRevisionid($sem['imageid']);
+		$tmp = array_keys($resources['managementnode']);
+		$sem['mnid'] = $tmp[0];
+		$sem['start'] = unixToDatetime(time());
+		$sem['end'] = '2038-01-01 00:00:00';
+	}
+	$query = "SELECT vm.id "
+	       . "FROM computer vm, "
+	       .      "vmhost v "
+	       . "WHERE v.computerid = $compid AND "
+	       .       "vm.vmhostid = v.id";
+	$qh = doQuery($query);
+	while($row = mysql_fetch_assoc($qh)) {
+		$rc = moveReservationsOffComputer($row['id']);
+		if($rc != 0)
+			# lock computer so that reservations on other VMs on this host do not get moved to it
+			getSemaphore($sem['imageid'], $sem['revid'], $sem['mnid'], $row['id'], $sem['start'], $sem['end']);
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn getCompFinalReservationTime($compid, $extraskipstate=0)
 ///
 /// \param $compid - a computer id
+/// \param $extraskipstate - (default=0) id of an additional request state to
+/// ignore; if 0 passed, no additional states are ignored
 ///
 /// \return unix timestamp of last end time of any reservations for $compid
 ///
 /// \brief determines the final end time of all reservations on a computer
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function getCompFinalReservationTime($compid) {
+function getCompFinalReservationTime($compid, $extraskipstate=0) {
 	$end = 0;
+	$skipstates = "1,5,12";
+	if($extraskipstate)
+		$skipstates .= ",$extraskipstate";
 	$query = "SELECT UNIX_TIMESTAMP(rq.end) as end "
 	       . "FROM request rq, "
 	       .      "reservation rs "
 	       . "WHERE rs.requestid = rq.id AND "
 	       .       "rs.computerid = $compid AND "
-	       .       "rq.stateid NOT IN (1,5,12) "
+	       .       "rq.stateid NOT IN ($skipstates) "
 	       . "ORDER BY rq.end DESC "
 	       . "LIMIT 1";
 	$qh = doQuery($query, 101);
@@ -5279,6 +5827,153 @@ function getCompFinalReservationTime($co
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
+/// \fn getCompFinalVMReservationTime($hostid, $addsemaphores, $notomaintenance)
+///
+/// \param $hostid - computer id of a vm host
+/// \param $addsemaphores - (optional, default = 0) 1 to add semaphores for each
+/// of the VMs
+/// \param $notomaintenance - (optional, default = 0) 1 to ignore any
+/// tomaintenance reservations
+///
+/// \return unix timestamp of last end time of any reservations for VMs on
+/// $hostid; 0 if no reservations; -1 if $addsemaphores = 1 and failed to get
+/// semaphores
+///
+/// \brief determines the final end time of all reservations of all VMs on a
+/// VM host computer
+///
+////////////////////////////////////////////////////////////////////////////////
+function getCompFinalVMReservationTime($hostid, $addsemaphores=0,
+                                       $notomaintenance=0) {
+	global $uniqid, $mysql_link_vcl;
+	if($addsemaphores) {
+		$query = "SELECT vm.id "
+		       . "FROM computer vm, "
+		       .      "vmhost v "
+		       . "WHERE v.computerid = $hostid AND "
+		       .       "vm.vmhostid = v.id";
+		$qh = doQuery($query);
+		$compids = array();
+		while($row = mysql_fetch_assoc($qh))
+			$compids[] = $row['id'];
+		if(empty($compids))
+			return 0;
+		$allcompids = implode(',', $compids);
+		$imageid = getImageId('noimage');
+		$revid = getProductionRevisionid($imageid);
+		$tmp = getManagementNodes();
+		$tmp = array_keys($tmp);
+		$mnid = $tmp[0];
+		$query = "INSERT INTO semaphore "
+		       . "SELECT c.id, "
+		       .        "$imageid, "
+		       .        "$revid, "
+		       .        "$mnid, "
+		       .        "NOW() + INTERVAL " . SEMTIMEOUT . " SECOND, "
+		       .        "'$uniqid' "
+				 . "FROM computer c "
+		       . "LEFT JOIN semaphore s ON (c.id = s.computerid) "
+		       . "WHERE c.id IN ($allcompids) AND "
+		       .       "(s.expires IS NULL OR s.expires < NOW()) "
+		       . "GROUP BY c.id";
+		doQuery($query);
+		$cnt = mysql_affected_rows($mysql_link_vcl);
+		if($cnt != count($compids))
+			return -1;
+	}
+
+	$end = 0;
+	$skipstates = '1,5,12';
+	if($notomaintenance)
+		$skipstates .= ',18';
+	$query = "SELECT UNIX_TIMESTAMP(rq.end) as end "
+	       . "FROM request rq, "
+	       .      "reservation rs "
+	       . "WHERE rs.requestid = rq.id AND "
+	       .       "rq.stateid NOT IN ($skipstates) AND "
+	       .       "rs.computerid IN (SELECT vm.id "
+	       .                          "FROM computer vm, "
+	       .                               "vmhost v "
+	       .                          "WHERE v.computerid = $hostid AND "
+	       .                                "vm.vmhostid = v.id) "
+	       . "ORDER BY rq.end DESC "
+	       . "LIMIT 1";
+	$qh = doQuery($query, 101);
+	if($row = mysql_fetch_assoc($qh))
+		$end = $row['end'];
+	$query = "SELECT UNIX_TIMESTAMP(t.end) as end "
+	       . "FROM blockComputers c, "
+	       .      "blockTimes t "
+	       . "WHERE c.blockTimeid = t.id AND "
+	       .       "t.end > NOW() AND "
+	       .       "c.computerid IN (SELECT vm.id "
+	       .                        "FROM computer vm, "
+	       .                             "vmhost v "
+	       .                        "WHERE v.computerid = $hostid AND "
+	       .                              "vm.vmhostid = v.id) "
+	       . "ORDER BY t.end DESC "
+	       . "LIMIT 1";
+	$qh = doQuery($query, 101);
+	if($row = mysql_fetch_assoc($qh))
+		if($row['end'] > $end)
+			$end = $row['end'];
+	return $end;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn getExistingChangeStateStartTime($compid, $stateid)
+///
+/// \param $compid - computer id
+/// \param $stateid - id of state of reservation
+///
+/// \return unix timestamp
+///
+/// \brief gets the start time for the earliest existing reservation for $compid
+/// that has a state of $stateid
+///
+////////////////////////////////////////////////////////////////////////////////
+function getExistingChangeStateStartTime($compid, $stateid) {
+	$query = "SELECT rq.start "
+	       . "FROM request rq, "
+	       .      "reservation rs "
+	       . "WHERE rs.requestid = rq.id AND "
+	       .       "rs.computerid = $compid AND "
+	       .       "rq.stateid = $stateid AND "
+	       .       "rq.start > NOW() "
+	       . "ORDER BY rq.start "
+	       . "LIMIT 1";
+	$qh = doQuery($query);
+	if($row = mysql_fetch_assoc($qh))
+		return datetimeToUnix($row['start']);
+	return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn updateExistingToState($compid, $start, $stateid)
+///
+/// \param $compid - computer id
+/// \param $start - new start time in datetime format
+/// \param $stateid - id of state for existing reservation
+///
+/// \brief updates the start time of an existing reservation for $compid with a
+/// state of $stateid
+///
+////////////////////////////////////////////////////////////////////////////////
+function updateExistingToState($compid, $start, $stateid) {
+	$query = "UPDATE request rq, "
+	       .        "reservation rs "
+	       . "SET rq.start = '$start' "
+	       . "WHERE rs.requestid = rq.id AND "
+	       .       "rq.stateid = $stateid AND "
+	       .       "rq.start > '$start' AND "
+	       .       "rs.computerid = $compid";
+	doQuery($query);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
 /// \fn getUserRequests($type, $id)
 ///
 /// \param $type - "normal", "forimaging", or "all"
@@ -5850,6 +6545,50 @@ function getProvisioning() {
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
+/// \fn getProvisioningTypes()
+///
+/// \return array of provisioning types allowed for each computer type of this
+/// form:\n
+/// {[blade] => {[id] => type,\n
+///              [id] => type},\n
+///  [lab] => {[id] => type}\n
+///  [virtualmachine] => {[id] => type,\n
+///                       [id] => type}\n
+///
+/// \brief generates an array of provisioning types allowed for each computer
+/// type
+///
+////////////////////////////////////////////////////////////////////////////////
+function getProvisioningTypes() {
+	$query = "SELECT id, prettyname FROM provisioning WHERE name = 'none'";
+	$qh = doQuery($query);
+	$none = mysql_fetch_assoc($qh);
+	$query = "SELECT p.id, "
+	       .        "p.prettyname, "
+	       .        "o.name AS `type` "
+	       . "FROM provisioning p, "
+	       .      "OSinstalltype o, "
+	       .      "provisioningOSinstalltype po "
+	       . "WHERE po.provisioningid = p.id AND "
+	       .       "po.OSinstalltypeid = o.id "
+	       . "ORDER BY o.name";
+	$qh = doQuery($query);
+	$types = array('blade' => array($none['id'] => $none['prettyname']),
+	               'lab' => array(),
+	               'virtualmachine' => array());
+	while($row = mysql_fetch_assoc($qh)) {
+		if($row['type'] == 'kickstart' || $row['type'] == 'partimage')
+			$types['blade'][$row['id']] = $row['prettyname'];
+		if($row['type'] == 'none')
+			$types['lab'][$row['id']] = $row['prettyname'];
+		if($row['type'] == 'vbox' || $row['type'] == 'vmware')
+			$types['virtualmachine'][$row['id']] = $row['prettyname'];
+	}
+	return $types;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
 /// \fn getSchedules()
 ///
 /// \return array of schedules where the index are the id from the schedule table,
@@ -5857,6 +6596,7 @@ function getProvisioning() {
 /// \b name - name of schedule\n
 /// \b ownerid - user id of owner\n
 /// \b owner - unity id of owner\n
+/// \b resourceid - id from resource table\n
 /// \b times - array of start and end times for the schedule
 ///
 /// \brief gets information for schedules in schedule table
@@ -5943,11 +6683,15 @@ function formatMinOfWeek($min) {
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn getManagementNodes($alive)
+/// \fn getManagementNodes($alive, $includedeleted, $id)
 ///
 /// \param $alive - (optional) if given, only return "alive" nodes, can be
 ///                 either "now" or "future" so we know how recently it must
 ///                 have checked in
+/// \param $includedeleted - (optional, default=0) 1 to include management
+///                 nodes with a state of deleted, 0 to leave them out
+/// \param $id - (optional, default is all nodes) specify an id of a management
+///                 node to only have data for that node returned
 ///
 /// \return an array of management nodes where eash index is the id from the
 /// managementnode table and each element is an array of data about the node
@@ -5957,7 +6701,7 @@ function formatMinOfWeek($min) {
 /// if $alive = future, must have checked in within 1 hour
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function getManagementNodes($alive="neither") {
+function getManagementNodes($alive="neither", $includedeleted=0, $id=0) {
 	if($alive == "now")
 		$lastcheckin = unixToDatetime(time() - 300);
 	elseif($alive == "future")
@@ -5988,7 +6732,9 @@ function getManagementNodes($alive="neit
 	       .        "m.sharedMailBox AS sharedmailbox, "
 	       .        "r.id as resourceid, "
 	       .        "m.predictivemoduleid, "
-	       .        "mo.prettyname AS predictivemodule "
+	       .        "mo.prettyname AS predictivemodule, "
+	       .        "m.availablenetworks, "
+	       .        "m.NOT_STANDALONE AS federatedauth "
 	       . "FROM user u, "
 	       .      "state s, "
 	       .      "resource r, "
@@ -6004,6 +6750,10 @@ function getManagementNodes($alive="neit
 	       .       "rt.name = 'managementnode' AND "
 	       .       "u.affiliationid = a.id AND "
 	       .       "m.predictivemoduleid = mo.id";
+	if($id != 0)
+		$query .= " AND m.id = $id";
+	if($includedeleted == 0)
+		$query .= " AND s.name != 'deleted'";
 	if($alive == "now" || $alive == "future") {
 		$query .= " AND m.lastcheckin > '$lastcheckin'"
 		       .  " AND s.name != 'maintenance'";
@@ -6012,12 +6762,107 @@ function getManagementNodes($alive="neit
 	$return = array();
 	while($row = mysql_fetch_assoc($qh)) {
 		$return[$row["id"]] = $row;
+		$return[$row['id']]['availablenetworks'] = explode(',', $row['availablenetworks']);
+		if($row['state'] == 'deleted')
+			$return[$row['id']]['deleted'] = 1;
+		else
+			$return[$row['id']]['deleted'] = 0;
 	}
+
+	# Get items from variable table for specific management node id
+	foreach ($return as $mn_id => $value ) {
+		if(array_key_exists("hostname", $value)) {
+			$mn_hostname = $value['hostname'];
+			$timeservers = getVariable('timesource|'.$mn_hostname);
+			if($timeservers == NULL) {
+				$timeservers = getVariable('timesource|global');
+			}
+			$return[$mn_id]['timeservers'] = $timeservers;
+		}
+	}
+	
 	return $return;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
+/// \fn getMnsFromImage($imageid)
+///
+/// \param $imageid - id of an image
+///
+/// \return array of management node ids
+///
+/// \brief determines which management nodes can handle an image based on
+/// image group to computer group, then computer group to management node group
+/// mapping
+///
+////////////////////////////////////////////////////////////////////////////////
+function getMnsFromImage($imageid) {
+	$comps = getMappedResources($imageid, 'image', 'computer');
+	if(empty($comps))
+		return array();
+	$inlist = implode(',', $comps);
+	$query = "SELECT DISTINCT rgm.resourcegroupid "
+	       . "FROM resourcegroupmembers rgm, "
+	       .      "resource r, "
+	       .      "computer c "
+	       . "WHERE c.id = r.subid AND "
+	       .       "r.resourcetypeid = 12 AND "
+	       .       "r.id = rgm.resourceid AND "
+	       .       "c.id in ($inlist)";
+	$qh = doQuery($query);
+	$compgroups = array();
+	while($row = mysql_fetch_assoc($qh))
+		$compgroups[] = $row['resourcegroupid'];
+	$mngrps = array();
+	foreach($compgroups as $grpid) {
+		$mngrpset = getResourceMapping('managementnode', 'computer', '', implode(',', $compgroups));
+		foreach($mngrpset as $mngrpid => $compgrpset)
+			$mngrps[$mngrpid] = 1;
+	}
+	$mngrpnames = array();
+	foreach(array_keys($mngrps) as $mnid) {
+		$mngrpnames[] = getResourceGroupName($mnid);
+	}
+	$mns = getResourcesFromGroups($mngrpnames, 'managementnode', 0);
+	$mnids = array();
+	foreach($mns as $mnid => $name)
+		$mnids[$mnid] = 1;
+	return array_keys($mnids);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn checkAvailableNetworks($ip)
+///
+/// \param $ip - public ip address for a reservation
+///
+/// \return array of management node ids that can handle $ip
+///
+/// \brief finds any management nodes that can manage networks containing $ip
+///
+////////////////////////////////////////////////////////////////////////////////
+function checkAvailableNetworks($ip) {
+	$ip = ip2long($ip);
+	$mnids = array();
+	$mns = getManagementNodes();
+	foreach($mns as $mn) {
+		foreach($mn['availablenetworks'] as $net) {
+			if($net == '')
+				continue;
+			list($net, $netmask) = explode('/', $net);
+			$net = ip2long($net);
+			$mask = pow(2, (32 - $netmask)) - 1;
+			$mask = ~ $mask;
+			if(($ip & $mask) == ($net & $mask))
+				$mnids[$mn['id']] = 1;
+		}
+	}
+	return array_keys($mnids);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
 /// \fn getPredictiveModules()
 ///
 /// \return an array of predictive loading modules where the index is the module
@@ -6043,8 +6888,9 @@ function getPredictiveModules() {
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn getTimeSlots($end, $start)
+/// \fn getTimeSlots($compids, $end, $start)
 ///
+/// \param $compids - array of computer ids
 /// \param $end - (optional) end time as unix timestamp
 /// \param $start - (optional) start time as unix timestamp
 ///
@@ -6755,12 +7601,19 @@ function findAvailableTimes($start, $end
 	$query = "SELECT c.id AS compid "
 	       . "FROM computer c, "
 	       .      "image i, "
-	       .      "state s "
+	       .      "state s, "
+	       .      "provisioningOSinstalltype poi, "
+	       .      "OSinstalltype oi, "
+	       .      "OS o "
 	       . "WHERE c.stateid = s.id AND "
 	       .       "i.id = $imageid AND "
 	       .       "s.name NOT IN ($nowignorestates) AND "
 	       .       "c.platformid = $platformid AND "
 	       .       "c.scheduleid IN ($schedules) AND "
+	       .       "i.OSid = o.id AND "
+	       .       "o.installtype = oi.name AND "
+	       .       "oi.id = poi.OSinstalltypeid AND "
+	       .       "poi.provisioningid = c.provisioningid AND "
 	       .       "c.RAM >= i.minram AND "
 	       .       "c.procnumber >= i.minprocnumber AND "
 	       .       "c.procspeed >= i.minprocspeed AND "
@@ -6774,9 +7627,9 @@ function findAvailableTimes($start, $end
 	$query .=                         "DATE_ADD(rq.end, INTERVAL 15 MINUTE) >= '$startdt' AND "
 	       .                          "rs.computerid IN ($incompids)) AND "
 	       .       "c.id IN ($incompids) "
-	       . "ORDER BY (c.procspeed * c.procnumber) DESC, "
-	       .          "RAM DESC, "
-	       .          "network DESC";
+	       . "ORDER BY RAM, "
+	       .          "(c.procspeed * c.procnumber), "
+	       .          "network";
 	$qh = doQuery($query, 101);
 	while($row = mysql_fetch_assoc($qh)) {
 		$row['duration'] = $reqduration;
@@ -6799,7 +7652,10 @@ function findAvailableTimes($start, $end
 		       .      "reservation rs2, "
 		       .      "image i, "
 		       .      "state s, "
-		       .      "computer c "
+		       .      "computer c, "
+		       .      "provisioningOSinstalltype poi, "
+		       .      "OSinstalltype oi, "
+		       .      "OS o "
 		       . "WHERE rq1.id = rs1.requestid AND "
 		       .       "rs2.requestid = rq2.id AND "
 		       .       "rq1.id != rq2.id AND "
@@ -6811,6 +7667,10 @@ function findAvailableTimes($start, $end
 		       .       "c.id = rs1.computerid AND "
 		       .       "c.platformid = $platformid AND "
 		       .       "c.scheduleid IN ($schedules) AND "
+		       .       "i.OSid = o.id AND "
+		       .       "o.installtype = oi.name AND "
+		       .       "oi.id = poi.OSinstalltypeid AND "
+		       .       "poi.provisioningid = c.provisioningid AND "
 		       .       "c.RAM >= i.minram AND "
 		       .       "c.procnumber >= i.minprocnumber AND "
 		       .       "c.procspeed >= i.minprocspeed AND "
@@ -6857,7 +7717,10 @@ function findAvailableTimes($start, $end
 	       .      "reservation rs, "
 	       .      "image i, "
 	       .      "state s, "
-	       .      "computer c "
+	       .      "computer c, "
+	       .      "provisioningOSinstalltype poi, "
+	       .      "OSinstalltype oi, "
+	       .      "OS o "
 	       . "WHERE rs.requestid = rq.id AND "
 	       .       "(rq.start > '$startdt' OR "
 	       .        "(DATE_ADD(rq.end, INTERVAL 15 MINUTE) > '$startdt' AND rq.start <= '$startdt')) AND "
@@ -6866,6 +7729,10 @@ function findAvailableTimes($start, $end
 	       .       "c.id = rs.computerid AND "
 	       .       "c.platformid = $platformid AND "
 	       .       "c.scheduleid IN ($schedules) AND "
+	       .       "i.OSid = o.id AND "
+	       .       "o.installtype = oi.name AND "
+	       .       "oi.id = poi.OSinstalltypeid AND "
+	       .       "poi.provisioningid = c.provisioningid AND "
 	       .       "c.RAM >= i.minram AND "
 	       .       "c.procnumber >= i.minprocnumber AND "
 	       .       "c.procspeed >= i.minprocspeed AND "
@@ -6904,7 +7771,10 @@ function findAvailableTimes($start, $end
 	       .      "reservation rs, "
 	       .      "image i, "
 	       .      "state s, "
-	       .      "computer c "
+	       .      "computer c, "
+	       .      "provisioningOSinstalltype poi, "
+	       .      "OSinstalltype oi, "
+	       .      "OS o "
 	       . "WHERE rs.requestid = rq.id AND "
 	       .       "(rq.start > '$startdt' OR "
 	       .        "(DATE_ADD(rq.end, INTERVAL 15 MINUTE) > '$startdt' AND rq.start <= '$startdt')) AND "
@@ -6913,10 +7783,15 @@ function findAvailableTimes($start, $end
 	       .       "c.id = rs.computerid AND "
 	       .       "c.platformid = $platformid AND "
 	       .       "c.scheduleid IN ($schedules) AND "
+	       .       "i.OSid = o.id AND "
+	       .       "o.installtype = oi.name AND "
+	       .       "oi.id = poi.OSinstalltypeid AND "
+	       .       "poi.provisioningid = c.provisioningid AND "
 	       .       "c.RAM >= i.minram AND "
 	       .       "c.procnumber >= i.minprocnumber AND "
 	       .       "c.procspeed >= i.minprocspeed AND "
 	       .       "c.network >= i.minnetwork AND "
+	       .       "c.deleted = 0 AND "
 	       .       "c.stateid = s.id AND "
 	       .       "s.name NOT IN ($ignorestates) AND ";
 	if($reqid != '')
@@ -7249,7 +8124,7 @@ function getComputers($sort=0, $included
 	       .        "c.platformid AS platformid, "
 	       .        "sc.name AS schedule, "
 	       .        "c.scheduleid AS scheduleid, "
-	       .        "cur.name AS currentimg, "
+	       .        "cur.prettyname AS currentimg, "
 	       .        "c.currentimageid AS currentimgid, "
 	       .        "c.imagerevisionid, "
 	       .        "next.name AS nextimg, "
@@ -7269,6 +8144,7 @@ function getComputers($sort=0, $included
 	       .        "c.notes, "
 	       .        "c.vmhostid, "
 	       .        "c2.hostname AS vmhost, "
+	       .        "c2.id AS vmhostcomputerid, "
 	       .        "c.location, "
 	       .        "c.provisioningid, "
 	       .        "pr.prettyname AS provisioning, "
@@ -7852,7 +8728,7 @@ function isImageBlockTimeActive($imageid
 /// \param $selectedid - (optional) index of $dataArr to be initially selected;
 /// use -1 for nothing to be selected
 /// \param $skip - (optional) this is used if the array from getImages is passed
-/// as $dataArr so we know to skip index 4 since it is the noimage element
+/// as $dataArr so we know to skip the 'No Image" element
 /// \param $multiple - (optional) use this to print select input with the
 /// multiple tag set
 /// \param $domid - (optional) use this to pass in the javascript id to be used
@@ -7867,6 +8743,66 @@ function isImageBlockTimeActive($imageid
 ////////////////////////////////////////////////////////////////////////////////
 function printSelectInput($name, $dataArr, $selectedid=-1, $skip=0, $multiple=0,
                           $domid="", $extra="") {
+	print selectInputHTML($name, $dataArr, $domid, $extra, $selectedid, $skip,
+	                      $multiple);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn selectInputAutoDijitHTML($name, $dataArr, $domid='', $extra='',
+///                              $selectedid=-1) {
+///
+/// \param $name - name of input element
+/// \param $dataArr - array containing options
+/// \param $domid - (optional) use this to pass in the javascript id to be used
+/// for the select object
+/// \param $extra - (optional) any extra attributes that need to be set
+/// \param $selectedid - (optional) index of $dataArr to be initially selected;
+/// use -1 for nothing to be selected
+///
+/// \return html
+///
+/// \brief wrapper for calling selectInputHTML with the resulting element
+/// being a dijit.form.Select if number of items is <= 10 and being a
+/// dijit.form.FilteringSelect if number of items is > 10
+///
+////////////////////////////////////////////////////////////////////////////////
+function selectInputAutoDijitHTML($name, $dataArr, $domid='', $extra='',
+                                  $selectedid=-1) {
+	if(count($dataArr) > 10 &&
+	   USEFILTERINGSELECT && count($dataArr) < FILTERINGSELECTTHRESHOLD)
+		$type = 'dojoType="dijit.form.FilteringSelect" queryExpr="*${0}*"';
+	else
+		$type = 'dojoType="dijit.form.Select" maxHeight="250"';
+	return selectInputHTML($name, $dataArr, $domid, "$type $extra", $selectedid);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn selectInputHTML($name, $dataArr, $domid, $extra, $selectedid, $skip,
+///                     $multiple)
+///
+/// \param $name - name of input element
+/// \param $dataArr - array containing options
+/// \param $domid - (optional) use this to pass in the javascript id to be used
+/// for the select object
+/// \param $extra - (optional) any extra attributes that need to be set
+/// \param $selectedid - (optional) index of $dataArr to be initially selected;
+/// use -1 for nothing to be selected
+/// \param $skip - (optional) this is used if the array from getImages is passed
+/// as $dataArr so we know to skip the 'No Image" element
+/// \param $multiple - (optional) use this to print select input with the
+/// multiple tag set
+///
+/// \brief generates HTML for select input
+/// it is assumed that if $selectedid is left off, we assume $dataArr has no 
+/// index '-1'\n
+/// each OPTION's value is the index of that element of the array
+///
+////////////////////////////////////////////////////////////////////////////////
+function selectInputHTML($name, $dataArr, $domid="", $extra="", $selectedid=-1,
+                         $skip=0, $multiple=0) {
+	$h = '';
 	if(! empty($domid))
 		$domid = "id=\"$domid\"";
 	if($multiple)
@@ -7874,52 +8810,218 @@ function printSelectInput($name, $dataAr
 	else
 		$multiple = "";
 	if($name != '')
-		print "      <select name=$name $multiple $domid $extra>\n";
+		$h .= "      <select name=$name $multiple $domid $extra>\n";
 	else
-		print "      <select $multiple $domid $extra>\n";
+		$h .= "      <select $multiple $domid $extra>\n";
 	foreach(array_keys($dataArr) as $id) {
-		if(($skip && $id == 4) || ($dataArr[$id] != 0 && empty($dataArr[$id])))
+		if(($dataArr[$id] != 0 && empty($dataArr[$id])))
 			continue;
 		if($id == $selectedid)
-		   print "        <option value=\"$id\" selected=\"selected\">";

[... 2112 lines stripped ...]