array( 'Hostname' => "Hostname", 'InternalId' => "InternalId", 'RootPassword' => "RootPassword" ), 'numeric' => array( 'NodeId' => "NodeId", 'TemplateId' => "TemplateId", 'UserId' => "UserId", 'VirtualizationType' => "VirtualizationType", 'DiskSpace' => "DiskSpace", 'GuaranteedRam' => "GuaranteedRam", 'BurstableRam' => "BurstableRam", 'CpuCount' => "CpuCount", 'Status' => "Status", 'IncomingTrafficUsed' => "IncomingTrafficUsed", 'IncomingTrafficLast' => "IncomingTrafficLast", 'OutgoingTrafficUsed' => "OutgoingTrafficUsed", 'OutgoingTrafficLast' => "OutgoingTrafficLast", 'IncomingTrafficLimit' => "IncomingTrafficLimit", 'OutgoingTrafficLimit' => "OutgoingTrafficLimit", 'TotalTrafficLimit' => "TotalTrafficLimit" ), 'node' => array( 'Node' => "NodeId" ), 'template' => array( 'Template' => "TemplateId" ), 'user' => array( 'User' => "UserId" ) ); public $prototype_export = array( 'Hostname', 'InternalId', 'NodeId', 'Status' ); public function __get($name) { switch($name) { case "sRamUsed": return $this->GetRamUsed(); break; case "sRamTotal": return $this->GetRamTotal(); break; case "sDiskUsed": return $this->GetDiskUsed(); break; case "sDiskTotal": return $this->GetDiskTotal(); break; case "sBandwidthUsed": return $this->GetBandwidthUsed(); break; case "sCurrentStatus": return (int)$this->GetCurrentStatus(); break; case "sStatusText": return $this->GetStatusText(); break; default: return parent::__get($name); break; } } public function GetBandwidthUsed() { return ($this->sOutgoingTrafficUsed + $this->IncomingTrafficUsed) / (1024 * 1024); } public function GetCurrentStatus() { if($this->sStatus == CVM_STATUS_SUSPENDED || $this->sStatus == CVM_STATUS_TERMINATED) { return $this->sStatus; } else { $command = "vzctl status {$this->sInternalId}"; $result = $this->sNode->ssh->RunCommandCached($command, false); if($result->returncode == 0) { $values = split_whitespace($result->stdout); if($values[4] == "running") { return CVM_STATUS_STARTED; } else { return CVM_STATUS_STOPPED; } } } } public function GetStatusText() { $status = $this->sCurrentStatus; if($status == CVM_STATUS_STARTED) { return "running"; } elseif($status == CVM_STATUS_STOPPED) { return "stopped"; } elseif($status == CVM_STATUS_SUSPENDED) { return "suspended"; } else { return "unknown"; } } public function GetRamUsed() { $ram = $this->GetRam(); return $ram['used']; } public function GetRamTotal() { $ram = $this->GetRam(); return $ram['total']; } public function GetRam() { $result = $this->RunCommandCached("free -m", true); $lines = explode("\n", $result->stdout); array_shift($lines); $total_free = 0; $total_used = 0; $total_total = 0; foreach($lines as $line) { $line = trim($line); $values = split_whitespace($line); if(trim($values[0]) == "Mem:") { $total_total = $values[1]; $total_used = $values[2]; $total_free = $values[3]; } } return array( 'free' => $total_free, 'used' => $total_used, 'total' => $total_total ); } public function GetDiskUsed() { $disk = $this->GetDisk(); return $disk['used']; } public function GetDiskTotal() { $disk = $this->GetDisk(); return $disk['total']; } public function GetDisk() { $result = $this->RunCommandCached("df -l -x tmpfs", true); $lines = explode("\n", $result->stdout); array_shift($lines); $total_free = 0; $total_used = 0; $total_total = 0; foreach($lines as $disk) { $disk = trim($disk); if(!empty($disk)) { $values = split_whitespace($disk); $total_free += (int)$values[3] / 1024; $total_used += (int)$values[2] / 1024; $total_total += ((int)$values[2] + (int)$values[3]) / 1024; } } return array( 'free' => $total_free, 'used' => $total_used, 'total' => $total_total ); } public function CheckAllowed() { if($this->sStatus == CVM_STATUS_SUSPENDED) { throw new ContainerSuspendedException("No operations can be performed on this container beacuse it is suspended.", 1, $this->sInternalId); } elseif($this->sStatus == CVM_STATUS_TERMINATED) { throw new ContainerSuspendedException("No operations can be performed on this container beacuse it is terminated.", 1, $this->sInternalId); } else { return true; } } public function SetOptions($options) { if(is_array($options)) { foreach($options as $key => $value) { $config_elements[] = "--{$key} {$value}"; } $config_string = implode(" ", $config_elements); $this->sNode->ssh->RunCommand("vzctl set {$this->sInternalId} {$config_string} --save", true); } else { throw new InvalidArgumentException("The option argument to Container::SetOptions should be an array."); } } public function RunCommand($command, $throw_exception = false) { return $this->sNode->ssh->RunCommand("vzctl exec {$this->sInternalId} $command", $throw_exception); } public function RunCommandCached($command, $throw_exception = false) { return $this->sNode->ssh->RunCommandCached("vzctl exec {$this->sInternalId} $command", $throw_exception); } public function Deploy($conf = array()) { $sRootPassword = random_string(20); $this->uRootPassword = $sRootPassword; $this->InsertIntoDatabase(); $command = shrink_command("vzctl create {$this->sInternalId} --ostemplate {$this->sTemplate->sTemplateName} "); $result = $this->sNode->ssh->RunCommand($command, false); $result->returncode = 0; if($result->returncode == 0 && strpos($result->stderr, "ERROR") === false) { // TODO: set sensible defaults depending on container resource configuration // http://wiki.openvz.org/UBC_consistency_check // http://wiki.openvz.org/UBC_parameter_units // http://wiki.openvz.org/UBC_configuration_examples // http://wiki.openvz.org/UBC_parameters_table $this->uStatus = CVM_STATUS_CREATED; $this->InsertIntoDatabase(); $dummy_processes = 1000; $dummy_files = $this->sGuaranteedRam * 32; $dummy_sockets = $this->sGuaranteedRam * 3; $sKMemSize = (isset($conf['sKMemSize'])) ? $conf['sKMemSize'] : (40 * 1024 * ($dummy_processes / 2)) + ($dummy_files * 384 * 100); $sKMemSizeLimit = (isset($conf['sKMemSizeLimit'])) ? $conf['sKMemSizeLimit'] : (int)($sKMemSize * 1.1); $sLockedPages = (isset($conf['sLockedPages'])) ? $conf['sLockedPages'] : $dummy_processes; $sShmPages = (isset($conf['sShmPages'])) ? $conf['sShmPages'] : $this->sGuaranteedRam . "M"; $sOomGuarPages = (isset($conf['sOomGuarPages'])) ? $conf['sOomGuarPages'] : $this->sGuaranteedRam . "M"; $sTcpSock = (isset($conf['sTcpSock'])) ? $conf['sTcpSock'] : $dummy_sockets; $sOtherSock = (isset($conf['sOtherSock'])) ? $conf['sOtherSock'] : $dummy_sockets; $sFLock = (isset($conf['sFLock'])) ? $conf['sFLock'] : $dummy_processes; $sFLockLimit = (isset($conf['sFLockLimit'])) ? $conf['sFLockLimit'] : (int)($sFLock * 1.1); $sTcpSndBuf = (isset($conf['sTcpSndBuf'])) ? $conf['sTcpSndBuf'] : round($this->sGuaranteedRam * 1024 / 5) . "K"; $sTcpRcvBuf = (isset($conf['sTcpRcvBuf'])) ? $conf['sTcpRcvBuf'] : round($this->sGuaranteedRam * 1024 / 5) . "K"; $sOtherBuf = (isset($conf['sOtherBuf'])) ? $conf['sOtherBuf'] : round($this->sGuaranteedRam * 1024 / 5) . "K"; $sOtherBufLimit = (isset($conf['sOtherBufLimit'])) ? $conf['sOtherBufLimit'] : (int)($sOtherBuf + (2 * $dummy_processes * 16)); $sTcpSndBufLimit = (isset($conf['sTcpSndBufLimit'])) ? $conf['sTcpSndBufLimit'] : (int)($sTcpSndBuf + (2 * $dummy_processes * 16)); $sTcpRcvBufLimit = (isset($conf['sTcpRcvBufLimit'])) ? $conf['sTcpRcvBufLimit'] : (int)($sTcpRcvBuf + (2 * $dummy_processes * 16)); $sDgramBuf = (isset($conf['sDgramBuf'])) ? $conf['sDgramBuf'] : round($this->sGuaranteedRam * 1024 / 5) . "K"; $sNumFile = (isset($conf['sNumFile'])) ? $conf['sNumFile'] : $dummy_files; $sNumProc = (isset($conf['sNumProc'])) ? $conf['sNumProc'] : $dummy_processes; $sDCache = (isset($conf['sDCache'])) ? $conf['sDCache'] : $dummy_files * 384; $sDCacheLimit = (isset($conf['sDCacheLimit'])) ? $conf['sDCacheLimit'] : (int)($sDCache * 1.1); $sAvgProc = (isset($conf['sAvgProc'])) ? $conf['sAvgProc'] : $dummy_processes / 2; $command = shrink_command("vzctl set {$this->sInternalId} --onboot yes --setmode restart --hostname {$this->sHostname} --nameserver 8.8.8.8 --nameserver 8.8.4.4 --numproc {$this->sCpuCount} --vmguarpages {$this->sGuaranteedRam}M:unlimited --privvmpages {$this->sBurstableRam}M:{$this->sBurstableRam}M --quotatime 0 --diskspace {$this->sDiskSpace}M:{$this->sDiskSpace}M --userpasswd root:{$sRootPassword} --kmemsize {$sKMemSize}:{$sKMemSizeLimit} --lockedpages {$sLockedPages}:{$sLockedPages} --shmpages {$sShmPages}:{$sShmPages} --physpages 0:unlimited --oomguarpages {$sOomGuarPages}:unlimited --numtcpsock {$sTcpSock}:{$sTcpSock} --numflock {$sFLock}:{$sFLockLimit} --numpty 32:32 --numsiginfo 512:512 --tcpsndbuf {$sTcpSndBuf}:{$sTcpSndBufLimit} --tcprcvbuf {$sTcpRcvBuf}:{$sTcpRcvBufLimit} --othersockbuf {$sOtherBuf}:{$sOtherBufLimit} --dgramrcvbuf {$sDgramBuf}:{$sDgramBuf} --numothersock {$sOtherSock}:{$sOtherSock} --numfile {$sNumFile}:{$sNumFile} --numproc {$sNumProc}:{$sNumProc} --dcachesize {$sDCache}:{$sDCacheLimit} --numiptent 128:128 --diskinodes 200000:220000 --avnumproc {$sAvgProc}:{$sAvgProc} --save "); /* This may be useful if we turn out to have a kernel that supports vswap $command = shrink_command("vzctl set {$this->sInternalId} --onboot yes --setmode restart --hostname {$this->sHostname} --nameserver 8.8.8.8 --nameserver 8.8.4.4 --numproc {$this->sCpuCount} --quotatime 0 --diskspace {$this->sDiskSpace}M:{$this->sDiskSpace}M --userpasswd root:{$sRootPassword} --numtcpsock 360:360 --numflock 188:206 --numpty 16:16 --numsiginfo 256:256 --tcpsndbuf 1720320:2703360 --tcprcvbuf 1720320:2703360 --othersockbuf 1126080:2097152 --dgramrcvbuf 262144:262144 --numothersock 360:360 --numfile 9312:9312 --dcachesize 3409920:3624960 --numiptent 128:128 --diskinodes 200000:220000 --avnumproc 180:180 --ram {$this->sGuaranteedRam}M --swap {$this->sBurstableRam}M --save ");*/ $result = $this->sNode->ssh->RunCommand($command, false); if($result->returncode == 0) { $this->uStatus = CVM_STATUS_CONFIGURED; $this->InsertIntoDatabase(); return true; } else { throw new ContainerConfigureException($result->stderr, $result->returncode, $this->sInternalId); } } else { throw new ContainerCreateException($result->stderr, $result->returncode, $this->sInternalId); } } public function Destroy() { if($this->sCurrentStatus == CVM_STATUS_STARTED) { $this->Stop(); } $command = "vzctl destroy {$this->sInternalId}"; $result = $this->sNode->ssh->RunCommand($command, false); if($result->returncode == 0) { return true; } else { throw new ContainerDestroyException("Destroying container failed: {$result->stderr}", $result->returncode, $this->sInternalId); } } public function Reinstall() { try { $this->Destroy(); $this->Deploy(); } catch (ContainerDestroyException $e) { throw new ContainerReinstallException("Reinstalling container failed during destroying: " . $e->getMessage(), $e->getCode(), $this->sInternalId, $e); } catch (ContainerCreateException $e) { throw new ContainerReinstallException("Reinstalling container failed during creation: " . $e->getMessage(), $e->getCode(), $this->sInternalId, $e); } catch (ContainerConfigureException $e) { throw new ContainerReinstallException("Reinstalling container failed during configuration: " . $e->getMessage(), $e->getCode(), $this->sInternalId, $e); } } public function Start($forced = false) { if($this->sStatus == CVM_STATUS_SUSPENDED && $forced == false) { throw new ContainerSuspendedException("The container cannot be started as it is suspended.", 1, $this->sInternalId); } elseif($this->sStatus == CVM_STATUS_TERMINATED && $forced == false) { throw new ContainerTerminatedException("The container cannot be started as it is terminated.", 1, $this->sInternalId); } else { $command = "vzctl start {$this->sInternalId}"; $result = $this->sNode->ssh->RunCommand($command, false); if($result->returncode == 0) { $this->uStatus = CVM_STATUS_STARTED; $this->InsertIntoDatabase(); return true; } else { throw new ContainerStartException($result->stderr, $result->returncode, $this->sInternalId); } } } public function Stop() { if($this->sStatus == CVM_STATUS_SUSPENDED) { throw new ContainerSuspendedException("The container cannot be stopped as it is suspended.", 1, $this->sInternalId); } elseif($this->sStatus == CVM_STATUS_TERMINATED) { throw new ContainerTerminatedException("The container cannot be stopped as it is terminated.", 1, $this->sInternalId); } else { $command = "vzctl stop {$this->sInternalId}"; $result = $this->sNode->ssh->RunCommand($command, false); // vzctl is retarded enough to return exit status 0 when the command fails because the container isn't running, so we'll have to check the stderr for specific error string(s) as well. come on guys, it's 2012. if($result->returncode == 0 && strpos($result->stderr, "Unable to stop") === false) { $this->uStatus = CVM_STATUS_STOPPED; $this->InsertIntoDatabase(); return true; } else { throw new ContainerStopException($result->stderr, $result->returncode, $this->sInternalId); } } } public function Suspend() { if($this->sStatus != CVM_STATUS_SUSPENDED) { try { $this->Stop(); $this->uStatus = CVM_STATUS_SUSPENDED; $this->InsertIntoDatabase(); } catch (ContainerStopException $e) { throw new ContainerSuspendException("Suspension failed as the container could not be stopped.", 1, $this->sInternalId, $e); } } else { throw new ContainerSuspendException("The container is already suspended.", 1, $this->sInternalId); } } public function Unsuspend() { if($this->sStatus == CVM_STATUS_SUSPENDED) { try { $this->Start(true); $this->uStatus = CVM_STATUS_STARTED; $this->InsertIntoDatabase(); } catch (ContainerStartException $e) { throw new ContainerUnsuspendException("Unsuspension failed as the container could not be started.", 1, $this->sInternalId, $e); } } else { throw new ContainerUnsuspendException("The container is not suspended.", 1, $this->sInternalId); } } public function AddIp($ip) { $command = shrink_command("vzctl set {$this->sInternalId} --ipadd {$ip} --save "); $result = $this->sNode->ssh->RunCommand($command, false); pretty_dump($result); if($result->returncode == 0) { return true; } else { throw new ContainerIpAddException($result->stderr, $result->returncode, $this->sInternalId); } } public function RemoveIp($ip) { $command = shrink_command("vzctl set {$this->sInternalId} --ipdel {$ip} --save "); $result = $this->sNode->ssh->RunCommand($command, false); if($result->returncode == 0) { return true; } else { throw new ContainerIpRemoveException($result->stderr, $result->returncode, $this->sInternalId); } } public function UpdateTraffic() { $result = $this->sNode->ssh->RunCommand("vzctl exec {$this->sInternalId} cat /proc/net/dev | grep venet0", false); if($result->returncode == 0) { $lines = split_lines($result->stdout); $values = split_whitespace(str_replace(":", " ", $lines[0])); $uIncoming = $values[1]; $uOutgoing = $values[9]; if($uIncoming < (int)$this->sIncomingTrafficLast || $uOutgoing < (int)$this->sOutgoingTrafficLast) { // the counter has reset (wrap-around, reboot, etc.) $uNewIncoming = $uIncoming; $uNewOutgoing = $uOutgoing; } else { $uNewIncoming = $uIncoming - $this->sIncomingTrafficLast; $uNewOutgoing = $uOutgoing - $this->sOutgoingTrafficLast; } $this->uIncomingTrafficUsed = $this->sIncomingTrafficUsed + $uNewIncoming; $this->uOutgoingTrafficUsed = $this->sOutgoingTrafficUsed + $uNewOutgoing; $this->uIncomingTrafficLast = $uIncoming; $this->uOutgoingTrafficLast = $uOutgoing; $this->InsertIntoDatabase(); } else { throw new ContainerTrafficRetrieveException($result->stderr, $result->returncode, $this->sInternalId); } } public function SetRootPassword($password) { if($this->sStatus == CVM_STATUS_SUSPENDED) { throw new ContainerSuspendedException("The root password cannot be changed, because the container is suspended.", 1, $this->sInternalId); } elseif($this->sStatus == CVM_STATUS_TERMINATED) { throw new ContainerTerminatedException("The root password cannot be changed, because the container is terminated.", 1, $this->sInternalId); } else { $sPassword = escapeshellarg($password); $this->SetOptions(array( 'userpasswd' => "root:{$sPassword}" )); } } public function EnableTunTap() { // TODO: Finish EnableTunTap function, check whether tun module is available on host $command = "vzctl set {$this->sInternalId} --devnodes net/tun:rw --save"; } }