From e8955ff103d11743fb43a78ca53198fdade705d7 Mon Sep 17 00:00:00 2001
From: Austin Kregel <5355937+austinkregel@users.noreply.github.com>
Date: Sun, 12 May 2024 21:13:14 -0400
Subject: [PATCH] Initial commit
---
.gitattributes | 13 +
.gitignore | 9 +
CHANGELOG.md | 32 +++
CODE_OF_CONDUCT.md | 74 ++++++
CONTRIBUTING.md | 32 +++
LICENSE.md | 21 ++
README.md | 82 ++++++
composer.json | 44 ++++
phpunit.xml.dist | 23 ++
src/LibvirtBasementServiceProvider.php | 22 ++
src/LibvirtService.php | 312 +++++++++++++++++++++++
src/Models/Server.php | 13 +
tests/AbstractTestCase.php | 10 +
tests/Integration/LibvirtServiceTest.php | 72 ++++++
14 files changed, 759 insertions(+)
create mode 100644 .gitattributes
create mode 100644 .gitignore
create mode 100644 CHANGELOG.md
create mode 100644 CODE_OF_CONDUCT.md
create mode 100644 CONTRIBUTING.md
create mode 100644 LICENSE.md
create mode 100644 README.md
create mode 100644 composer.json
create mode 100644 phpunit.xml.dist
create mode 100644 src/LibvirtBasementServiceProvider.php
create mode 100644 src/LibvirtService.php
create mode 100644 src/Models/Server.php
create mode 100644 tests/AbstractTestCase.php
create mode 100644 tests/Integration/LibvirtServiceTest.php
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..e991b7f
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,13 @@
+# Path-based git attributes
+# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html
+
+# Ignore all test and documentation with "export-ignore".
+/.editorconfig export-ignore
+/.gitattributes export-ignore
+/.gitignore export-ignore
+/.github/PULL_REQUEST_TEMPLATE.md export-ignore
+/.github/ISSUE_TEMPLATE.md export-ignore
+/phpcs.xml.dist export-ignore
+/phpunit.xml.dist export-ignore
+/tests export-ignore
+/docs export-ignore
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8f16734
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+build
+composer.lock
+vendor
+phpcs.xml
+phpunit.xml
+.phpunit.result.cache
+node_modules
+npm-debug.log
+*.log
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..3971c27
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,32 @@
+# Changelog
+
+All notable changes to `basement-common` will be documented in this file.
+
+Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
+
+## NEXT - YYYY-MM-DD
+
+### Added
+
+#### v0.1.0
+ - Updated requirements to >= Laravel 10
+ - Rearranged .github
+
+
+
+#### v0.0.8
+
+- Initial interfaces for domain and server services.
+- Initial abstract classes for domains, regions, servers, sizes, and sshkeys.
+
+### Deprecated
+- Nothing
+
+### Fixed
+- Nothing
+
+### Removed
+- Nothing
+
+### Security
+- Nothing
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..5557916
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,74 @@
+# Contributor Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to make participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of experience,
+nationality, personal appearance, race, religion, or sexual identity and
+orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at `austin@kregel.co`. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at [http://contributor-covenant.org/version/1/4][version]
+
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/4/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..9233077
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,32 @@
+# Contributing
+
+Contributions are **welcome** and will be fully **credited**.
+
+We accept contributions via Pull Requests on [Github](https://github.com/austinkregel/basement-common).
+
+
+## Pull Requests
+
+- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - Check the code style with ``$ composer check-style`` and fix it with ``$ composer fix-style``.
+
+- **Add tests!** - Your patch won't be accepted if it doesn't have tests.
+
+- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.
+
+- **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option.
+
+- **Create feature branches** - Don't ask us to pull from your main branch.
+
+- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
+
+- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.
+
+
+## Running Tests
+
+``` bash
+$ composer test
+```
+
+
+**Happy coding**!
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..82209c5
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,21 @@
+# The MIT License (MIT)
+
+Copyright (c) 2024 Austin Kregel <5355937+austinkregel@users.noreply.github.com>
+
+> Permission is hereby granted, free of charge, to any person obtaining a copy
+> of this software and associated documentation files (the "Software"), to deal
+> in the Software without restriction, including without limitation the rights
+> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+> copies of the Software, and to permit persons to whom the Software is
+> furnished to do so, subject to the following conditions:
+>
+> The above copyright notice and this permission notice shall be included in
+> all copies or substantial portions of the Software.
+>
+> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+> THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ffc714f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,82 @@
+# basement-common
+
+[![Latest Version on Packagist][ico-version]][link-packagist]
+[![Software License][ico-license]](LICENSE.md)
+[![Total Downloads][ico-downloads]][link-downloads]
+
+This package is a basic QEMU management interface using the interfaces from [the-basement/common](https://packagist.org/packages/the-basement/common).
+
+## Install
+
+Via Composer
+
+``` bash
+$ composer require the-basement/libvirt
+```
+
+## Basic Usage
+
+Creating a VM in QEMU/KVM
+
+```php
+// This assumes you have an ubuntu server image available from your KVM host
+// This also assumes the default image location of the disks created by KVM.
+// Both of these can be changed; disks that exist will not be overwritten
+// disks that don't exist will be created.
+$service = new TheBasement\Libvirt\LibvirtService();
+$service->createServer([
+ 'name' => 'my-virtual-machine',
+ 'memory' => (string) (1024 * 1024), // 1G in KiB
+ 'cores' => 1,
+ 'threads' => 1,
+ 'iso_path' => '/var/lib/libvirt/iso/ubuntu-22.04.4-live-server-amd64.iso',
+ 'storage_pool' => 'default',
+ 'network_mac' => '',
+ 'video_ram' => '65536', // bytes of video ram
+ 'disk_path' => '/var/lib/libvirt/images/ubuntu22.04-2.qcow2',
+ 'disk_name' => 'ubuntu22.04-2.qcow2',
+ 'disk_capacity' => 10 * 1024 * 1024 * 1024, // 10 GB in bytes
+]);
+
+// Gets all servers defined for the KVM
+$servers = $service->findAllServers();
+```
+
+
+
+## Change log
+
+Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
+
+## Testing
+
+``` bash
+$ composer test
+```
+
+## Contributing
+
+Please see [CONTRIBUTING](CONTRIBUTING.md) and [CODE_OF_CONDUCT](CODE_OF_CONDUCT.md) for details.
+
+## Security
+
+If you discover any security related issues, please email security@austinkregel.com instead of using the issue tracker.
+
+## Credits
+
+- [Austin Kregel][link-author]
+- [All Contributors][link-contributors]
+
+## License
+
+The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
+
+[ico-version]: https://img.shields.io/packagist/v/the-basement/libvirt.svg?style=flat-square
+[ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square
+[ico-code-quality]: https://img.shields.io/scrutinizer/g/the-basement/libvirt.svg?style=flat-square
+[ico-downloads]: https://img.shields.io/packagist/dt/the-basement/libvirt.svg?style=flat-square
+
+[link-packagist]: https://packagist.org/packages/the-basement/libvirt
+[link-downloads]: https://packagist.org/packages/the-basement/libvirt
+[link-author]: https://github.com/austinkregel
+[link-contributors]: ../../contributors
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..86b35dd
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,44 @@
+{
+ "name": "the-basement/libvirt",
+ "type": "library",
+ "description": "This package implements the domain service contract for the libvirt hypervisor.",
+ "keywords": [
+ "thebasement.club",
+ "libvirt",
+ "hypervisor",
+ "kvm",
+ "qemu",
+ "virtualization"
+ ],
+ "homepage": "https://github.com/The-Basement-Club/basement-libvirt",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Austin Kregel",
+ "email": "5355937+austinkregel@users.noreply.github.com",
+ "homepage": "https://austinkregel.com",
+ "role": "Developer"
+ }
+ ],
+ "require": {
+ "php": ">=8.2",
+ "the-basement/common": "^0.1.1"
+ },
+ "autoload": {
+ "psr-4": {
+ "TheBasement\\Libvirt\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "TheBasement\\Libvirt\\Tests\\": "tests"
+ }
+ },
+ "config": {
+ "sort-packages": true
+ },
+ "require-dev": {
+ "laravel/pint": "^1.15",
+ "phpunit/phpunit": "^11.1"
+ }
+}
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 0000000..f4034ae
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+ tests
+
+
+
+
+
+
+
diff --git a/src/LibvirtBasementServiceProvider.php b/src/LibvirtBasementServiceProvider.php
new file mode 100644
index 0000000..7e47c4a
--- /dev/null
+++ b/src/LibvirtBasementServiceProvider.php
@@ -0,0 +1,22 @@
+app->make(ServerServiceFactory::class)
+ ->register('libvirt', LibvirtService::class);
+ }
+
+ public function boot()
+ {
+ //
+ }
+}
\ No newline at end of file
diff --git a/src/LibvirtService.php b/src/LibvirtService.php
new file mode 100644
index 0000000..a52cf2f
--- /dev/null
+++ b/src/LibvirtService.php
@@ -0,0 +1,312 @@
+resource = libvirt_connect($host, false);
+ }
+
+ public function createServer(array $config): Serverlike
+ {
+ // Create the root element
+ $domain = new \SimpleXMLElement('');
+ $domain->addAttribute('type', 'kvm');
+
+ // Add child elements
+ $domain->addChild('name', $config['name']);
+ $domain->addChild('memory', (string) $config['memory']);
+ if (isset($config['vcpu'])) {
+ $domain->addChild('vcpu', (string) ($config['vcpu']));
+ }
+
+ $os = $domain->addChild('os');
+ $type = $os->addChild('type', 'hvm');
+ $type->addAttribute('arch', 'x86_64');
+ $type->addAttribute('machine', 'pc');
+
+ $os->addChild('boot')
+ ->addAttribute('dev', 'cdrom');
+
+ $features = $domain->addChild('features');
+ $features->addChild('acpi');
+ $features->addChild('apic');
+
+ $cpu = $domain->addChild('cpu');
+
+ $cpu->addAttribute('mode', 'host-passthrough');
+ $cpu->addAttribute('check', 'none');
+ $cpu->addAttribute('migratable', 'on');
+
+ $clock = $domain->addChild('clock');
+ $clock->addAttribute('offset', 'utc');
+
+ $catchUpTimer = $clock->addChild('timer');
+ $catchUpTimer->addAttribute('name', 'rtc');
+ $catchUpTimer->addAttribute('tickpolicy', 'catchup');
+
+ $delayTimer = $clock->addChild('timer');
+ $delayTimer->addAttribute('name', 'pit');
+ $delayTimer->addAttribute('tickpolicy', 'delay');
+
+ if (isset($config['cores']) || isset($config['threads'])) {
+ $topology = $cpu->addChild('topology');
+ $topology->addAttribute('sockets', '1');
+ $topology->addAttribute('cores', (string) ($config['cores'] ?? 1));
+ $topology->addAttribute('threads', (string) ($config['threads'] ?? 1));
+ }
+
+// $domain->addChild('on_poweroff', 'destroy');
+ $domain->addChild('on_reboot', 'restart');
+// $domain->addChild('on_crash', 'destroy');
+ $pm = $domain->addChild('pm');
+ $pm->addChild('suspend-to-mem')->addAttribute('enabled', 'no');
+ $pm->addChild('suspend-to-disk')->addAttribute('enabled', 'no');
+
+ // Devices
+ $devices = $domain->addChild('devices');
+
+ // Add our disk
+ $disk = $devices->addChild('disk');
+ $disk->addAttribute('type', 'file');
+ $disk->addAttribute('device', 'disk');
+
+ $driver = $disk->addChild('driver');
+ $driver->addAttribute('name', 'qemu');
+ $driver->addAttribute('type', 'raw');
+ $driver->addAttribute('discard', 'unmap');
+ $disk->addChild('source')->addAttribute('file', $config['disk_path']);
+ $target = $disk->addChild('target');
+ $target->addAttribute('dev', 'vda');
+ $target->addAttribute('bus', 'virtio');
+
+ if (isset($config['iso_path'])) {
+ // Add our ISO as a CD
+ $cdrom = $devices->addChild('disk');
+ $cdrom->addAttribute('type', 'file');
+ $cdrom->addAttribute('device', 'cdrom');
+ $iso = $cdrom->addChild('driver');
+ $iso->addAttribute('name', 'qemu');
+ $iso->addAttribute('type', 'raw');
+ $cdrom->addChild('source')->addAttribute('file', $config['iso_path']);
+ $target = $cdrom->addChild('target');
+ $target->addAttribute('dev', 'hda');
+ $target->addAttribute('bus', 'sata');
+
+ $cdrom->addChild('readonly');
+ }
+
+ $graphics = $devices->addChild('graphics');
+ $graphics->addAttribute('type', 'vnc');
+ $graphics->addAttribute('port', '-1');
+ $graphics->addAttribute('autoport', 'yes');
+
+ // Convert SimpleXMLElement object to XML string
+
+ // Define the storage for the VM
+ $storagePool = libvirt_storagepool_lookup_by_name($this->resource, $config['storage_pool']);
+ $volume = $devices->addChild('volume');
+ $volume->addChild('name', $config['disk_name']);
+ $volume->addChild('target')->addChild('path', $config['disk_path']);
+ $volume->addChild('capacity', (string) $config['disk_capacity']); // In KiB
+
+ // This will provision the drive for us on the storage
+ libvirt_storagevolume_create_xml($storagePool, $volume->asXML());
+
+ $virtIoController = $devices->addChild('controller');
+ $virtIoController->addAttribute('type', 'virtio-serial');
+ $virtIoController->addAttribute('index', '0');
+
+ $interface = $devices->addChild('interface');
+ $interface->addAttribute('type', 'network');
+ // Dynamically set the mac?
+ if (isset($config['network_mac'])) {
+ $interface->addChild('mac')->addAttribute('address', $config['network_mac']);
+ }
+ $interface->addChild('source')->addAttribute('network', 'default');
+ $interface->addChild('model')->addAttribute('type', 'virtio');
+
+ $console = $devices->addChild('console');
+ $console->addAttribute('type', 'pty');
+ $target = $console->addChild('target');
+ $target->addAttribute('type', 'serial');
+ $target->addAttribute('port', '0');
+
+ $channel = $devices->addChild('channel');
+ $channel->addAttribute('type', 'unix');
+ $target = $channel->addChild('target');
+ $target->addAttribute('type', 'virtio');
+ $target->addAttribute('name', 'org.qemu.guest_agent.0');
+
+ $graphics = $devices->addChild('graphics');
+ $graphics->addAttribute('type', 'spice');
+ $graphics->addAttribute('autoport', 'yes');
+
+ $graphics->addChild('listen')->addAttribute('type', 'address');
+ $graphics->addChild('image')->addAttribute('compression', 'off');
+
+ $audio = $devices->addChild('audio');
+ $audio->addAttribute('type', 'spice');
+ $audio->addAttribute('id', '1');
+
+ $video = $devices->addChild('video');
+ $model = $video->addChild('model');
+ $model->addAttribute('type', 'qxl');
+ $model->addAttribute('ram', $config['video_ram'] ?? '65536');
+ $model->addAttribute('heads', '1');
+ $model->addAttribute('primary', 'yes');
+
+ $devices->addChild('memballoon')->addAttribute('model', 'virtio');
+
+ $rng = $devices->addChild('rng');
+ $rng->addAttribute('model', 'virtio');
+ $rng->addChild('backend', '/dev/urandom')->addAttribute('model', 'random');
+
+ $keyboard = $devices->addChild('input');
+ $keyboard->addAttribute('type', 'keyboard');
+ $keyboard->addAttribute('bus', 'usb');
+
+ $mouse = $devices->addChild('input');
+ $mouse->addAttribute('type', 'mouse');
+ $mouse->addAttribute('bus', 'usb');
+
+ libvirt_domain_define_xml($this->resource, $domain->asXML());
+ libvirt_domain_create(libvirt_domain_lookup_by_name($this->resource, $config['name']));
+ // libvirt_domain_define_xml will --- Will save VM
+ return new Server(json_decode(json_encode($domain), true));
+ }
+
+ public function findAllRegions(): array
+ {
+ return [
+ 'default'
+ ];
+ }
+
+ public function findAllSizes(): array
+ {
+ return libvirt_connect_get_machine_types($this->resource);
+ }
+
+ public function findAllServers(): array
+ {
+ $vmNamesThatAreOn = libvirt_list_active_domains($this->resource);
+
+ $vms = libvirt_connect_get_all_domain_stats($this->resource);
+
+ $formattedArray = [];
+ foreach ($vms as $key => $vm) {
+ foreach ($vm as $k => $v) {
+ $formattedArray = Arr::add($formattedArray, $k, $v);
+ }
+ }
+
+ return [
+ 'data' => array_map(fn ($data, $key) => array_merge(
+ [
+ 'id' => libvirt_domain_get_id($resource = libvirt_domain_lookup_by_name($this->resource, $key)),
+ 'name' => $key,
+ 'on' => in_array($key, $vmNamesThatAreOn),
+ 'state' => match ($data['state.state']) {
+ VIR_DOMAIN_NOSTATE => 'no state',
+ VIR_DOMAIN_RUNNING => 'running',
+ VIR_DOMAIN_BLOCKED => 'blocked',
+ VIR_DOMAIN_PAUSED => 'paused',
+ VIR_DOMAIN_SHUTDOWN => 'shutdown',
+ VIR_DOMAIN_SHUTOFF => 'shutoff',
+ VIR_DOMAIN_CRASHED => 'crashed',
+ VIR_DOMAIN_PMSUSPENDED => 'suspended',
+ },
+ // bytes
+ 'memory' => ($info = libvirt_domain_get_info($resource))['memory'],
+ 'cpu_usage' => $info['cpuUsed'],
+ 'cpus' => $info['nrVirtCpu'],
+ 'disk' => array_values(array_filter($formattedArray['block'], 'is_array')),
+ ],
+ isset($formattedArray['balloon']) ? ['balloon' => $formattedArray['balloon']] : [],
+ isset($formattedArray['net']) ? ['net' => array_values(array_filter($formattedArray['net'], 'is_array'))] : [],
+ ), $vms, array_keys($vms)),
+ 'hypervisor' => libvirt_connect_get_information($this->resource),
+ 'storage_pool' => array_map(fn ($storagePoolName) => libvirt_storagepool_get_info(libvirt_storagepool_lookup_by_name($this->resource, $storagePoolName)), libvirt_list_storagepools($this->resource)),
+ ];
+ }
+
+ public function deleteServer(mixed $identifier): void
+ {
+ // We want to destroy all associated libvirt resources
+ $domain = libvirt_domain_lookup_by_name($this->resource, (string) $identifier);
+ // Lookup snapshots, disks, nic, etc and remove or destroy them
+ $disks = libvirt_domain_get_disk_devices($domain);
+
+ // Attempt to shutdown the domain
+ libvirt_domain_shutdown($domain);
+
+ // Wait for the domain to shut down
+ while (($info = libvirt_domain_get_info($domain)) && $info['state'] != VIR_DOMAIN_SHUTOFF) {
+ // Sleep for a bit to prevent high CPU usage
+ sleep(1);
+ }
+
+ foreach ($disks as $disk) {
+ libvirt_domain_detach_device($domain, $disk);
+ libvirt_domain_disk_remove($domain, $disk);
+ sleep(1);
+ }
+
+ libvirt_domain_destroy($domain);
+ libvirt_domain_undefine($domain);
+ }
+
+ public function powerOnServer(int|string $identifier): void
+ {
+ $domain = libvirt_domain_lookup_by_name($this->resource, (string) $identifier);
+ libvirt_domain_create($domain);
+ }
+
+ public function powerOffServer(int|string $identifier): void
+ {
+ $domain = libvirt_domain_lookup_by_name($this->resource, (string) $identifier);
+ libvirt_domain_shutdown($domain);
+ }
+
+ public function shutdownServer(int|string $identifier): void
+ {
+ $domain = libvirt_domain_lookup_by_name($this->resource, (string) $identifier);
+ libvirt_domain_shutdown($domain);
+ }
+
+ public function rebootServer(int|string $identifier): void
+ {
+ $domain = libvirt_domain_lookup_by_name($this->resource, $identifier);
+ libvirt_domain_reboot($domain);
+ }
+
+ public function findAllSshkeys(): array
+ {
+ throw new NotImplementedException('SSH Key management is not supported');
+ }
+
+ public function createServerKey(array $config): SshKeylike
+ {
+ throw new NotImplementedException('SSH Key management is not supported');
+ }
+
+ public function removeServerKey($identifier): void
+ {
+ throw new NotImplementedException('SSH Key management is not supported');
+ }
+}
diff --git a/src/Models/Server.php b/src/Models/Server.php
new file mode 100644
index 0000000..80e62d4
--- /dev/null
+++ b/src/Models/Server.php
@@ -0,0 +1,13 @@
+libvirtService = new LibvirtService();
+ }
+
+ public function testCreateServer(): void
+ {
+ $config = [
+ 'name' => 'myvm',
+ 'memory' => 1024,
+ 'cores' => 1,
+ 'threads' => 0,
+ 'disk_path' => '/path/to/disk.img',
+ 'iso_path' => '/path/to/iso.iso',
+ 'disk_name' => 'mydisk',
+ ];
+
+ $server = $this->libvirtService->createServer($config);
+
+ $this->assertArrayHasKey('id', $server);
+ $this->assertArrayHasKey('name', $server);
+ $this->assertArrayHasKey('on', $server);
+ $this->assertArrayHasKey('state', $server);
+ $this->assertArrayHasKey('memory', $server);
+ $this->assertArrayHasKey('cpu_usage', $server);
+ $this->assertArrayHasKey('cpus', $server);
+ $this->assertArrayHasKey('disk', $server);
+ $this->assertArrayHasKey('balloon', $server);
+ $this->assertArrayHasKey('net', $server);
+ }
+
+ public function testFindAllRegions(): void
+ {
+ $regions = $this->libvirtService->findAllRegions();
+
+ $this->assertIsArray($regions);
+ $this->assertNotEmpty($regions);
+ }
+
+ public function testFindAllSizes(): void
+ {
+ $sizes = $this->libvirtService->findAllSizes();
+
+ $this->assertIsArray($sizes);
+ $this->assertNotEmpty($sizes);
+ }
+
+ public function testFindAllServers(): void
+ {
+ $servers = $this->libvirtService->findAllServers();
+
+ $this->assertIsArray($servers);
+ $this->assertNotEmpty($servers);
+ $this->assertArrayHasKey('data', $servers);
+ }
+}
\ No newline at end of file