diff --git a/README.md b/README.md index 01228195..d391c01b 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,26 @@ php occ serverinfo:update-storage-statistics -v --output=json_pretty } ``` -Show phpinfo +##### Enforce default os (>= Nextcloud 28) + +To obtain information about your server, the serverinfo app reads files outside the application directory (e.g. /proc on Linux) or executes shell commands (e.g. df on Linux). + +If you don't want that (for example, to avoid open_basedir warnings) enforce the default os. The default os only returns the hostname. + +Enable: + +``php occ config:app:set --value=yes serverinfo force_default_os`` + +Disable: + +``php occ config:app:delete serverinfo force_default_os`` + +##### Show phpinfo (>= Nextcloud 28) + +Enable: ``php occ config:app:set --value=yes serverinfo phpinfo`` + +Disable: + +``php occ config:app:delete serverinfo phpinfo`` diff --git a/lib/OperatingSystems/DefaultOs.php b/lib/OperatingSystems/DefaultOs.php index 23f06147..8b4e4552 100644 --- a/lib/OperatingSystems/DefaultOs.php +++ b/lib/OperatingSystems/DefaultOs.php @@ -23,259 +23,46 @@ namespace OCA\ServerInfo\OperatingSystems; -use OCA\ServerInfo\Resources\Disk; use OCA\ServerInfo\Resources\Memory; -use OCA\ServerInfo\Resources\NetInterface; -use RuntimeException; class DefaultOs implements IOperatingSystem { - private const AF_INET = 2; - private const AF_INET6 = 10; - public function supported(): bool { - return true; + return false; } public function getMemory(): Memory { - $data = new Memory(); - - try { - $meminfo = $this->readContent('/proc/meminfo'); - } catch (RuntimeException $e) { - return $data; - } - - $matches = []; - $pattern = '/(?(?:MemTotal|MemFree|MemAvailable|SwapTotal|SwapFree)+):\s+(?\d+)\s+(?\w{2})/'; - - $result = preg_match_all($pattern, $meminfo, $matches); - if ($result === 0 || $result === false) { - return $data; - } - - foreach ($matches['Key'] as $i => $key) { - // Value is always in KB: https://github.com/torvalds/linux/blob/c70672d8d316ebd46ea447effadfe57ab7a30a50/fs/proc/meminfo.c#L58-L60 - $value = (int)((int)$matches['Value'][$i] / 1024); - - switch ($key) { - case 'MemTotal': - $data->setMemTotal($value); - break; - case 'MemFree': - $data->setMemFree($value); - break; - case 'MemAvailable': - $data->setMemAvailable($value); - break; - case 'SwapTotal': - $data->setSwapTotal($value); - break; - case 'SwapFree': - $data->setSwapFree($value); - break; - } - } - - return $data; + return new Memory(); } public function getCpuName(): string { - $data = 'Unknown Processor'; - - try { - $cpuinfo = $this->readContent('/proc/cpuinfo'); - } catch (RuntimeException $e) { - return $data; - } - - $matches = []; - - if (str_contains($cpuinfo, 'Raspberry Pi')) { - $pattern = '/Model\s+:\s(.+)/'; - } elseif (str_contains($cpuinfo, 'PowerNV') || str_contains($cpuinfo, 'CHRP IBM pSeries')) { - $pattern = '/cpu\s+:\s+(.+)/'; - } else { - $pattern = '/model name\s:\s(.+)/'; - } - - $result = preg_match_all($pattern, $cpuinfo, $matches); - if ($result === 0 || $result === false) { - return $data; - } - - $model = $matches[1][0]; - - $pattern = '/processor\s+:\s(.+)/'; - - preg_match_all($pattern, $cpuinfo, $matches); - $cores = count($matches[1]); - - if ($cores === 1) { - $data = $model . ' (1 core)'; - } else { - $data = $model . ' (' . $cores . ' cores)'; - } - - return $data; + return 'Unknown Processor'; } public function getTime(): string { - return (string)shell_exec('date'); + return ''; } public function getUptime(): int { - $data = -1; - - try { - $uptime = $this->readContent('/proc/uptime'); - } catch (RuntimeException $e) { - return $data; - } - - [$uptimeInSeconds,] = array_map('intval', explode(' ', $uptime)); - - return $uptimeInSeconds; + return -1; } public function getNetworkInfo(): array { - $result = []; - $result['hostname'] = \gethostname(); - $dns = shell_exec('cat /etc/resolv.conf |grep -i \'^nameserver\'|head -n1|cut -d \' \' -f2'); - $result['dns'] = $dns; - $gw = shell_exec('ip route | awk \'/default/ { print $3 }\''); - $result['gateway'] = $gw; - return $result; + return [ + 'hostname' => \gethostname(), + 'dns' => '', + 'gateway' => '', + ]; } public function getNetworkInterfaces(): array { - $data = []; - - foreach ($this->getNetInterfaces() as $interfaceName => $interface) { - $netInterface = new NetInterface($interfaceName, $interface['up']); - $data[] = $netInterface; - - foreach ($interface['unicast'] as $unicast) { - if (isset($unicast['family'])) { - if ($unicast['family'] === self::AF_INET) { - $netInterface->addIPv4($unicast['address']); - } - if ($unicast['family'] === self::AF_INET6) { - $netInterface->addIPv6($unicast['address']); - } - } - } - - if ($netInterface->isLoopback()) { - continue; - } - - $interfacePath = '/sys/class/net/' . $interfaceName; - - try { - $netInterface->setMAC($this->readContent($interfacePath . '/address')); - - $speed = (int)$this->readContent($interfacePath . '/speed'); - if ($speed >= 1000) { - $netInterface->setSpeed($speed / 1000 . ' Gbps'); - } else { - $netInterface->setSpeed($speed . ' Mbps'); - } - - $netInterface->setDuplex($this->readContent($interfacePath . '/duplex')); - } catch (RuntimeException $e) { - // unable to read interface data - } - } - - return $data; + return []; } public function getDiskInfo(): array { - $data = []; - - try { - $disks = $this->executeCommand('df -TPk'); - } catch (RuntimeException $e) { - return $data; - } - - $matches = []; - $pattern = '/^(?[\S]+)\s*(?[\S]+)\s*(?\d+)\s*(?\d+)\s*(?\d+)\s*(?\d+%)\s*(?[\w\/-]+)$/m'; - - $result = preg_match_all($pattern, $disks, $matches); - if ($result === 0 || $result === false) { - return $data; - } - - foreach ($matches['Filesystem'] as $i => $filesystem) { - if (in_array($matches['Type'][$i], ['tmpfs', 'devtmpfs', 'squashfs', 'overlay'], false)) { - continue; - } elseif (in_array($matches['Mounted'][$i], ['/etc/hostname', '/etc/hosts'], false)) { - continue; - } - - $disk = new Disk(); - $disk->setDevice($filesystem); - $disk->setFs($matches['Type'][$i]); - $disk->setUsed((int)((int)$matches['Used'][$i] / 1024)); - $disk->setAvailable((int)((int)$matches['Available'][$i] / 1024)); - $disk->setPercent($matches['Capacity'][$i]); - $disk->setMount($matches['Mounted'][$i]); - - $data[] = $disk; - } - - return $data; + return []; } public function getThermalZones(): array { - $thermalZones = glob('/sys/class/thermal/thermal_zone*') ?: []; - $result = []; - - foreach ($thermalZones as $thermalZone) { - $tzone = []; - try { - $tzone['hash'] = md5($thermalZone); - $tzone['type'] = $this->readContent($thermalZone . '/type'); - $tzone['temp'] = (float)((int)($this->readContent($thermalZone . '/temp')) / 1000); - } catch (RuntimeException $e) { - continue; - } - $result[] = $tzone; - } - - return $result; - } - - /** - * @throws RuntimeException - */ - protected function readContent(string $filename): string { - $data = @file_get_contents($filename); - if ($data === false || $data === '') { - throw new RuntimeException('Unable to read: "' . $filename . '"'); - } - return trim($data); - } - - protected function executeCommand(string $command): string { - $output = @shell_exec(escapeshellcmd($command)); - if ($output === false || $output === null || $output === '') { - throw new RuntimeException('No output for command: "' . $command . '"'); - } - return $output; - } - - /** - * Wrapper for net_get_interfaces - * - * @throws RuntimeException - */ - protected function getNetInterfaces(): array { - $data = net_get_interfaces(); - if ($data === false) { - throw new RuntimeException('Unable to get network interfaces'); - } - return $data; + return []; } } diff --git a/lib/OperatingSystems/Linux.php b/lib/OperatingSystems/Linux.php new file mode 100644 index 00000000..dc028848 --- /dev/null +++ b/lib/OperatingSystems/Linux.php @@ -0,0 +1,281 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\ServerInfo\OperatingSystems; + +use OCA\ServerInfo\Resources\Disk; +use OCA\ServerInfo\Resources\Memory; +use OCA\ServerInfo\Resources\NetInterface; +use RuntimeException; + +class Linux implements IOperatingSystem { + private const AF_INET = 2; + private const AF_INET6 = 10; + + public function supported(): bool { + return true; + } + + public function getMemory(): Memory { + $data = new Memory(); + + try { + $meminfo = $this->readContent('/proc/meminfo'); + } catch (RuntimeException $e) { + return $data; + } + + $matches = []; + $pattern = '/(?(?:MemTotal|MemFree|MemAvailable|SwapTotal|SwapFree)+):\s+(?\d+)\s+(?\w{2})/'; + + $result = preg_match_all($pattern, $meminfo, $matches); + if ($result === 0 || $result === false) { + return $data; + } + + foreach ($matches['Key'] as $i => $key) { + // Value is always in KB: https://github.com/torvalds/linux/blob/c70672d8d316ebd46ea447effadfe57ab7a30a50/fs/proc/meminfo.c#L58-L60 + $value = (int)((int)$matches['Value'][$i] / 1024); + + switch ($key) { + case 'MemTotal': + $data->setMemTotal($value); + break; + case 'MemFree': + $data->setMemFree($value); + break; + case 'MemAvailable': + $data->setMemAvailable($value); + break; + case 'SwapTotal': + $data->setSwapTotal($value); + break; + case 'SwapFree': + $data->setSwapFree($value); + break; + } + } + + return $data; + } + + public function getCpuName(): string { + $data = 'Unknown Processor'; + + try { + $cpuinfo = $this->readContent('/proc/cpuinfo'); + } catch (RuntimeException $e) { + return $data; + } + + $matches = []; + + if (str_contains($cpuinfo, 'Raspberry Pi')) { + $pattern = '/Model\s+:\s(.+)/'; + } elseif (str_contains($cpuinfo, 'PowerNV') || str_contains($cpuinfo, 'CHRP IBM pSeries')) { + $pattern = '/cpu\s+:\s+(.+)/'; + } else { + $pattern = '/model name\s:\s(.+)/'; + } + + $result = preg_match_all($pattern, $cpuinfo, $matches); + if ($result === 0 || $result === false) { + return $data; + } + + $model = $matches[1][0]; + + $pattern = '/processor\s+:\s(.+)/'; + + preg_match_all($pattern, $cpuinfo, $matches); + $cores = count($matches[1]); + + if ($cores === 1) { + $data = $model . ' (1 core)'; + } else { + $data = $model . ' (' . $cores . ' cores)'; + } + + return $data; + } + + public function getTime(): string { + return (string)shell_exec('date'); + } + + public function getUptime(): int { + $data = -1; + + try { + $uptime = $this->readContent('/proc/uptime'); + } catch (RuntimeException $e) { + return $data; + } + + [$uptimeInSeconds,] = array_map('intval', explode(' ', $uptime)); + + return $uptimeInSeconds; + } + + public function getNetworkInfo(): array { + $result = []; + $result['hostname'] = \gethostname(); + $dns = shell_exec('cat /etc/resolv.conf |grep -i \'^nameserver\'|head -n1|cut -d \' \' -f2'); + $result['dns'] = $dns; + $gw = shell_exec('ip route | awk \'/default/ { print $3 }\''); + $result['gateway'] = $gw; + return $result; + } + + public function getNetworkInterfaces(): array { + $data = []; + + foreach ($this->getNetInterfaces() as $interfaceName => $interface) { + $netInterface = new NetInterface($interfaceName, $interface['up']); + $data[] = $netInterface; + + foreach ($interface['unicast'] as $unicast) { + if (isset($unicast['family'])) { + if ($unicast['family'] === self::AF_INET) { + $netInterface->addIPv4($unicast['address']); + } + if ($unicast['family'] === self::AF_INET6) { + $netInterface->addIPv6($unicast['address']); + } + } + } + + if ($netInterface->isLoopback()) { + continue; + } + + $interfacePath = '/sys/class/net/' . $interfaceName; + + try { + $netInterface->setMAC($this->readContent($interfacePath . '/address')); + + $speed = (int)$this->readContent($interfacePath . '/speed'); + if ($speed >= 1000) { + $netInterface->setSpeed($speed / 1000 . ' Gbps'); + } else { + $netInterface->setSpeed($speed . ' Mbps'); + } + + $netInterface->setDuplex($this->readContent($interfacePath . '/duplex')); + } catch (RuntimeException $e) { + // unable to read interface data + } + } + + return $data; + } + + public function getDiskInfo(): array { + $data = []; + + try { + $disks = $this->executeCommand('df -TPk'); + } catch (RuntimeException $e) { + return $data; + } + + $matches = []; + $pattern = '/^(?[\S]+)\s*(?[\S]+)\s*(?\d+)\s*(?\d+)\s*(?\d+)\s*(?\d+%)\s*(?[\w\/-]+)$/m'; + + $result = preg_match_all($pattern, $disks, $matches); + if ($result === 0 || $result === false) { + return $data; + } + + foreach ($matches['Filesystem'] as $i => $filesystem) { + if (in_array($matches['Type'][$i], ['tmpfs', 'devtmpfs', 'squashfs', 'overlay'], false)) { + continue; + } elseif (in_array($matches['Mounted'][$i], ['/etc/hostname', '/etc/hosts'], false)) { + continue; + } + + $disk = new Disk(); + $disk->setDevice($filesystem); + $disk->setFs($matches['Type'][$i]); + $disk->setUsed((int)((int)$matches['Used'][$i] / 1024)); + $disk->setAvailable((int)((int)$matches['Available'][$i] / 1024)); + $disk->setPercent($matches['Capacity'][$i]); + $disk->setMount($matches['Mounted'][$i]); + + $data[] = $disk; + } + + return $data; + } + + public function getThermalZones(): array { + $thermalZones = glob('/sys/class/thermal/thermal_zone*') ?: []; + $result = []; + + foreach ($thermalZones as $thermalZone) { + $tzone = []; + try { + $tzone['hash'] = md5($thermalZone); + $tzone['type'] = $this->readContent($thermalZone . '/type'); + $tzone['temp'] = (float)((int)($this->readContent($thermalZone . '/temp')) / 1000); + } catch (RuntimeException $e) { + continue; + } + $result[] = $tzone; + } + + return $result; + } + + /** + * @throws RuntimeException + */ + protected function readContent(string $filename): string { + $data = @file_get_contents($filename); + if ($data === false || $data === '') { + throw new RuntimeException('Unable to read: "' . $filename . '"'); + } + return trim($data); + } + + protected function executeCommand(string $command): string { + $output = @shell_exec(escapeshellcmd($command)); + if ($output === false || $output === null || $output === '') { + throw new RuntimeException('No output for command: "' . $command . '"'); + } + return $output; + } + + /** + * Wrapper for net_get_interfaces + * + * @throws RuntimeException + */ + protected function getNetInterfaces(): array { + $data = net_get_interfaces(); + if ($data === false) { + throw new RuntimeException('Unable to get network interfaces'); + } + return $data; + } +} diff --git a/lib/Os.php b/lib/Os.php index b3448f77..420b8cd6 100644 --- a/lib/Os.php +++ b/lib/Os.php @@ -26,25 +26,20 @@ use OCA\ServerInfo\OperatingSystems\DefaultOs; use OCA\ServerInfo\OperatingSystems\FreeBSD; use OCA\ServerInfo\OperatingSystems\IOperatingSystem; +use OCA\ServerInfo\OperatingSystems\Linux; use OCA\ServerInfo\Resources\Memory; +use OCP\IConfig; class Os implements IOperatingSystem { - protected IOperatingSystem $backend; + private IOperatingSystem $backend; - /** - * Os constructor. - */ - public function __construct() { - if (PHP_OS === 'FreeBSD') { - $this->backend = new FreeBSD(); - } else { - $this->backend = new DefaultOs(); - } + public function __construct(IConfig $config) { + $forceDefaultOs = $config->getAppValue('serverinfo', 'force_default_os', 'no') === 'yes'; + $this->backend = $this->getBackend($forceDefaultOs); } public function supported(): bool { - $data = $this->backend->supported(); - return $data; + return $this->backend->supported(); } public function getHostname(): string { @@ -101,8 +96,7 @@ public function getDiskData(): array { } public function getNetworkInfo(): array { - $data = $this->backend->getNetworkInfo(); - return $data; + return $this->backend->getNetworkInfo(); } public function getNetworkInterfaces(): array { @@ -110,7 +104,18 @@ public function getNetworkInterfaces(): array { } public function getThermalZones(): array { - $data = $this->backend->getThermalZones(); - return $data; + return $this->backend->getThermalZones(); + } + + private function getBackend(bool $forceDefaultOs): IOperatingSystem { + if (!$forceDefaultOs) { + if (PHP_OS === 'Linux') { + return new Linux(); + } + if (PHP_OS === 'FreeBSD') { + return new FreeBSD(); + } + } + return new DefaultOs(); } } diff --git a/lib/Resources/Memory.php b/lib/Resources/Memory.php index 6b0b61ca..e8dbd71a 100644 --- a/lib/Resources/Memory.php +++ b/lib/Resources/Memory.php @@ -26,11 +26,11 @@ namespace OCA\ServerInfo\Resources; class Memory { - private int $memTotal = -1; - private int $memFree = -1; - private int $memAvailable = -1; - private int $swapTotal = -1; - private int $swapFree = -1; + private int $memTotal = 0; + private int $memFree = 0; + private int $memAvailable = 0; + private int $swapTotal = 0; + private int $swapFree = 0; /** * @return int in MB diff --git a/tests/data/df_tp b/tests/data/linux_df_tp similarity index 100% rename from tests/data/df_tp rename to tests/data/linux_df_tp diff --git a/tests/data/meminfo b/tests/data/linux_meminfo similarity index 100% rename from tests/data/meminfo rename to tests/data/linux_meminfo diff --git a/tests/data/uptime b/tests/data/linux_uptime similarity index 100% rename from tests/data/uptime rename to tests/data/linux_uptime diff --git a/tests/lib/DefaultOsTest.php b/tests/lib/DefaultOsTest.php index 51b81e3e..9958b503 100644 --- a/tests/lib/DefaultOsTest.php +++ b/tests/lib/DefaultOsTest.php @@ -27,244 +27,47 @@ namespace OCA\ServerInfo\Tests; use OCA\ServerInfo\OperatingSystems\DefaultOs; -use OCA\ServerInfo\Resources\Disk; -use OCA\ServerInfo\Resources\Memory; -use OCA\ServerInfo\Resources\NetInterface; use PHPUnit\Framework\MockObject\MockObject; -use RuntimeException; use Test\TestCase; -/** - * Class DefaultOsTest - * - * @package OCA\ServerInfo\Tests - */ class DefaultOsTest extends TestCase { - /** @var DefaultOs&MockObject */ + /** @var DefaultOs|MockObject */ protected $os; protected function setUp(): void { parent::setUp(); // TODO: Change the autogenerated stub - $this->os = $this->getMockBuilder(DefaultOs::class) - ->disableOriginalConstructor() - ->disableOriginalClone() - ->disableArgumentCloning() - ->disallowMockingUnknownTypes() - ->setMethods(['readContent', 'executeCommand', 'getNetInterfaces']) - ->getMock(); + $this->os = new DefaultOs(); } public function testGetMemory(): void { - $this->os->method('readContent') - ->with('/proc/meminfo') - ->willReturn(file_get_contents(__DIR__ . '/../data/meminfo')); - $memory = $this->os->getMemory(); - $this->assertEquals(15947, $memory->getMemTotal()); - $this->assertEquals(2386, $memory->getMemFree()); - $this->assertEquals(7495, $memory->getMemAvailable()); - $this->assertEquals(975, $memory->getSwapTotal()); - $this->assertEquals(896, $memory->getSwapFree()); - } - - public function testGetMemoryNoData(): void { - $this->os->method('readContent') - ->with('/proc/meminfo') - ->willThrowException(new RuntimeException('Unable to read: "/proc/meminfo"')); - - $this->assertEquals(new Memory(), $this->os->getMemory()); - } - - public function testGetMemoryInvalidData(): void { - $this->os->method('readContent') - ->with('/proc/meminfo') - ->willReturn('invalid_data'); - - $this->assertEquals(new Memory(), $this->os->getMemory()); + $this->assertEquals(-1, $memory->getMemTotal()); + $this->assertEquals(-1, $memory->getMemFree()); + $this->assertEquals(-1, $memory->getMemAvailable()); + $this->assertEquals(-1, $memory->getSwapTotal()); + $this->assertEquals(-1, $memory->getSwapFree()); } public function testGetCpuName(): void { - $this->os->method('readContent') - ->with('/proc/cpuinfo') - ->willReturn(file_get_contents(__DIR__ . '/../data/linux_cpuinfo')); - - $this->assertEquals('Intel(R) Core(TM) i5-6500 CPU @ 3.20GHz (4 cores)', $this->os->getCpuName()); - } - - public function testGetCpuNameOneCore(): void { - $this->os->method('readContent') - ->with('/proc/cpuinfo') - ->willReturn(file_get_contents(__DIR__ . '/../data/linux_cpuinfo_one_core')); - - $this->assertEquals('Intel(R) Core(TM) i5-6500 CPU @ 3.20GHz (1 core)', $this->os->getCpuName()); - } - - public function testGetCpuNamePi3b(): void { - $this->os->method('readContent') - ->with('/proc/cpuinfo') - ->willReturn(file_get_contents(__DIR__ . '/../data/linux_cpuinfo_pi3b')); - - $this->assertEquals('Raspberry Pi 3 Model B Rev 1.2 (4 cores)', $this->os->getCpuName()); - } - - public function testGetCpuNamePi4b(): void { - $this->os->method('readContent') - ->with('/proc/cpuinfo') - ->willReturn(file_get_contents(__DIR__ . '/../data/linux_cpuinfo_pi4b')); - - $this->assertEquals('Raspberry Pi 4 Model B Rev 1.2 (4 cores)', $this->os->getCpuName()); - } - - public function testGetCpuNameOpenPower(): void { - $this->os->method('readContent') - ->with('/proc/cpuinfo') - ->willReturn(file_get_contents(__DIR__ . '/../data/linux_cpuinfo_openpower')); - - $this->assertEquals('POWER9, altivec supported (176 cores)', $this->os->getCpuName()); - } - - public function testGetCpuNameNoData(): void { - $this->os->method('readContent') - ->with('/proc/cpuinfo') - ->willThrowException(new RuntimeException('Unable to read: "/proc/cpuinfo"')); - - $this->assertEquals('Unknown Processor', $this->os->getCpuName()); - } - - public function testGetCpuNameInvalidData(): void { - $this->os->method('readContent') - ->with('/proc/cpuinfo') - ->willReturn('invalid_data'); - $this->assertEquals('Unknown Processor', $this->os->getCpuName()); } public function testGetUptime(): void { - $this->os->method('readContent') - ->with('/proc/uptime') - ->willReturn(file_get_contents(__DIR__ . '/../data/uptime')); - - $this->assertEquals(13278, $this->os->getUptime()); - } - - public function testGetUptimeNoData(): void { - $this->os->method('readContent') - ->with('/proc/uptime') - ->willThrowException(new RuntimeException('Unable to read: "/proc/uptime"')); - $this->assertEquals(-1, $this->os->getUptime()); } - public function testGetDiskInfo(): void { - $this->os->method('executeCommand') - ->with('df -TPk') - ->willReturn(file_get_contents(__DIR__ . '/../data/df_tp')); - - $disk1 = new Disk(); - $disk1->setDevice('/dev/mapper/homestead--vg-root'); - $disk1->setFs('ext4'); - $disk1->setUsed(6060); - $disk1->setAvailable(46212); - $disk1->setPercent('12%'); - $disk1->setMount('/'); - - $disk2 = new Disk(); - $disk2->setDevice('/dev/mapper/homestead--vg-mysql--master'); - $disk2->setFs('ext4'); - $disk2->setUsed(251); - $disk2->setAvailable(60451); - $disk2->setPercent('1%'); - $disk2->setMount('/homestead-vg/master'); - - $disk3 = new Disk(); - $disk3->setDevice('vagrant'); - $disk3->setFs('vboxsf'); - $disk3->setUsed(600421); - $disk3->setAvailable(335246); - $disk3->setPercent('65%'); - $disk3->setMount('/vagrant'); - - $disk4 = new Disk(); - $disk4->setDevice('home_vagrant_code'); - $disk4->setFs('vboxsf'); - $disk4->setUsed(600421); - $disk4->setAvailable(335246); - $disk4->setPercent('65%'); - $disk4->setMount('/home/vagrant/code'); - - $disk5 = new Disk(); - $disk5->setDevice('nfs.example.com:/export'); - $disk5->setFs('nfs4'); - $disk5->setUsed(0); - $disk5->setAvailable(1); - $disk5->setPercent('0%'); - $disk5->setMount('/nfs'); - - $disk6 = new Disk(); - $disk6->setDevice('198.51.100.42:/storage'); - $disk6->setFs('fuse.sshfs'); - $disk6->setUsed(51); - $disk6->setAvailable(44354); - $disk6->setPercent('1%'); - $disk6->setMount('/mnt/sshfs'); - - $this->assertEquals([$disk1, $disk2, $disk3, $disk4, $disk5, $disk6], $this->os->getDiskInfo()); - } - - public function testGetDiskInfoNoCommandOutput(): void { - $this->os->method('executeCommand') - ->with('df -TP') - ->willThrowException(new RuntimeException('No output for command "df -TP"')); - - $this->assertEquals([], $this->os->getDiskInfo()); - } - - public function testGetDiskInfoInvalidCommandOutput(): void { - $this->os->method('executeCommand') - ->with('df -TP') - ->willReturn('invalid_data'); + public function testGetDiskInfo(): void { $this->assertEquals([], $this->os->getDiskInfo()); } public function testSupported(): void { - $this->assertTrue($this->os->supported()); + $this->assertFalse($this->os->supported()); } public function testGetNetworkInterfaces(): void { - $this->os->method('getNetInterfaces') - ->willReturn(json_decode(file_get_contents(__DIR__ . '/../data/linux_net_get_interfaces.json'), true, 512, JSON_THROW_ON_ERROR)); - $this->os->method('readContent') - ->willReturnCallback(static function ($filename) { - if ($filename === '/sys/class/net/eth0/address') { - return '02:42:ac:13:00:0a'; - } - if ($filename === '/sys/class/net/eth0/speed') { - return '10000'; - } - if ($filename === '/sys/class/net/eth0/duplex') { - return 'full'; - } - throw new RuntimeException(); - }); - - - $net1 = new NetInterface('lo', true); - $net1->addIPv4('127.0.0.1'); - $net1->addIPv6('::1'); - - $net2 = new NetInterface('eth0', true); - $net2->addIPv4('10.0.2.15'); - $net2->addIPv6('fe80::a00:27ff:fe91:f84b'); - $net2->setMAC('02:42:ac:13:00:0a'); - $net2->setSpeed('10 Gbps'); - $net2->setDuplex('full'); - - $expected = [$net1, $net2]; - $actual = $this->os->getNetworkInterfaces(); - - $this->assertEquals($expected, $actual); + $this->assertEquals([], $this->os->getNetworkInterfaces()); } } diff --git a/tests/lib/LinuxTest.php b/tests/lib/LinuxTest.php new file mode 100644 index 00000000..7d979c0a --- /dev/null +++ b/tests/lib/LinuxTest.php @@ -0,0 +1,265 @@ + + * + * @author Daniel Kesselberg + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\ServerInfo\Tests; + +use OCA\ServerInfo\OperatingSystems\Linux; +use OCA\ServerInfo\Resources\Disk; +use OCA\ServerInfo\Resources\Memory; +use OCA\ServerInfo\Resources\NetInterface; +use PHPUnit\Framework\MockObject\MockObject; +use RuntimeException; +use Test\TestCase; + +class LinuxTest extends TestCase { + /** @var Linux&MockObject */ + protected $os; + + protected function setUp(): void { + parent::setUp(); // TODO: Change the autogenerated stub + + $this->os = $this->getMockBuilder(Linux::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->disableArgumentCloning() + ->disallowMockingUnknownTypes() + ->setMethods(['readContent', 'executeCommand', 'getNetInterfaces']) + ->getMock(); + } + + public function testGetMemory(): void { + $this->os->method('readContent') + ->with('/proc/meminfo') + ->willReturn(file_get_contents(__DIR__ . '/../data/linux_meminfo')); + + $memory = $this->os->getMemory(); + + $this->assertEquals(15947, $memory->getMemTotal()); + $this->assertEquals(2386, $memory->getMemFree()); + $this->assertEquals(7495, $memory->getMemAvailable()); + $this->assertEquals(975, $memory->getSwapTotal()); + $this->assertEquals(896, $memory->getSwapFree()); + } + + public function testGetMemoryNoData(): void { + $this->os->method('readContent') + ->with('/proc/meminfo') + ->willThrowException(new RuntimeException('Unable to read: "/proc/meminfo"')); + + $this->assertEquals(new Memory(), $this->os->getMemory()); + } + + public function testGetMemoryInvalidData(): void { + $this->os->method('readContent') + ->with('/proc/meminfo') + ->willReturn('invalid_data'); + + $this->assertEquals(new Memory(), $this->os->getMemory()); + } + + public function testGetCpuName(): void { + $this->os->method('readContent') + ->with('/proc/cpuinfo') + ->willReturn(file_get_contents(__DIR__ . '/../data/linux_cpuinfo')); + + $this->assertEquals('Intel(R) Core(TM) i5-6500 CPU @ 3.20GHz (4 cores)', $this->os->getCpuName()); + } + + public function testGetCpuNameOneCore(): void { + $this->os->method('readContent') + ->with('/proc/cpuinfo') + ->willReturn(file_get_contents(__DIR__ . '/../data/linux_cpuinfo_one_core')); + + $this->assertEquals('Intel(R) Core(TM) i5-6500 CPU @ 3.20GHz (1 core)', $this->os->getCpuName()); + } + + public function testGetCpuNamePi3b(): void { + $this->os->method('readContent') + ->with('/proc/cpuinfo') + ->willReturn(file_get_contents(__DIR__ . '/../data/linux_cpuinfo_pi3b')); + + $this->assertEquals('Raspberry Pi 3 Model B Rev 1.2 (4 cores)', $this->os->getCpuName()); + } + + public function testGetCpuNamePi4b(): void { + $this->os->method('readContent') + ->with('/proc/cpuinfo') + ->willReturn(file_get_contents(__DIR__ . '/../data/linux_cpuinfo_pi4b')); + + $this->assertEquals('Raspberry Pi 4 Model B Rev 1.2 (4 cores)', $this->os->getCpuName()); + } + + public function testGetCpuNameOpenPower(): void { + $this->os->method('readContent') + ->with('/proc/cpuinfo') + ->willReturn(file_get_contents(__DIR__ . '/../data/linux_cpuinfo_openpower')); + + $this->assertEquals('POWER9, altivec supported (176 cores)', $this->os->getCpuName()); + } + + public function testGetCpuNameNoData(): void { + $this->os->method('readContent') + ->with('/proc/cpuinfo') + ->willThrowException(new RuntimeException('Unable to read: "/proc/cpuinfo"')); + + $this->assertEquals('Unknown Processor', $this->os->getCpuName()); + } + + public function testGetCpuNameInvalidData(): void { + $this->os->method('readContent') + ->with('/proc/cpuinfo') + ->willReturn('invalid_data'); + + $this->assertEquals('Unknown Processor', $this->os->getCpuName()); + } + + public function testGetUptime(): void { + $this->os->method('readContent') + ->with('/proc/uptime') + ->willReturn(file_get_contents(__DIR__ . '/../data/linux_uptime')); + + $this->assertEquals(13278, $this->os->getUptime()); + } + + public function testGetUptimeNoData(): void { + $this->os->method('readContent') + ->with('/proc/uptime') + ->willThrowException(new RuntimeException('Unable to read: "/proc/uptime"')); + + $this->assertEquals(-1, $this->os->getUptime()); + } + + public function testGetDiskInfo(): void { + $this->os->method('executeCommand') + ->with('df -TPk') + ->willReturn(file_get_contents(__DIR__ . '/../data/linux_df_tp')); + + $disk1 = new Disk(); + $disk1->setDevice('/dev/mapper/homestead--vg-root'); + $disk1->setFs('ext4'); + $disk1->setUsed(6060); + $disk1->setAvailable(46212); + $disk1->setPercent('12%'); + $disk1->setMount('/'); + + $disk2 = new Disk(); + $disk2->setDevice('/dev/mapper/homestead--vg-mysql--master'); + $disk2->setFs('ext4'); + $disk2->setUsed(251); + $disk2->setAvailable(60451); + $disk2->setPercent('1%'); + $disk2->setMount('/homestead-vg/master'); + + $disk3 = new Disk(); + $disk3->setDevice('vagrant'); + $disk3->setFs('vboxsf'); + $disk3->setUsed(600421); + $disk3->setAvailable(335246); + $disk3->setPercent('65%'); + $disk3->setMount('/vagrant'); + + $disk4 = new Disk(); + $disk4->setDevice('home_vagrant_code'); + $disk4->setFs('vboxsf'); + $disk4->setUsed(600421); + $disk4->setAvailable(335246); + $disk4->setPercent('65%'); + $disk4->setMount('/home/vagrant/code'); + + $disk5 = new Disk(); + $disk5->setDevice('nfs.example.com:/export'); + $disk5->setFs('nfs4'); + $disk5->setUsed(0); + $disk5->setAvailable(1); + $disk5->setPercent('0%'); + $disk5->setMount('/nfs'); + + $disk6 = new Disk(); + $disk6->setDevice('198.51.100.42:/storage'); + $disk6->setFs('fuse.sshfs'); + $disk6->setUsed(51); + $disk6->setAvailable(44354); + $disk6->setPercent('1%'); + $disk6->setMount('/mnt/sshfs'); + + $this->assertEquals([$disk1, $disk2, $disk3, $disk4, $disk5, $disk6], $this->os->getDiskInfo()); + } + + public function testGetDiskInfoNoCommandOutput(): void { + $this->os->method('executeCommand') + ->with('df -TP') + ->willThrowException(new RuntimeException('No output for command "df -TP"')); + + $this->assertEquals([], $this->os->getDiskInfo()); + } + + public function testGetDiskInfoInvalidCommandOutput(): void { + $this->os->method('executeCommand') + ->with('df -TP') + ->willReturn('invalid_data'); + + $this->assertEquals([], $this->os->getDiskInfo()); + } + + public function testSupported(): void { + $this->assertTrue($this->os->supported()); + } + + public function testGetNetworkInterfaces(): void { + $this->os->method('getNetInterfaces') + ->willReturn(json_decode(file_get_contents(__DIR__ . '/../data/linux_net_get_interfaces.json'), true, 512, JSON_THROW_ON_ERROR)); + $this->os->method('readContent') + ->willReturnCallback(static function ($filename) { + if ($filename === '/sys/class/net/eth0/address') { + return '02:42:ac:13:00:0a'; + } + if ($filename === '/sys/class/net/eth0/speed') { + return '10000'; + } + if ($filename === '/sys/class/net/eth0/duplex') { + return 'full'; + } + throw new RuntimeException(); + }); + + + $net1 = new NetInterface('lo', true); + $net1->addIPv4('127.0.0.1'); + $net1->addIPv6('::1'); + + $net2 = new NetInterface('eth0', true); + $net2->addIPv4('10.0.2.15'); + $net2->addIPv6('fe80::a00:27ff:fe91:f84b'); + $net2->setMAC('02:42:ac:13:00:0a'); + $net2->setSpeed('10 Gbps'); + $net2->setDuplex('full'); + + $expected = [$net1, $net2]; + $actual = $this->os->getNetworkInterfaces(); + + $this->assertEquals($expected, $actual); + } +}