commit 58440850dfedaecfa7cd8de1881c8f1b9fa6f596 Author: Joshua M. Boniface Date: Tue Sep 12 16:40:05 2023 -0400 Add initial documentation (move from main repo) diff --git a/README.md b/README.md new file mode 100644 index 0000000..55f0069 --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +

+Logo banner +

+License +Code style: Black +Release +Documentation Status +

+ +## What is PVC? + +PVC is a Linux KVM-based hyperconverged infrastructure (HCI) virtualization cluster solution that is fully Free Software, scalable, redundant, self-healing, self-managing, and designed for administrator simplicity. It is an alternative to other HCI solutions such as Ganeti, Harvester, Nutanix, and VMWare, as well as to other common virtualization stacks such as ProxMox and OpenStack. + +PVC is a complete HCI solution, built from well-known and well-trusted Free Software tools, to assist an administrator in creating and managing a cluster of servers to run virtual machines, as well as self-managing several important aspects including storage failover, node failure and recovery, virtual machine failure and recovery, and network plumbing. It is designed to act consistently, reliably, and unobtrusively, letting the administrator concentrate on more important things. + +PVC is highly scalable. From a minimum (production) node count of 3, up to 12 or more, and supporting many dozens of VMs, PVC scales along with your workload and requirements. Deploy a cluster once and grow it as your needs expand. + +As a consequence of its features, PVC makes administrating very high-uptime VMs extremely easy, featuring VM live migration, built-in always-enabled shared storage with transparent multi-node replication, and consistent network plumbing throughout the cluster. Nodes can also be seamlessly removed from or added to service, with zero VM downtime, to facilitate maintenance, upgrades, or other work. + +PVC also features an optional, fully customizable VM provisioning framework, designed to automate and simplify VM deployments using custom provisioning profiles, scripts, and CloudInit userdata API support. + +Installation of PVC is accomplished by two main components: a [Node installer ISO](https://github.com/parallelvirtualcluster/pvc-installer) which creates on-demand installer ISOs, and an [Ansible role framework](https://github.com/parallelvirtualcluster/pvc-ansible) to configure, bootstrap, and administrate the nodes. Installation can also be fully automated with a companion [cluster bootstrapping system](https://github.com/parallelvirtualcluster/pvc-bootstrap). Once up, the cluster is managed via an HTTP REST API, accessible via a Python Click CLI client or WebUI. + +Just give it physical servers, and it will run your VMs without you having to think about it, all in just an hour or two of setup time. + + +## What is it based on? + +The core node and API daemons, as well as the CLI API client, are written in Python 3 and are fully Free Software (GNU GPL v3). In addition to these, PVC makes use of the following software tools to provide a holistic hyperconverged infrastructure solution: + + * Debian GNU/Linux as the base OS. + * Linux KVM, QEMU, and Libvirt for VM management. + * Linux `ip`, FRRouting, NFTables, DNSMasq, and PowerDNS for network management. + * Ceph for storage management. + * Apache Zookeeper for the primary cluster state database. + * Patroni PostgreSQL manager for the secondary relation databases (DNS aggregation, Provisioner configuration). + + +## Getting Started + +To get started with PVC, please see the [About](https://docs.parallelvirtualcluster.org/en/latest/about/) page for general information about the project, and the [Getting Started](https://docs.parallelvirtualcluster.org/en/latest/getting-started/) page for details on configuring your first cluster. + + +## Changelog + +View the changelog in [CHANGELOG.md](CHANGELOG.md). + + +## Screenshots + +While PVC's API and internals aren't very screenshot-worthy, here is some example output of the CLI tool. + +

Node listing
Listing the nodes in a cluster

+ +

Network listing
Listing the networks in a cluster, showing 3 bridged and 1 IPv4-only managed networks

+ +

VM listing and migration
Listing a limited set of VMs and migrating one with status updates

+ +

Node logs
Viewing the logs of a node (keepalives and VM [un]migration)

diff --git a/docs/about.md b/docs/about.md new file mode 100644 index 0000000..a2ef7de --- /dev/null +++ b/docs/about.md @@ -0,0 +1,166 @@ +# About the Parallel Virtual Cluster system + +- [About the Parallel Virtual Cluster system](#about-the-parallel-virtual-cluster-system) + * [Project Motivation](#project-motivation) + * [Building Blocks](#building-blocks) + * [Cluster Architecture](#cluster-architecture) + * [Clients](#clients) + + [API Client](#api-client) + + [Direct Bindings](#direct-bindings) + + [CLI Client](#cli-client) + * [Deployment](#deployment) + * [Frequently Asked Questions](#frequently-asked-questions) + + [General Questions](#general-questions) + + [Feature Questions](#feature-questions) + + [Storage Questions](#storage-questions) + * [About The Author](#about-the-author) + +This document contains information about the project itself, the software stack, its motivations, and a number of frequently-asked questions. + +## Project Motivation + +Server administration has changed significantly in recent decades. Computing-as-a-resource and software-defined infrastructure is now the norm, and the days of pet servers, painstaking manual configurations, and installing from CR-ROM ISOs is long gone. This is a brave new world. + +As part of these trends, Infrastructure-as-a-Service (IaaS) has become a critical component of server administration. Administrators and developers are increasingly interfacing with their infrastructure via programmable APIs and software tools, and automation is a hard requirement. While Container infrastructure like Docker and Kubernetes has become more and more popular in this space, Virtual Machines (VMs) are still a very common feature and do not seem to be going anywhere any time soon. + +However, the current state of the free and open source virtualization ecosystem is lacking. + +At the lower- to middle-end, projects like ProxMox provide an easy way to administer small virtualization clusters, but these projects tend to lack advanced redundancy facilities that are built-in by default. Ganeti, a former Google tool, was long-dead when PVC was initially conceived, but has recently been given new life by the FLOSS community, and was the inspiration for much of PVC's functionality. Harvester is also a newer player in the space, created by Rancher Labs after PVC was established, but its use of custom solutions for everything, especially the storage backend, gives us some pause. + +At the high-end, very large projects like OpenStack and CloudStack provide very advanced functionality, but these project are sprawling and complicated for Administrators to use, and are very focused on large enterprise deployments, not suitable for smaller clusters and teams. + +Finally, proprietary solutions dominate this space. VMWare and Nutanix are the two largest names, with these products providing functionality for both small and large clusters, but proprietary software limits both flexibility and freedom, and the costs associated with these solutions is immense. + +PVC aims to bridge the gaps between these categories. Like the larger FLOSS and proprietary projects, PVC can scale up to very large cluster sizes, while remaining usable even for small clusters as well. Like the smaller FLOSS and proprietary projects, PVC aims to be very simple to use, with a fully programmable API, allowing administrators to get on with more important things. Like the other FLOSS solutions, PVC is free, both as in beer and as in speech, allowing the administrator to inspect, modify, and tailor it to their needs. And finally, PVC is built from the ground-up to support host-level redundancy at every layer, rather than this being an expensive, optional, or tacked on feature, using standard, well-tested and well-supported components. + +In short, it is a Free Software, scalable, redundant, self-healing, and self-managing private cloud solution designed with administrator simplicity in mind. + +## Building Blocks + +PVC is build from a number of other, open source components. The main system itself is a series of software daemons (services) written in Python 3, with the CLI interface also written in Python 3. + +Virtual machines themselves are run with the Linux KVM subsystem via the Libvirt virtual machine management library. This provides the maximum flexibility and compatibility for running various guest operating systems in multiple modes (fully-virtualized, para-virtualized, virtio-enabled, etc.). + +To manage cluster state, PVC uses Zookeeper. This is an Apache project designed to provide a highly-available and always-consistent key-value database. The various daemons all connect to the distributed Zookeeper database to both obtain details about cluster state, and to manage that state. For instance the node daemon watches Zookeeper for information on what VMs to run, networks to create, etc., while the API writes to or reads information from Zookeeper in response to requests. The Zookeeper database is the glue which holds the cluster together. + +Additional relational database functionality, specifically for the managed network DNS aggregation subsystem and the VM provisioner, is provided by the PostgreSQL database system and the Patroni management tool, which provides automatic clustering and failover for PostgreSQL database instances. + +Node network routing for managed networks providing EBGP VXLAN and route-learning is provided by FRRouting, a descendant project of Quaaga and GNU Zebra. Upstream routers can use this interface to learn routes to cluster networks as well. PVC also makes extensive use of the standard Linux `iprouting` stack. + +The storage subsystem is provided by Ceph, a distributed object-based storage subsystem with proven stability, extensive scalability, self-managing, and self-healing functionality. The Ceph RBD (RADOS Block Device) subsystem is used to provide VM block devices similar to traditional LVM or ZFS zvols, but in a distributed, shared-storage manner. + +All the components are designed to be run on top of Debian GNU/Linux, specifically Debian 10.x "Buster" or 11.x "Bullseye", with the SystemD system service manager. This OS provides a stable base to run the various other subsystems while remaining truly Free Software, while SystemD provides functionality such as automatic daemon restarting and complex startup/shutdown ordering. + +## Cluster Architecture + +A PVC cluster is based around "nodes", which are physical servers on which the various daemons, storage, networks, and virtual machines run. Each node is self-contained and is able to perform any and all cluster functions if needed and configured to do so; there is no strict segmentation of function between different "types" of physical hosts. Ideally, all nodes in a cluster will be identical in specifications, but in some situations mismatched nodes are acceptable, with limitations. + +A subset of the nodes, called "coordinators", are statically configured to provide services for the cluster. For instance, all databases, FRRouting instances, and Ceph management daemons run only on the set of cluster coordinators. At cluster bootstrap, 1 (testing-only), 3 (small clusters), or 5 (large clusters) nodes may be chosen as the coordinators. Other nodes can then be added as "hypervisor" nodes, which then provide only block device (storage) and VM (compute) functionality by connecting to the set of coordinators. This limits the scaling problem of the databases while ensuring there is still maximum redundancy and resiliency for the core cluster services. + +Additional nodes can be added to the cluster either as coordinators, or as hypervisors, by adding them to the Ansible configuration and running it against the full set of nodes. Note that the number of coordinators must always be odd, and more than 5 coordinators are normally unnecessary and can cause issues with the database; it is thus normally advisable to add any nodes beyond the initial set as hypervisors instead of coordinators. Nodes can be removed from service, but this is a manual process and should not be attempted unless absolutely required; the Ceph subsystem in particular is sensitive to changes in the coordinator nodes. Nodes can also be upgraded or replaced dynamically and without interrupting the cluster, allowing for seamless hardware maintenance, upgrades, and even replacement, as cluster state configuration is held cluster-wide. + +During runtime, one coordinator is elected the "primary" for the cluster. This designation can shift dynamically in response to cluster events, or be manually migrated by an administrator. The coordinator takes on a number of roles for which only one host may be active at once, for instance to provide DHCP services to managed client networks or to interface with the API. + +Nodes are networked together via a set of statically-configured, simple layer-2 networks. At a minimum, 2 discrete networks are required, with an optional 3rd. + + * The "upstream" network is the primary network for the nodes, and provides functions such as upstream Internet access, routing to and from the cluster nodes, and management via the API; it may be either a firewalled public or NAT'd RFC1918 network, but should never be exposed directly to the Internet. It should also contain, or be able to route to, the IPMI BMC management interfaces of the node chassis'. + * The "cluster" network is an unrouted RFC1918 network which provides inter-node communication for managed client network traffic (VXLANs), cross-node routing, VM migration and failover, and database replication and access. + * The "storage" network is another unrouted RFC1918 network which provides a dedicated logical and/or physical link between the nodes for storage traffic, including VM block device storage traffic, inter-OSD replication traffic, and Ceph heartbeat traffic, thus allowing it to be completely isolated from the other networks for maximum performance. This network can be optionally colocated with the "cluster" network, by specifying the same device for both, and can be further combined by specifying the same IP for both to completely collapse the "cluster" and "storage" networks. A collapsed cluster+storage configuration may be ideal to simplify management of small clusters, or a split configuration can be used to provide flexibility for large or demanding high-performance clusters - this choice is left to the administrator based on their needs. + + Within each network is a single "floating" IP address which follows the primary coordinator, providing a single interface to the cluster. Once configured, the cluster is then able to create additional networks of two kinds, "bridged" traditional vLANs and "managed" routed VXLANs, to provide network access to VMs. + +Further information about the general cluster architecture, including important considerations for node specifications/sizing and network configuration, [can be found at the cluster architecture page](/cluster-architecture). It is imperative that potential PVC administrators read this document thoroughly to understand the specific requirements of PVC and avoid potential missteps in obtaining and deploying their cluster. + +More information about the node daemon can be found at the [Node Daemon manual page](/manuals/daemon) and details about the health system and health plugins for nodes can be found at the [health plugin manual page](/manuals/health-plugins). + +## Clients + +### API Client + +The API client is a Flask-based RESTful API and is the core interface to PVC. By default the API will run on the primary coordinator, listening on TCP port 7370 on the "upstream" network floating IP address. All other clients communicate with this API to perform actions against the cluster. The API features basic authentication using UUID-based API keys to prevent unauthorized access, and can optionally be configured with full TLS encryption to provide integrity and confidentiality across public networks. + +The API generally accepts all requests as HTTP form requests following standard RESTful guidelines, supporting arguments in the URI string or, with limited exceptions, in the message body. The API returns JSON response bodies to all requests consisting either of the information requested, or a `{ "message": "text" }` construct to pass informational status messages back to the client. + +The API client manual can be found at the [API manual page](/manuals/api), and the full API details can be found in the [API reference specification](/manuals/api-reference.html). + +### Direct Bindings + +The API client uses a dedicated set of Python libraries, packaged as the `pvc-daemon-common` Debian package, to communicate with the cluster. One can thus use these libraries to build custom Python clients that directly interface with the PVC cluster, without having to get "into the weeds" of the Zookeeper or PostgreSQL databases. + +### CLI Client + +The CLI client is a Python Click application, which provides a convenient CLI interface to the API client. It supports connecting to multiple clusters from a single instance, with or without authentication and over both HTTP or HTTPS, including a special "local" cluster if the client determines that an API configuration exists on the local host. Information about the configured clusters is stored in a local JSON document, and a default cluster can be set with an environment variable. The CLI client can thus be run either on PVC nodes themselves, or on other, remote systems which can then interface with cluster(s) over the network. + +The CLI client is self-documenting using the `-h`/`--help` arguments throughout, easing the administrator learning curve and providing easy access to command details. A short manual can also be found at the [CLI manual page](/manuals/cli). + +## Deployment + +The overall management, deployment, bootstrapping, and configuring of nodes is accomplished via a set of Ansible roles and playbooks, found in the [`pvc-ansible` repository](https://github.com/parallelvirtualcluster/pvc-ansible), and nodes are installed via a custom installer ISO generated by the [`pvc-installer` repository](https://github.com/parallelvirtualcluster/pvc-installer). Once the cluster is set up, nodes can be added, replaced, updated, or reconfigured using this Ansible framework. + +Details about the Ansible setup and node installer can be found in those repositories. + +The [getting started documentation](/getting-started) provides a walk-through of using these tools to bootstrap a new cluster. + +## Frequently Asked Questions + +### General Questions + +#### What is it? + +PVC is a virtual machine management suite designed around high-availability and ease-of-use. It can be considered an alternative to OpenStack, ProxMox, Nutanix, and other similar solutions that manage not just the VMs, but the surrounding infrastructure as well. + +#### Why would you make this? + +After becoming frustrated by numerous other management tools, I discovered that what I wanted didn't exist as FLOSS software, so I built it myself. Since then, I have also been able to leverage PVC both for my own purposes as well as for my employer, a win-win for the project. + +#### Is PVC right for me? + +PVC might be right for you if: + +1. You need KVM-based VMs. +2. You want management of storage and networking (a.k.a. "batteries-included") in the same tool. +3. You want hypervisor-level redundancy, able to tolerate hypervisor downtime seamlessly, for all elements of the stack. +4. You have a requirement of at least 3 nodes' worth of compute and storage. + +If all you want is a simple home server solution, or you demand scalability beyond a few dozen compute nodes, PVC is likely not what you're looking for. Its sweet spot is specifically in the 3-9 node range, for instance in an advanced homelab, for SMBs or small ISPs with a relatively small server stack, or for MSPs looking to deploy small on-premises clusters at low cost. + +#### Is 3 hypervisors really the minimum? + +For a redundant cluster, yes. PVC requires a majority quorum for proper operation at various levels, and the smallest possible majority quorum is 2-of-3; thus 3 nodes is the smallest safe minimum. That said, you can run PVC on a single node for testing/lab purposes without host-level redundancy, should you wish to do so, and it might also be possible to run 2 "main" systems with a 3rd "quorum observer" hosting only the management tools but no VMs; however these options are not officially supported, as PVC is designed primarily for 3+ node operation. + +### Feature Questions + +#### Does PVC support containers (Docker/Kubernetes/LXC/etc.)? + +No, not directly. PVC supports only KVM VMs. To run containers, you would need to run a VM which then runs your containers. For instance PVC makes an excellent underlying layer for a virtual Kubernetes cluster, instead of bare hardware. + +#### Does PVC have a WebUI? + +Not yet. Right now, PVC management is done exclusively with the CLI interface to the API. A WebUI can and likely will be built in the future, but I'm not a frontend developer and I do not consider this a personal priority. As of late 2020 the API is generally stable, so I would welcome 3rd party assistance here. + +#### I want feature X, does it fit with PVC? + +That depends on the specific feature. I will limit features to those that align with the overall goals of PVC, that is to say, to provide an easy-to-use hyperconverged virtualization system focused on redundancy. If a feature suits this goal it is likely to be considered; if it does not, it will not. PVC is rapidly approaching the completion of its 1.0 road-map, which I consider feature-complete for the primary use-case, and future versions may expand in scope. + +### Storage Questions + +#### Can I use RAID-5/RAID-6 with PVC? + +The short answer is no. The long answer is: Ceph, the storage backend used by PVC, does support "erasure coded" pools which implement a RAID-5-like (striped with distributed parity) functionality, but PVC does not support this for several reasons, mostly related to ease of management and performance. If you use PVC, you must accept at the very least a 2x storage penalty, and for true multi-node safety and resiliency, a 3x storage penalty for VM storage. This is a trade-off of the architecture and should be taken into account when sizing storage in nodes. + +#### Can I use spinning HDDs with PVC? + +You can, but you won't like the results. SSDs, and specifically datacentre-grade SSDs for resiliency, are required to obtain any sort of reasonable performance when running multiple VMs. The higher-performance the drives, the faster the storage. + +#### What network speed does PVC require? + +For optimal performance, nodes should use at least 10-Gigabit Ethernet network interfaces wherever possible, and on large clusters a dedicated 10-Gigabit "storage" network, separate from the "upstream"/"cluster" networks, is strongly recommended. The storage system performance, especially for writes, is more heavily bottlenecked by the network speed than the actual storage device speed when speaking of high-performance disks. 1-Gigabit Ethernet will be sufficient for some use-cases and is sufficient for the non-storage networks (VM traffic notwithstanding), but storage performance will become severely limited as the cluster grows. Even slower network speeds (e.g. 100-Megabit) are not sufficient for PVC to operate properly except in very limited testing scenarios. + +#### What Ceph version does PVC use? + +PVC requires Ceph 14.x (Nautilus). The official PVC repository at https://repo.bonifacelabs.ca includes Ceph 14.2.x for Debian Buster (updated regularly), since by default it only includes 12.x (Luminous). + +## About The Author + +PVC is written by [Joshua](https://www.boniface.me) [M.](https://bonifacelabs.ca) [Boniface](https://github.com/joshuaboniface). A Linux system administrator by trade, Joshua is always looking for the best solutions to his user's problems, be they developers or end users. PVC grew out of his frustration with the various FOSS virtualization tools, as well as and specifically, the constant failures of Pacemaker/Corosync to gracefully manage a virtualization cluster. He started work on PVC at the end of May 2018 as a simple alternative to a Corosync/Pacemaker-managed virtualization cluster, and has been growing the feature set and stability of the system ever since. + diff --git a/docs/cluster-architecture.md b/docs/cluster-architecture.md new file mode 100644 index 0000000..a7ff988 --- /dev/null +++ b/docs/cluster-architecture.md @@ -0,0 +1,414 @@ +# PVC Cluster Architecture considerations + +- [PVC Cluster Architecture considerations](#pvc-cluster-architecture-considerations) + * [Node Specification](#node-specification) + + [n-1 Redundancy](#n-1-redundancy) + + [CPU](#cpu) + + [Memory](#memory) + + [Disk](#disk) + + [Network](#network) + * [PVC architecture](#pvc+architecture) + + [Operating System](#operating-system) + + [Ceph Storage Layout](#ceph-storage-layout) + + [Networks](#networks) + - [System Networks](#system+networks) + - [Client Networks](#client+networks) + + [Fencing and Recovery](#fencing-and-recovery) + * [Advanced Layouts](#advanced+layouts) + + [Coordinators versus Hypervisors](#coordinators-versus-hypervisors) + + [Georedundancy](#georedundancy) + * [Example System Diagrams](#example+system-diagrams) + + [Small 3-node cluster](#small-3-node-cluster) + + [Large 8-node cluster](#large-8-node-cluster) + +This document contains considerations the administrator should make when preparing for and building a PVC cluster. It is important that prospective PVC administrators read this document *thoroughly* before deploying a cluster to ensure they understand the requirements, caveats, and important details about how PVC operates. + +## Node Specification + +PVC nodes, especially coordinator nodes, run a significant number of software applications in addition to the virtual machines (VMs). It is therefore extremely important to size the systems correctly for the expected workload while planning both for redundancy and future capacity. In general, taller nodes are better for performance, providing a more powerful cluster on fewer physical machines, though each workload may be different in this regard. + +The following table provides recommended minimum specifications for each component of the cluster nodes. In general, these minimums are the lowest possible for a production-quality cluster that would provide decent performance for up to about a dozen virtual machines. Of course, further upward scaling is recommended and the specific computational and storage needs of the VM workloads should be taken into account. + +| Resource | Recommended Minimum | +| -------- | --------------------| +| CPU generation | Intel Sandy Bridge (2011) *or* AMD Naples (2017) | +| CPU cores per node | 8 @ 2.0GHz | +| RAM per node | 32GB | +| System disk (SSD/HDD/USB/SD/eMMC) | 2x 100GB RAID-1 | +| Data disk (SSD only) | 1x 400GB | +| Network interfaces | 2x 10Gbps (LACP LAG) | +| Remote IPMI-over-IP | Available and connected | +| Total CPU cores (3 nodes healthy) | 24 | +| Total CPU cores (3 nodes n-1) | 16 | +| Total RAM (3 nodes healthy) | 96GB | +| Total RAM (3 nodes n-1) | 64GB | +| Total disk space (3 nodes) | 400GB | + +For testing, or low-budget homelab applications, some aspects can be further tuned down, however consider the following sections carefully. + +### n-1 Redundancy + +Care should be taken to examine the "healthy" versus "n-1" total resource availability. Under normal operation, PVC will use all available resources and distribute VMs across all cluster nodes. However, during single-node failure or maintenance conditions, all VMs will be required to run on the remaining hypervisors. Thus, care should be taken during planning to ensure there is sufficient resources for the expected workload of the cluster. + +The general values for default resource availability of a 3-node cluster for n-1 availability (1 node offline) are: + + * 1/3 of the total data disk space (3 copies of all data, distributed across all 3 nodes) + * 2/3 of the total RAM + * 2/3 of the total CPU cores + +For memory provisioning of VMs, PVC will warn the administrator, via a Degraded cluster state, if the "n-1" RAM quantity is exceeded by the total maximum allocation of all running VMs. If nodes are of mismatched sizes, the "n-1" RAM quantity is calculated by removing (one of) the largest node in the cluster and adding the remaining nodes' RAM counts together. + +### CPU + +CPU resources are a very important part of the overall performance of a PVC cluster. Numerous aspects of the system require high-performance CPU cores, including the VM workloads themselves, the PVC databases, and, especially, the Ceph storage subsystem. + +As a general rule, more cores, and faster cores, are always better, and real cores are preferable to SMT virtual cores in most cases. + +#### SMT + +SMT in particular can be a contentious subject, and performance can vary wildly for different workloads; thus, while they are useful, in terms of performance calculations they should always be considered as an afterthought or "bonus" to assist with many VMs contending for resources, and base specifications should be done based on the number of real CPU cores instead. + +#### CPU core counts + +The following should be considered recommended minimums for CPU core allocations: + + * PVC system daemons, including Zookeeper and PostgreSQL databases: 2 CPU cores + * Ceph Monitor and Manager processes: 1 CPU core + * Ceph OSD processes: 2 CPU cores *per OSD disk* + * Virtual Machines: 1 CPU core per vCPU in the largest spec'd VM (e.g. 12 vCPUs in a VM = 12 cores here) + +To provide an example, consider a cluster that would run 2 OSD disks per node, and want to run several VMs, the largest of which would require 12 vCPUs: + + * PVC system: 2 cores + * Ceph Mon/Mgr: 1 core + * Ceph OSDs: 2 * 2 = 4 cores + * VMs: 12 cores + +This gives a total of 19 cores, and thus a 20+ core CPU would be recommended. + +Additional CPU cores, as previously mentioned, are always better. For instance, though 2 is the recommended minimum per OSD disk, better performance can be achieved if there are 4 cores available per OSD instead. This trade-off depends heavily on the required workload and VM specifications and should be carefully considered. + +#### CPU performance + +While CPU frequency is not a tell-all or even particularly useful metric across generations or manufacturers, within a specific generation and manufacturer, faster CPUs will almost always improve performance across the board, especially when considering the Ceph storage subsystem. If a 2.0GHz and a 2.6GHz CPU of the same core count are both available, the 2.6GHz one is almost always the better choice from a pure performance perspective. + +### Memory + +Memory is extremely important to PVC clusters, and like CPU resources a not-insignificant amount of memory is required for the baseline cluster before VMs are considered. + +#### Memory allocations + +The following should be considered recommended minimums for memory allocations: + + * PVC daemons: 1 GB + * Zookeeper database: 1 GB + * PostgreSQL database: 1 GB + * Ceph Monitor and Manager processes: 1 GB + * Ceph OSD processes: 1 GB *per OSD disk* + +All additional memory can be consumed by virtual machines. + +To provide an example, in the same cluster as mentioned in the CPU section: + + * PVC system: 1 GB + * Zookeeper: 1 GB + * PostgreSQL: 1 GB + * Ceph Mon/Mgr: 1 GB + * Ceph OSDs: 2 * 1 GB = 2 GB + +This gives a total of 6 GB of memory for the base system, with VMs requiring additional memory. + +#### VM Memory Overprovisioning + +An important consideration is that the KVM hypervisor used by PVC will only allocate guest memory *as required by the guest*, but PVC tracks memory allocation based on the allocated maximum. Thus, for example, a VM may be allocated 8192 MB of memory in PVC, and thus the PVC system considers 8 GB "allocated" and "provisioned" to this VM, but if the actual guest is only using 500 MB of that memory, the actual memory usage on the hypervisor node will be 500 MB for that VM. Thus it is possible for "all" memory to be allocated on a node but there still be many GB of "free" memory. This is an intentional design decision to avoid excessive overprovisioning of memory and thus situations where non-VM processes become memory starved, as the PVC system itself does *not* track the usage by the aforementioned processes. + +#### Memory Performance + +Given the recommended CPU requirements, all PVC hypervisors should contain at least DDR3 memory, which is sufficiently performant for all tasks. Memory latency and performance, however, can become important especially in large NUMA systems, and especially with regards to the Ceph storage subsystem. Care should be taken to optimize the memory layout in nodes, for instance making use of all available memory channels in the CPU architecture and preferring 1 DIMM-per-channel (DPC) over 2 DPC. + +#### Ceph OSD memory utilization + +While the recommended *minimum* is 1 GB per OSD process, in reality, Ceph can allocate between 4 and 6 GB of memory per OSD process, especially for caching metadata and other frequently-used data. Thus, for maximum performance, 4 GB instead of 1 GB should be allocated per-OSD. + +#### Memory limit tuning + +The PVC Ansible deployment system allows the administrator to specify limits on some aspects of the aforementioned memory requirements, for instance limiting Zookeeper or Ceph OSD processes to lower amounts of memory. This is not recommended except in situations where memory is extremely constrained; in such situations adding additional memory to nodes is always preferable. For details and examples please see the Ansible variable files. + +### Disk + +#### System Disks + +The performance of system disks is of critical importance in the PVC cluster. At least 32GB of space are required, and at least 100GB is recommended to ensure optimal performance. The system disks should be fast SAS HDDs, SSDs, eMMC flash, class-10 SD, or other flash-based mediums, and RAID-1 is critical for reliability purposes, especially for more wear- or failure-sensitive media types. + +PVC will store the various databases on these disks, so overall performance can affect the responsiveness of the system. However note that no VM data is ever stored on system disks; this is provided exclusively by the Ceph data disks (OSDs). + +#### Ceph OSD disks + +All VM block devices are stored on Ceph OSD data disks. The default pool configuration of the Ceph storage subsystem uses a `copies=3` layout with a `host`-level failure domain; thus, in a 3-node cluster, each block of data is stored 3 times, once per node. This ensures that 2 copies of each piece of data are available even if a host is down, at the cost of 1/3 of the total overall storage space. Other configurations are possible, but this is the minimum recommended. + +The performance of VM disks will be dictated almost exclusively by the performance of these disks in combination with the CPU resources of the system as discussed previously. Very fast, robust, and resilient storage is highly recommended for OSD disks to maximize performance and longevity. High-performance SATA, SAS, or NVMe SSDs are recommended for this task, sized according to the expected workload. Spinning disks (HDDs) are *not* recommended for this purpose, and their very low random performance will significantly limit the overall storage performance of the cluster. + +Initially, it is optimal if all nodes contain the same number and same size of OSD disks, to ensure even distribution of the data across all disks and thus maximize performance. PVC supports adding additional OSDs at a later time, however the administrator should be cautious to always add new disks in parallel on all nodes at the same time, as otherwise the replication ratio will prevent the new space from being utilized. Thus, in a 3-node cluster, disks must be added 3-at-a-time to all 3 nodes, and these disks must be identically sized, in order to increase the total usable storage space by the value of one of these disks. + +In addition to the primary data disks, PVC also supports the offloading of the Ceph BlueStore OSD database and WAL functions of the OSDs onto a separate OSD database volume group on a dedicated storage device. In the normal use-case, this would be an extremely fast and endurant Intel Optane or similar extremely-performant NVMe SSD which is significantly faster than the primary data SSDs. This will help accelerate random write I/Os and metadata lookups, especially when using lower-performance SATA or SAS SSDs. Generally speaking this volume should be large enough to support 5% of the capacity of all OSDs on a node, with some room for future expansion. Only one such device and volume group is supported at this time. + +### Network + +Because PVC makes extensive use of cross-node communications, high-throughput and low-latency networking is critical. At a minimum, 10-gigabit networking is recommended to ensure suitable throughput for the storage subsystem as well as for VM traffic. Higher-speed networking can also improve performance, especially when using extremely fast Ceph OSD disks. + +A minimum of 2 network interfaces is recommended. These should then be combined into a logical aggregate (LAG) using 802.3ad (LACP) to provide redundant links and a boost in available bandwidth. Additional NICs can also be used to separate discrete parts of the networking stack, which will be discussed below. + +#### Remote IPMI-over-IP + +IPMI provides a method to manage the physical chassis' of nodes from outside of their operating system. Common implementations include Dell iDRAC, HP iLO, Cisco CIMC, and others. + +PVC nodes in production deployments should always feature an IPMI-over-IP interface of some kind, which is then reachable either in, or via, the Upstream system network (see [System Networks](#system-networks)). This requirement is discussed in more detail during the [Fencing and Recovery](#fencing-and-recovery) section below. + +## PVC Architecture + +### Operating System + +As an underlying OS, only Debian GNU/Linux 10.x "Buster" or 11.x "Bullseye" are supported by PVC. This is the operating system installed by the PVC [node installer](https://github.com/parallelvirtualcluster/pvc-installer) and expected by the PVC [Ansible configuration system](https://github.com/parallelvirtualcluster/pvc-ansible). Ubuntu or other Debian-derived distributions may work, but are not officially supported. PVC also makes use of a custom repository to provide the PVC software and (for Debian Buster) an updated version of Ceph beyond what is available in the base operating system, and this is only compatible officially with Debian 10 or 11. PVC will generally be upgraded regularly to support new Debian versions. As a rule, using the current versions of the official node installer and Ansible repository is the preferred and only supported method for deploying PVC. + +Currently, only the `amd64` (Intel 64 or AMD64) architecture is officially supported by PVC. Given the cross-platform nature of Python and the various software components in Debian, it may work on `armhf` or `arm64` systems as well, however this has not been tested by the author and is not officially supported at this time. + +### Ceph Storage Layout + +PVC makes use of Ceph, a distributed, replicated, self-healing, and self-managing storage system to provide shared VM storage. While a PVC administrator is not required to understand Ceph for day-to-day administration, and PVC provides interfaces to most of the common storage functions required to operate a cluster, at least some knowledge of Ceph is advisable. + +The Ceph subsystem of PVC creates a "hyperconverged" cluster whereby storage and VM hypervisor functions are collocated onto the same physical servers; PVC does not differentiate between "storage" and "compute" nodes, and while storage support can be disabled and an external Ceph cluster used, this is not recommended. The performance of the storage must be taken into account when sizing the nodes as mentioned above. + +Ceph on PVC is laid out similar to the other daemons. The Ceph Monitor and Manager functions are delegated to the Coordinators over the storage network, with all nodes connecting to these hosts to obtain the CRUSH maps and select OSD disks. OSDs are then distributed on all hosts, potentially including non-coordinator hypervisors if desired, and communicate with clients and each other over the storage network. + +Disks must be balanced across all storage-containing nodes. For instance, adding 1 disk to 1 node is not sufficient to increase storage space; 1 disk must be added to all storage-containing nodes, based on the configured replication scheme of the various pools (see below), at the same time for the available space to increase. Ideally, disk sizes should also be identical across all storage disks, though the weight of each disk can be configured when added to the cluster. Generally speaking, fewer larger disks are preferable to many smaller disks to minimize storage resource utilization, however slightly more storage performance can be gained from using many small disks, if the other cluster hardware, and specifically CPUs, are performant enough. The administrator should therefore always aim to choose the biggest disks they can and grow by adding more identical disks as space or performance needs grow. + +PVC Ceph pools make use of the replication mechanism of Ceph to store multiple copies of each object, thus ensuring that data is always available even when a host is unavailable. Only "replica"-based Ceph redundancy is supported by PVC; erasure coded pools are not supported due to major performance impacts related to rewrites and random I/O as well as management overhead. + +The default replication level for a new pool is `copies=3, mincopies=2`. This will store 3 copies of each object, with a host-level failure domain, and will allow I/O as long as 2 copies are available. Thus, in a cluster of any size, all data is fully available even if a single host becomes unavailable. It will however use 3x the space for each piece of data stored, which must be considered when sizing the disk space for the cluster: a pool in this configuration, running on 3 nodes each with a single 400GB disk, will effectively have 400GB of total space available for use. As mentioned above, new disks must also be added in groups across nodes equal to the total number of `copies` to ensure new space is usable; for instance in a `copies=3` scheme, at least 3 disks must thus be added to different hosts at the same time for the available space to grow. + +Non-default values can also be set at pool creation time. For instance, one could create a `copies=3, mincopies=1` pool, which would allow I/O with two hosts down, but leaves the cluster susceptible to a write hole should a disk fail in this state; this configuration is not recommended in most situations. Alternatively, for additional resilience, one could create a `copies=4, mincopies=2` pool, which would also allow 2 hosts to fail, without a write hole, but would consume 4x the space for each piece of data stored and require new disks to be added in groups of 4 instead. Practically any combination of values is possible, however these 3 are the most relevant for most use-cases, and for most, especially small, clusters, the default is sufficient to provide solid redundancy and guard against host failures until the administrator can respond. + +Replication levels cannot be changed within PVC once a pool is created, however they can be changed via manual Ceph commands on a coordinator should the administrator require this, though discussion of this process is outside of the scope of this documentation. The administrator should carefully consider sizing, failure domains, and performance when first selecting storage devices and creating pools, to ensure the right level of resiliency versus data usage for their use-case and planned cluster size. + +### Networks + +At a minimum, a production PVC cluster should use at least two 10Gbps Ethernet interfaces, connected in an LACP or active-backup bond on one or more switches. On top of this bond, the various cluster networks should be configured as 802.3q vLANs. PVC is be able to support configurations without bonding or 802.1q vLAN support, using multiple physical interfaces and no bridged client networks, but this is strongly discouraged due to the added complexity this introduces; the switches chosen for the cluster should include these requirements as a minimum. + +More advanced physical network layouts are also possible. For instance, one could have two isolated networks. On the first network, each node has two 10Gbps Ethernet interfaces, which are combined in a bond across two redundant switch fabrics and that handle the upstream and cluster networks. On the second network, each node has an additional two 10Gbps, which are also combined in a bond across the redundant switch fabrics and handle the storage network. This configuration could support up to 10Gbps of aggregate client traffic while also supporting 10Gbps of aggregate storage traffic. Even more complex network configurations are possible if the cluster requires such performance. See the [Example System Diagrams](#example-system-diagrams) section for some basic topology examples. + +Only Ethernet networks are supported by PVC. More exotic interconnects such as Infiniband are not supported by default, and must be manually set up with Ethernet (e.g. EoIB) layers on top to be usable with PVC. + +Lower-speed networks (e.g. 1Gbps or 100Mbps) should not be used as these will severely bottleneck the performance of the storage subsystem. In an advanced split layout, it may be acceptable to use 1Gbps interfaces for VM guest networks, however the core system networks should always be a minimum of 10Gbps. + +PVC manages the IP addressing of all nodes itself and creates the required addresses during node daemon startup; thus, the on-boot network configuration of each interface should be set to "manual" with no IP addresses configured. This can be ignored safely, however, and the addresses specified manually in the networking configurations. PVC nodes use a split (`/etc/network/interfaces.d/`) network configuration model. + +### System Networks + +#### Upstream: Connecting the nodes to the wider world + +The upstream network functions as the main upstream for the cluster nodes, providing Internet access and a way to route managed client network traffic out of the cluster. In most deployments, this should be an RFC1918 private subnet with an upstream router which can perform NAT translation and firewalling as required, both for the cluster nodes themselves, and also for any RFC1918 managed client networks. + +The floating IP address in the cluster network can be used as a single point of communication with the active primary node, for instance to access the DNS aggregator instance or the management API. PVC provides only limited access control mechanisms to the API interface, so the upstream network should always be protected by a firewall; running PVC directly accessible on the Internet is strongly discouraged and may post a serious security risk, and all access should be restricted to the smallest possible set of remote systems. + +Nodes in this network are generally assigned static IP addresses which are configured at node install time in the [Ansible deployment configuration](https://github.com/parallelvirtualcluster/pvc-ansible). + +The upstream router should be able to handle static routes to the PVC cluster, or form a BGP neighbour relationship with the coordinator nodes and/or floating IP address to learn routes to the managed client networks. + +The upstream network should generally be large enough to contain: + +0. The upstream router(s) +0. The nodes themselves +0. In most deployments, the node IPMI management interfaces. + +For example, for a 3+ node cluster, up to about 90 nodes, the following configuration might be used: + +| Description | Address | +|-------------|---------| +| Upstream network | 10.0.0.0/24 | +| Router VIP address | 10.0.0.1 | +| Router 1 address | 10.0.0.2 | +| Router 2 address | 10.0.0.3 | +| PVC floating address | 10.0.0.10 | +| node1 | 10.0.0.11 | +| node2 | 10.0.0.12 | +| etc. | etc. | +| node1-ipmi | 10.0.0.111 | +| node2-ipmi | 10.0.0.112 | +| etc. | etc. | + +For even larger clusters, a `/23` or even larger network may be used. + +#### Cluster: Connecting the nodes with each other + +The cluster network is an unrouted private network used by the PVC nodes to communicate with each other for database access and Libvirt migrations. It is also used as the underlying interface for the BGP EVPN VXLAN interfaces used by managed client networks. + +The floating IP address in the cluster network can be used as a single point of communication with the active primary node. + +Nodes in this network are generally assigned IPs automatically based on their node number (e.g. node1 at `.1`, node2 at `.2`, etc.). The network should be large enough to include all nodes sequentially. + +Generally the cluster network should be completely separate from the upstream network, either a separate physical interface (or set of bonded interfaces) or a dedicated vLAN on an underlying physical device, but they can be collocated if required. + +#### Storage: Connecting Ceph daemons with each other and with OSDs + +The storage network is an unrouted private network used by the PVC node storage OSDs to communicated with each other, for Ceph management functionality, and for QEMU-to-Ceph disk access, without using the main cluster network and introducing potentially large amounts of traffic there. + +The floating IP address in the storage network can be used as a single point of communication with the active primary node, though this will generally be of little use. + +Nodes in this network are generally assigned IPs automatically based on their node number (e.g. node1 at `.1`, node2 at `.2`, etc.). The network should be large enough to include all nodes sequentially. + +The administrator may choose to collocate the storage network on the same physical interface as the cluster network, or on a separate physical interface. This should be decided based on the size of the cluster and the perceived ratios of client network versus storage traffic. In large (>3 node) or storage-intensive clusters, this network should generally be a separate set of fast physical interfaces, separate from both the upstream and cluster networks, in order to maximize and isolate the storage bandwidth. If the administrator does choose to collocate these networks, they may also share the same IP address, thus eliminating any distinction between the Cluster and Storage networks. The PVC software handles this natively when the Cluster and Storage IPs of a node are identical. + +### Client Networks + +#### Bridged (unmanaged) Client Networks + +The first type of client network is the unmanaged bridged network. These networks have a separate vLAN on the device underlying the other networks, which is created when the network is configured. VMs are then bridged into this vLAN. + +With this client network type, PVC does no management of the network. This is left entirely to the administrator. It requires switch support and the configuration of the vLANs on the switchports of each node's physical interfaces before enabling the network. + +Generally, the same physical network interface will underlay both the cluster networks as well as bridged client networks. PVC does however support specifying a separate physical device for bridged client networks, for instance to separate these networks onto a different physical interface from the main cluster networks. + +#### VXLAN (managed) Client Networks + +The second type of client network is the managed VXLAN network. These networks make use of BGP EVPN, managed by route reflection on the coordinators, to create virtual layer 2 Ethernet tunnels between all nodes in the cluster. VXLANs are then run on top of these virtual layer 2 tunnels, with the active primary PVC node providing routing, DHCP, and DNS functionality to the network via a single IP address. + +With this client network type, PVC is in full control of the network. No vLAN configuration is required on the switchports of each node's physical interfaces, as the virtual layer 2 tunnel travels over the cluster layer 3 network. All client network traffic destined for outside the network will exit via the upstream network interface of the active primary coordinator node. + +NOTE: These networks may introduce a bottleneck and tromboning if there is a large amount of external and/or inter-network traffic on the cluster. The administrator should consider this carefully when deciding whether to use managed or bridged networks and properly evaluate the inter-network traffic requirements. + +#### SR-IOV Client Networks + +The third type of client network is the SR-IOV network. SR-IOV (Single-Root I/O Virtualization) is a technique and feature enabled on modern high-performance NICs (for instance, those from Intel or nVidia) which allows a single physical Ethernet port (a "PF" in SR-IOV terminology) to be split, at a hardware level, into multiple virtual Ethernet ports ("VF"s), which can then be managed separately. Starting with version 0.9.21, PVC support SR-IOV PF and VF configuration at the node level, and these VFs can be passed into VMs in two ways. + +SR-IOV's main benefit is to offload bridging and network functions from the hypervisor layer, and direct them onto the hardware itself. This can increase network throughput in some situations, as well as provide near-complete isolation of guest networks from the hypervisors (in contrast with bridges which *can* expose client traffic to the hypervisors, and VXLANs which *do* expose client traffic to the hypervisors). For instance, a VF can have a vLAN specified, and the tagging/untagging of packets is then carried out at the hardware layer. + +There are however caveats to working with SR-IOV. At the most basic level, the biggest difference with SR-IOV compared to the other two network types is that SR-IOV must be configured on a per-node basis. That is, each node must have SR-IOV explicitly enabled, it's specific PF devices defined, and a set of VFs created at PVC startup. Generally, with identical PVC nodes, this will not be a problem but is something to consider, especially if the servers are mismatched in any way. It is thus also possible to set some nodes with SR-IOV functionality, and others without, though care must be taken in this situation to set node limits in the VM metadata of any VMs which use SR-IOV VFs to prevent failed migrations. + +PFs are defined in the `pvcnoded.yml` configuration of each node, via the `sriov_device` list. Each PF can have an arbitrary number of VFs (`vfcount`) allocated, though each NIC vendor and model has specific limits. Once configured, specifically with Intel NICs, PFs (and specifically, the `vfcount` attribute in the driver) are immutable and cannot be changed easily without completely flushing the node and rebooting it, so care should be taken to select the desired settings as early in the cluster configuration as possible. + +Once created, VFs are also managed on a per-node basis. That is, each VF, on each host, even if they have the exact same device names, is managed separately. For instance, the PF `ens1f0` creating a VF `ens1f0v0` on "`hv1`", can have a different configuration from the identically-named VF `ens1f0v0` on "`hv2`". The administrator is responsible for ensuring consistency here, and for ensuring that devices do not overlap (e.g. assigning the same VF name to VMs on two separate nodes which might migrate to each other). PVC will however explicitly prevent two VMs from being assigned to the same VF on the same node, even if this may be technically possible in some cases. + +When attaching VFs to VMs, there are two supported modes: `macvtap`, and `hostdev`. + +`macvtap`, as the name suggests, uses the Linux `macvtap` driver to connect the VF to the VM. Once attached, the vNIC behaves just like a "bridged" network connection above, and like "bridged" connections, the "mode" of the NIC can be specified, defaulting to "virtio" but supporting various emulated devices instead. Note that in this mode, vLANs cannot be configured on the guest side; they must be specified in the VF configuration (`pvc network sriov vf set`) with one vLAN per VF. VMs with `macvtap` interfaces can be live migrated between nodes without issue, assuming there is a corresponding free VF on the destination node, and the SR-IOV functionality is transparent to the VM. + +`hostdev` is a direct PCIe pass-through method. With a VF attached to a VM in `hostdev` mode, the virtual PCIe NIC device itself becomes hidden from the node, and is visible only to the guest, where it appears as a discrete PCIe device. In this mode, vLANs and other attributes can be set on the guest side at will, though setting vLANs and other properties in the VF configuration is still supported. The main caveat to this mode is that VMs with connected `hostdev` SR-IOV VFs *cannot be live migrated between nodes*. Only a `shutdown` migration is supported, and, like `macvtap`, an identical PCIe device at the same bus address must be present on the target node. To prevent unexpected failures, PVC will explicitly set the VM metadata for the "migration method" to "shutdown" the first time that a `hostdev` VF is attached to it; if this changes later, the administrator must change this back explicitly. + +Generally speaking, SR-IOV connections are not recommended unless there is a good use-case for them. On modern hardware, software bridges are extremely performant, and are much simpler to manage. The functionality is provided for those rare use-cases where SR-IOV is absolutely required by the administrator, but care must be taken to understand all the requirements and caveats of SR-IOV before using it in production. + +#### Other Client Networks + +Future PVC versions may support other client network types, such as direct-routing between VMs. + +### Fencing and Recovery + +Self-management and self-healing are important components of PVC's design, and to accomplish this, PVC contains automated fencing and recovery functions to handle situations where nodes crash or become unreachable. PVC is then able, if properly configured, to directly power-cycle the failed node, and bring up any VMs that were running on it on the remaining hypervisors. This ensures that, while there might be a few minutes of downtime for VMs, they are recovered as quickly as possible without human intervention. + +To operate correctly, these functions require each node in the cluster to have a functional IPMI-over-IP setup with a configured user who is able to perform chassis power commands. This differs depending on the chassis manufacturer and model, and should be tested prior to deploying any production cluster. If IPMI is not configured correctly at node startup, the daemon will warn and disable automatic recovery of the node. The IPMI should be present in the Upstream system network (see [System Networks](#system-networks) above), or in another secured network which is reachable from the Upstream system network, whichever is more convenient for the layout of the networks. + +The general process is divided into 3 sections: detecting node failures, fencing nodes, and recovering from fenced nodes. Note that this process only applies to nodes in the `run` "daemon state"; if a node daemon cleanly shuts down (for instance due to a service restart or administrative action), it will not be fenced. + +#### Detecting Failed Nodes + +Within the PVC configuration, each node has 3 settings which determine the failure detection time. The first is the `keepalive_interval` setting. This is normally set to 5 seconds, and is the interval at which the node daemon of each node sends its keepalives (as well as gathers statistics about running VMs, Ceph components, etc.). This interval should never need to be changed, but is configurable for maximum flexibility in corner cases. During each keepalive, the node updates a specific key in the Zookeeper cluster with the current UNIX timestamp, which determines when the node was last alive. During their own keepalives, the other nodes check their peers' timestamps to confirm if they are updating normally. Note that, due to this happening during the peer keepalives, if all nodes lose contact with the Zookeeper database, they will *not* immediately begin fencing each other, since the keepalives will not complete; they will, however, upon recovery, jump immediately to the next section when they all realize that their last keepalives were over the threshold, and this situation is discussed there. + +The second option is the `fence_intervals` setting. This option determines how many keepalive intervals a node can miss before it is marked `dead` and a fencing sequence started. This is normally set to 6 intervals, which combined with the 5 second `keepalive_interval`, gives a total of 30 seconds (+/- up to another 5 second `keepalive_interval` for peers should they not line up) for the node to be without updates before fencing begins. + +The third setting is optional, and is best used in situations where the IPMI connectivity of a node is excessively flaky or can be impaired (e.g. georedundant clusters), or where VM uptime is more important than the burden of recovering from a split-brain situation, and is not as extensively tested. This option is `suicide_intervals`, and if set to a non-0 value, is the number of keepalive intervals before a node *itself* determines that it should forcibly power itself off, which should always be equal to or less than the normal `fence_intervals` setting. Naturally, the node must be somewhat functional to do this, and this can go very wrong, so using this option is not normally recommended. + +#### Fencing Nodes + +Once the cluster, and specifically one node in the cluster, has determined that a given node is `dead` due to a lack of keepalives, the fencing process starts. This spawns a dedicated child thread within the node daemon of the detecting node, which continually monitors the state of the `dead` node and then performs the fence. + +During the `dead` process, the failed node has 6 chances, called "saving throws", at `keepalive_interval` second windows, to send another keepalive before it is fenced. This additional, fixed, delay helps ensure that the cluster will gracefully recover from intermittent network failures or loss of Zookeeper contact, by providing nodes up to another 6 keepalive intervals to save themselves once the fence timer actually begins. This bring the total time, with default options, of a node stopping contact to a node being fenced, to between 60 and 65 seconds. This duration is considered by the author an acceptable compromise between speedy recovery and avoiding false positives (and hence larger outages). + +Once a node has been marked `dead` and has failed its 6 "saving throws", the fence process triggers an IPMI chassis reset sequence. First, the node is issued an IPMI `chassis power off` command to trigger a cold system shutdown. Next, it waits a fixed 1 second and then checks and logs the current `chassis power state`, and then issues a `chassis power on` signal to start up the node. It then finally waits a fixed 2 seconds, and then checks the current `chassis power status`. Using the results of these 3 commands, PVC is then able to determine with near certainty whether the node has truly been forced offline or not, and it can proceed to the next step. + +#### Recovery from Node Fences + +Once a node has been fenced, successfully or not, the system waits for one keepalive interval before proceeding. + +The cluster then determines what to do based both on the result of the fence (whether the node was determined to have been successfully cold-reset or not) and on two additional configuration values. The first, `successful_fence`, specifies what action to take when the fence was successful, and is either `migrate` (VMs to other nodes), the default, or `None` (no action). The second, `failed_fence`, is an identical choice for when the fence was unsuccessful, and defaults to `None`. + +If the fence was successful and `successful_fence` is set to `None`, then no migration takes place and the VMs on the fenced node will remain offline until the node recovers. If instead `successful_fence` is set to the default of `migrate`, the system will then begin migrating (and hence, starting) VMs that were active on the failed node to other nodes in the cluster. During this special `fence-flush` action, any stale RBD locks on the storage volumes are forcibly cleared, and this is considered safe since the fenced node is determined to have successfully been powered off and the VMs thus terminated. Once all VMs are migrated, the fenced node will then be set to a normal `flushed` state, as if it had been cleanly flushed before powering off. If and when the node returns to active, healthy service, either automatically (if the reset cleared the fault condition) or after human intervention, VMs can then migrate back and the cluster can resume normal operation; otherwise the cluster will remain in the degraded state until corrected. + +If the fence was unsuccessful and `failed_fence` is set to the default of `None`, no automatic recovery takes place, since the cluster cannot determine that it is safe to do so. This would most commonly occur during network partitions where the `dead` node potentially remains up with VMs running on it, and the cluster is now in a split-brain situation. The `suicide_interval` option mentioned above is provided for this specific situation, and would allow the administrator to set the `failed_fence` action to `migrate` as well, as they could be somewhat confident that the node will have forcibly terminated itself. However due to the inherent potential for danger in this scenario, it is recommended to leave these options at their defaults, and handle such situations manually instead, as well as ensuring proper network design to avoid the potential for such split-brain situations to occur. + +## Advanced Layouts + +### Coordinators versus Hypervisors + +While a normal basic PVC cluster would consist of 3, or perhaps 5, nodes, PVC is able to scale up much further by differentiating between "coordinator" and "hypervisor" nodes. Such a basic cluster would consist only of coordinator nodes. Scaling up however, it is prudent to add new nodes as hypervisor nodes instead to minimize database scaling problems. + +#### Coordinators + +Coordinators are a special set of 3 or 5 nodes with additional functionality. The coordinator nodes run, in addition to the PVC software itself, a number of databases and additional functions which are required by the whole cluster. An odd number of coordinators is *always* required to maintain quorum, though there are diminishing returns when creating more than 3. As mentioned above, generally for small clusters all nodes are coordinators. + +These additional functions are: + +0. The Zookeeper database cluster containing the cluster state and configuration +0. The Patroni PostgreSQL database cluster containing DNS records for managed networks and provisioning configurations +0. The FRR EBGP route reflectors and upstream BGP peers + +In addition to these functions, coordinators can usually also run all other PVC node functions. + +The set of coordinator nodes is generally configured at cluster bootstrap, initially with 3 nodes, which are then bootstrapped together to form a basic 3-node cluster. Additional nodes, either as coordinators or as hypervisors, can then be added to the running cluster to bring it up to its final size, either immediately or as the needs of the cluster change. + +##### The Primary Coordinator + +Within the set of coordinators, a single primary coordinator is elected at cluster startup and as nodes start and stop, or in response to administrative commands. Once a node becomes primary, it will remain so until it stops or is told not to be. This coordinator is responsible for some additional functionality in addition to the other coordinators. These additional functions are: + +0. The floating IPs in the main networks +0. The default gateway IP for each managed client network +0. The DNSMasq instance handling DHCP and DNS for each managed client network +0. The API and provisioner clients and workers + +PVC gracefully handles transitioning primary coordinator state, to minimize downtime. Workers will continue to operate on the old coordinator if available after a switchover and the administrator should be aware of any active tasks before switching the active primary coordinator. + +#### Hypervisors + +Hypervisor nodes do not run any of the database or routing functionality of coordinator nodes, nor can they become the primary coordinator node (for obvious reasons). When scaling a cluster up beyond the initial 3, or perhaps 5, coordinator nodes, or when an even number of nodes (e.g. 4) may be desired, any nodes beyond the 3 coordinators should be added as hypervisors. + +Hypervisor nodes are capable of running VMs and Ceph OSD disks, just like coordinator nodes, though the latter is optional. + +PVC has no limit to the number of hypervisor nodes that can connect to a set of coordinators, though beyond a dozen or so total nodes, a more scale-focused infrastructure solution may be warranted. + +### Georedundancy + +PVC supports geographic redundancy of nodes in order to facilitate disaster recovery scenarios when uptime is critical. Functionally, PVC behaves the same regardless of whether the 3 or more coordinators are in the same physical location, or remote physical locations. + +When using geographic redundancy, there are several caveats to keep in mind: + +* The Ceph storage subsystem is latency-sensitive. With the default replication configuration, at least 2 writes must succeed for the write to return a success, so the total write latency of a write on any system will be equal to the maximum latency between any two nodes. It is recommended to keep all PVC nodes as "close" as possible latency-wise or storage performance may suffer. + +* The inter-node PVC networks (see [System Networks](#system-networks)) must be layer-2 networks (broadcast domains). These networks must be spanned to all nodes in all locations. + +* The number of sites and positioning of coordinators at those sites is important. A majority (at least 2 in a 3-coordinator cluster, or 3 in a 5-coordinator cluster) of coordinators must be able to reach each other in a failure scenario for the cluster as a whole to remain functional. Thus, configurations such as 2 + 1 or 3 + 2 splits across 2 sites do *not* provide full redundancy, and the whole cluster will be down if the majority site is down. It is thus recommended to always have an odd number of sites to match the odd number of coordinators, for instance a 1 + 1 + 1 or 2 + 2 + 1 configuration. Also note that all hypervisors much be able to reach the majority coordinator group or their storage will be impacted as well. + + This diagram outlines the supported and unsupported/unreliable georedundant configurations for 3 nodes. Care must always be taken to ensure that the cluster can operate with the loss of any given georeundant site. + + ![georeundancy-caveats](/images/georedundancy-caveats.png) + + *Above: Supported and unsupported/unreliable georedundant configurations* + +* Even if the PVC software itself is in an unmanageable state, VMs will continue to run if at all possible. However, since the storage subsystem makes use of the same quorum, losing more than half of the coordinator nodes will very likely result in storage interruption as well, which will affect running VMs. + +* Nodes in remote geographic locations might not be able to be fenced by the remaining PVC nodes if the entire site is unreachable. The cluster will thus be unable to automatically recover VMs at the failed site should it go down. If at all possible, redundant links to georedundant sites are recommended to ensure there is always a network path. Note that the `suicide_interval` configuration option, while it might seem to help here, will not, because the remaining nodes will not be able to reliably confirm if the remote site actually *did* shut itself off. Thus automatic failover of georedundant sides is a potential deficiency that must be considered. + +If these requirements cannot be fulfilled, it may be best to have separate PVC clusters at each site and handle service redundancy at a higher layer to avoid a major disruption. + +## Example System Diagrams + +This section provides diagrams of 2 best-practice cluster configurations. These diagrams can be extrapolated out to almost any possible configuration and number of nodes. + +#### Small 3-node cluster + +[![Small 3-node cluster](/images/pvc-3-node-cluster.png)](/images/pvc-3-node-cluster.png) + +*Above: A diagram of a simple 3-node cluster with all nodes as coordinators. Dual 10 Gbps network interface per node, unified physical networking with collapsed cluster and storage networks.* + +#### Large 8-node cluster + +[![Larger 8-node cluster](/images/pvc-8-node-cluster.png)](/images/pvc-8-node-cluster.png) + +*Above: A diagram of a large 8-node cluster with 3 coordinators and 5 hypervisors. Quad 10Gbps network interfaces per node, split physical networking into guest/cluster and storage networks.* diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..fb2a3fe --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,207 @@ +# Getting started: Deploying a Parallel Virtual Cluster + +One of PVC's design goals is administrator simplicity. Thus, it is relatively easy to get a cluster up and running in about 2 hours with only a few configuration steps, a set of nodes (with physical or remote vKVM access), and the provided tooling. This guide will walk you through setting up a simple 3-node PVC cluster from scratch, ending with a fully-usable cluster ready to provision virtual machines. + +**NOTE:** All domains, IP addresses, etc. used in this guide are **examples**. Be sure to modify the commands and configurations to suit your specific systems and needs. + +### Part One: Cluster Design, Node Procurement & Setup, and Management Host Configuration + +#### Cluster Design + +It is important to consider the design of your PVC cluster before deploying it. While PVC is very flexible, and a cluster easily expanded later, some aspects are fixed after bootstrapping, so consider these carefully. + +0. Read through the [Cluster Architecture documentation](/cluster-architecture). This documentation details and explains the requirements of a PVC cluster. It is important to understand these requirements before proceeding. + +0. Prepare your cluster design worksheet as outlined in the Cluster Architecture documentation linked above. You will use this worksheet to populate data throughout this guide. + +#### Node Procurement + +0. Select your physical nodes. Some examples are outlined in the Cluster Architecture documentation linked above. For purposes of this guide, we will be using a set of 3 Dell PowerEdge R630 servers. + + **NOTE:** This example selection sets some definitions below. For instance, we will refer to the "iDRAC" rather than using any other term for the integrated lights-out management/IPMI system, for clarity and consistency going forward. Adjust this to your cluster accordingly. + +#### Node Physical Setup + +0. Connect your physical nodes to power and networking according to your design worksheet as outlined in the Cluster Architecture documentation linked above. While you don't need to connect everything now (e.g. only 1 NIC in a bond or one power cable), you must be able to bring up the cluster as you would in producting during the initial deployment. + +0. Ensure your systems start up and are running the latest firmware. While stock firmware is usually OK, this can be the source of elusive bugs later and is easiest to do early on. + +0. If applicable to your systems, create any hardware RAID arrays for system disks now. As outlined in the Cluster Architecture documentation, the system disk of a PVC system should be a hardware RAID-1 of two relatively low-capacity SSDs (120-240GB). In our example we only use a single system SSD disk, and even in production this may be fine, but will cause the loss of a node should the disk ever fail. + +0. Ensure that all data OSD disks are set to "non-RAID" mode, i.e. direct host passthrough. These disks should be exposed directly to the operating system unmolested. + + **NOTE:** Some RAID controllers, for instance older HP "Smart" Array controllers, do not permit direct passthrough. While we do not recommend using such systems for PVC, you can work around this by creating single-disk RAID-0 volumes, though be aware that doing so will result in missing SMART data for the disks. + +#### Management Host Configuration + +0. Prepare a management host for your PVC cluster(s). The management host can be nearly anything from your local workstation or laptop, to a dedicated machine, to a VM in another virtualization cluster. It must however meet the following requirements: + + a. The management host must be running a recent version of some flavour of Debian GNU/Linux. This should ideally be Debian itself (version 11 or newer) but could also be a derivative like Ubuntu. The Windows Subsystem for Linux may work, but has not been tested by the author. Non-Debian Linux, or MacOS, maybe functional but the deployment of dependencies will be manual. + + b. The management host must have at least 10GB of free space to hold temporary files during later steps, though the overall steady-state utilization of the PVC management framework is very small. + + c. The management host must have unmolested network access to the PVC cluster, both during bootstrap as well as afterwards over the "upstream" network. + +0. Download the [`create-local-repo.sh` BASH script](https://github.com/parallelvirtualcluster/pvc-ansible/raw/master/create-local-repo.sh) to your management system. Do not try to pipe directly into a BASH shell or you might miss the prompts. + +0. Run the `create-local-repo.sh` BASH script to prepare a local copy of the repositories needed for managing a PVC cluster. Follow the steps upon completion to finish preparing your local repository. + +0. It is recommended, though not required, to add the PVC CLI client to the management host as well. You can do this either by downloading the [`pvc-client-cli*.deb` file from the latest release](https://github.com/parallelvirtualcluster/pvc/releases) and installing it manually with `dpkg --install`, or (recommended) by adding the PVC repository and installing via `apt` by running the following commands: + + ``` + $ sudo mkdir -p /etc/apt/keyrings + $ wget -O- https://repo.parallelvirtualcluster.org/debian/pvc.pub | sudo gpg --dearmor --output /etc/apt/keyrings/pvc.gpg + $ CODENAME="$( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release )" + $ echo "deb [signed-by=/etc/apt/keyrings/pvc.gpg] https://repo.parallelvirtualcluster.org/debian/ ${CODENAME} pvc" | sudo tee /etc/apt/sources.list.d/pvc.list + $ sudo apt update + $ sudo apt install pvc-client-cli + ``` + + **NOTE:** Valid `CODENAME` values for the above commands are `buster`, `bullseye`, and `bookworm`. Ubuntu codenames are not supported; use the latest Debian codename instead. + +### Part Two: Prepare Cluster Configuration and Installer ISO + +0. In your + + + + +### Part One - Preparing for bootstrap + +0. Read through the [Cluster Architecture documentation](/cluster-architecture). This documentation details the requirements and conventions of a PVC cluster, and is important to understand before proceeding. + +0. Download the latest copy of the [`pvc-ansible`](https://github.com/parallelvirtualcluster/pvc-ansible) repository to your local machine. + +0. Leverage the `create-local-repo.sh` script in the `pvc-ansible` directory to set up a local cluster configuration directory; follow the instructions the script provides, as all future steps will be done inside your new local configuration directory. + +0. Create an initial `hosts` inventory, using `hosts.default` in the `pvc-ansible` repo as a template. You can manage multiple PVC clusters ("sites") from the Ansible repository easily, however for simplicity you can use the simple name `cluster` for your initial site. Define the 3 hostnames you will use under the site group; usually the provided names of `pvchv1`, `pvchv2`, and `pvchv3` are sufficient, though you may use any hostname pattern you wish. It is *very important* that the names all contain a sequential number, however, as this is used by various components. + +0. Create an initial set of `group_vars` for your cluster at `group_vars/`, using the `group_vars/default` in the `pvc-ansible` repo as a template. Inside these group vars are two main files: `base.yml` and `pvc.yml`. These example files are well-documented; read them carefully and specify all required options before proceeding, and reference the [Ansible setup examples](https://github.com/parallelvirtualcluster/pvc-ansible) for more detailed descriptions of the options. + + * `base.yml` configures the `base` role and some common per-cluster configurations such as an upstream domain, a root password, a set of administrative users, various hardware configuration items, as well as and most importantly, the basic network configuration of the nodes. Make special note of the various items that must be generated such as passwords; these should all be cluster-unique. + + * `pvc.yml` configures the `pvc` role, including all the dependent software and PVC itself. Important to note is the `pvc_nodes` list, which contains a list of all the nodes as well as per-node configurations for each. All nodes must be a part of this list. + +0. In the `pvc-installer` directory, run the `buildiso.sh` script to generate an installer ISO. This script requires `debootstrap`, `isolinux`, and `xorriso` to function. The resulting file will, by default, be named `pvc-installer_.iso` in the current directory. For additional options, use the `-h` flag to show help information for the script. + +### Part Two - Preparing and installing the physical hosts + +0. Prepare 3 physical servers with IPMI. The servers should match the specifications and requirements outlined in the [Cluster Architecture documentation](/cluster-architecture). Connect their networking based on the configuration set in the `base.yml` group vars file for your cluster. + +0. Load the installer ISO generated in step 6 of the previous section onto a USB stick, or using IPMI virtual media, on the physical servers. + +0. Boot the physical servers off of the installer ISO. Use UEFI mode - if available - for maximum flexibility and longevity. + +0. Follow the prompts from the installer ISO. It will ask for a hostname, the system disk device to use, the initial network interface to configure as well as vLANs and either DHCP or static IP information, and finally either an HTTP URL containing an SSH `authorized_keys` to use for the `deploy` user, or a password for this user if key auth is unavailable. + +0. Wait for the installer to complete. This may take several minutes. + +0. At the end of the install process, follow the prompts carefully; it is usually prudent to pre-see the `/etc/network/interfaces` configuration based on your expected final physical network config (e.g. set up bonding, etc.) before proceeding, especially if you use DHCP, as the bonding configuration applied later could affect the address. The `chroot` is likely unneeded unless you have good reason to edit the system in this way. + +0. Make note of the (temporary and insecure!) root password set by the installer; you may need it to troubleshoot the system if it does not come up properly. This will be overwritten later in the setup process. + +0. Press "Enter" to reboot the system and confirm it is reachable. + +0. Repeat the above steps for all 3 initial nodes. On boot, they will display their configured IP address to be used in the next steps. + +### Part Three - Initial bootstrap with Ansible + +0. Make note of the IP addresses of all 3 initial nodes, and configure DNS, `/etc/hosts`, or Ansible `ansible_host=` hostvars to map these IP addresses to the hostnames set in the Ansible `hosts` and `group_vars` files. + +0. Verify connectivity from your administrative host to the 3 initial nodes, including SSH access as the `deploy` user. Accept their host keys as required before proceeding as Ansible does not like those prompts. If you did not configure SSH key auth during the PVC installer process, configure it now, as it greatly simplifies Ansible configuration. + +0. Verify your `group_vars` setup from part 1, as errors here may require a re-installation and restart of the bootstrap process. + +0. Perform the initial bootstrap. From your local configuration repository directory, execute the following `ansible-playbook` command, replacing `` with the Ansible group name from the `hosts` file. Make special note of the additional `bootstrap=yes` variable, which tells the playbook that this is an initial bootstrap run. + `$ ansible-playbook -v -i hosts pvc.yml -l -e bootstrap=yes` + + **WARNING:** Never run this playbook with the `-e bootstrap=yes` option against an active, already-bootstrapped cluster. This will have **disastrous consequences** including the **loss of all data** in the Ceph system as well as any configured networks, VMs, etc. + +0. Wait for the Ansible playbook run to finish. Once completed, the cluster bootstrap will be finished, and all 3 nodes will have rebooted into a working PVC cluster. If any errors occur, carefully evaluate them and re-run the playbook (with `-o bootstrap=yes` - your cluster is not active yet!) as required. + +0. Download and install the CLI client package (`pvc-client-cli.deb`) on your administrative host, and add and verify connectivity to the cluster; this will also verify that the API is working. You will need to know the cluster upstream floating IP address you configured in the `networks` section of the `base.yml` playbook, and if you configured SSL or authentication for the API in your `group_vars`, adjust the first command as needed (see `pvc cluster add -h` for details). A human-readable description can also be specified, which is useful if you manage multiple clusters and their names become unweildy. + `$ pvc cluster add -a -d "My first PVC cluster" mycluster` + `$ pvc -c mycluster node list` + + You can also set a default cluster by exporting the `PVC_CLUSTER` environment variable to avoid requiring `-c cluster` with every subsequent command: + `$ export PVC_CLUSTER="mycluster"` + + **Note:** It is fully possible to administer the cluster from the nodes themselves via SSH should you so choose, to avoid requiring the PVC client on your local machine. + +### Part Four - Configuring the Ceph storage cluster + +0. Determine the Ceph OSD block devices on each host via an `ssh` shell. For instance, use `lsblk` or check `/dev/disk/by-path` to show the block devices by their physical SAS/SATA bus location, and obtain the relevant `/dev/sdX` name for each disk you wish to be a Ceph OSD on each host. + +0. Cofigure an OSD device for each data disk in each host. The general command is: + `$ pvc storage osd add --weight ` + + For example, if each node has two data disks, as `/dev/sdb` and `/dev/sdc`, run the commands as follows to add the first disk to each node, then the second disk to each node: + `$ pvc storage osd add --weight 1.0 pvchv1 /dev/sdb` + `$ pvc storage osd add --weight 1.0 pvchv2 /dev/sdb` + `$ pvc storage osd add --weight 1.0 pvchv3 /dev/sdb` + `$ pvc storage osd add --weight 1.0 pvchv1 /dev/sdc` + `$ pvc storage osd add --weight 1.0 pvchv2 /dev/sdc` + `$ pvc storage osd add --weight 1.0 pvchv3 /dev/sdc` + + **NOTE:** On the CLI, the `--weight` argument is optional, and defaults to `1.0`. In the API, it must be specified explicitly, but the CLI sets a default value. OSD weights determine the relative amount of data which can fit onto each OSD. Under normal circumstances, you would want all OSDs to be of identical size, and hence all should have the same weight. If your OSDs are instead different sizes, the weight should be proportional to the size, e.g. `1.0` for a 100GB disk, `2.0` for a 200GB disk, etc. For more details, see the [Cluster Architecture](/cluster-architecture) and Ceph documentation. + + **NOTE:** OSD commands wait for the action to complete on the node, and can take some time (up to 30 seconds). + + **NOTE:** You can add OSDs in any order you wish, for instance you can add the first OSD to each node and then add the second to each node, or you can add all nodes' OSDs together at once like the example. This ordering does not affect the cluster in any way. + +0. Verify that the OSDs were added and are functional (`up` and `in`): + `$ pvc storage osd list` + +0. Create an RBD pool to store VM images on. The general command is: + `$ pvc storage pool add ` + + **NOTE:** Ceph placement groups are a complex topic; as a general rule it's easier to grow than shrink, so start small and grow as your cluster grows. The following are some good starting numbers for 3-node clusters, though the Ceph documentation and the [Ceph placement group calculator](https://ceph.com/pgcalc/) are advisable for anything more complex. There is a trade-off between CPU usage and the number of total PGs for all pools in the cluster, with more PGs meaning more CPU usage. + + * 3 OSDs total: 128 PGs (1 pool) or 64 PGs (2 or more pools, each) + * 6 OSDs total: 256 PGs (1 pool) or 128 PGs (2 or more pools, each) + * 9+ OSDs total: 256 PGs + + For example, to create a pool named `vms` with 256 placement groups, run the command as follows: + `$ pvc storage pool add vms 256` + + **NOTE:** As detailed in the [cluster architecture documentation](/cluster-architecture), you can also set a custom replica configuration for each pool if the default of 3 replica copies with 2 minimum copies is not acceptable. See `pvc storage pool add -h` or that document for full details. + +0. Verify that the pool was added: + `$ pvc storage pool list` + +### Part Five - Creating virtual networks + +0. Determine a domain name and IPv4, and/or IPv6 network for your first client network, and any other client networks you may wish to create. These networks must not overlap with the cluster networks. For full details on the client network types, see the [cluster architecture documentation](/cluster-architecture). + +0. Create the virtual network. There are many options here, so see `pvc network add -h` for details. + + For example, to create the managed (EVPN VXLAN) network `100` with subnet `10.100.0.0/24`, gateway `.1` and DHCP from `.100` to `.199`, run the command as follows: + `$ pvc network add 100 --type managed --description my-managed-network --domain myhosts.local --ipnet 10.100.0.0/24 --gateway 10.100.0.1 --dhcp --dhcp-start 10.100.0.100 --dhcp-end 10.100.0.199` + + For another example, to create the static bridged (switch-configured, tagged VLAN, with no PVC management of IPs) network `200`, run the command as follows: + `$ pvc network add 200 --type bridged --description my-bridged-network` + + **NOTE:** Network descriptions cannot contain spaces or special characters; keep them short, sweet, and dash or underscore delimited. + +0. Verify that the network(s) were added: + `$ pvc network list` + +0. On the upstream router, configure one of: + + a) A BGP neighbour relationship with the cluster upstream floating address to automatically learn routes. + + b) Static routes for the configured client IP networks towards the cluster upstream floating address. + +0. On the upstream router, if required, configure NAT for the configured client IP networks. + +0. Verify the client networks are reachable by pinging the managed gateway from outside the cluster. + + +### You're Done! + +0. Set all 3 nodes to `ready` state, allowing them to run virtual machines. The general command is: + `$ pvc node ready ` + +Congratulations, you now have a basic PVC storage cluster, ready to run your VMs. + +For next steps, see the [Provisioner manual](/manuals/provisioner) for details on how to use the PVC provisioner to create new Virtual Machines, as well as the [CLI manual](/manuals/cli) and [API manual](/manuals/api) for details on day-to-day usage of PVC. diff --git a/docs/getting-started.md.bak b/docs/getting-started.md.bak new file mode 100644 index 0000000..135e1c0 --- /dev/null +++ b/docs/getting-started.md.bak @@ -0,0 +1,145 @@ +# Getting started - deploying a Parallel Virtual Cluster + +PVC aims to be easy to deploy, letting you get on with managing your cluster in just a few hours at most. Once initial setup is complete, the cluster is managed via the clients, though the Ansible framework is used to add, remove, or modify nodes as required. + +This guide will walk you through setting up a simple 3-node PVC cluster from scratch, ending with a fully-usable cluster ready to provision virtual machines. Note that all domains, IP addresses, etc. used are examples - when following this guide, be sure to modify the commands and configurations to suit your needs. + +### Part One - Preparing for bootstrap + +0. Read through the [Cluster Architecture documentation](/cluster-architecture). This documentation details the requirements and conventions of a PVC cluster, and is important to understand before proceeding. + +0. Download the latest copy of the [`pvc-ansible`](https://github.com/parallelvirtualcluster/pvc-ansible) repository to your local machine. + +0. Leverage the `create-local-repo.sh` script in the `pvc-ansible` directory to set up a local cluster configuration directory; follow the instructions the script provides, as all future steps will be done inside your new local configuration directory. + +0. Create an initial `hosts` inventory, using `hosts.default` in the `pvc-ansible` repo as a template. You can manage multiple PVC clusters ("sites") from the Ansible repository easily, however for simplicity you can use the simple name `cluster` for your initial site. Define the 3 hostnames you will use under the site group; usually the provided names of `pvchv1`, `pvchv2`, and `pvchv3` are sufficient, though you may use any hostname pattern you wish. It is *very important* that the names all contain a sequential number, however, as this is used by various components. + +0. Create an initial set of `group_vars` for your cluster at `group_vars/`, using the `group_vars/default` in the `pvc-ansible` repo as a template. Inside these group vars are two main files: `base.yml` and `pvc.yml`. These example files are well-documented; read them carefully and specify all required options before proceeding, and reference the [Ansible setup examples](https://github.com/parallelvirtualcluster/pvc-ansible) for more detailed descriptions of the options. + + * `base.yml` configures the `base` role and some common per-cluster configurations such as an upstream domain, a root password, a set of administrative users, various hardware configuration items, as well as and most importantly, the basic network configuration of the nodes. Make special note of the various items that must be generated such as passwords; these should all be cluster-unique. + + * `pvc.yml` configures the `pvc` role, including all the dependent software and PVC itself. Important to note is the `pvc_nodes` list, which contains a list of all the nodes as well as per-node configurations for each. All nodes must be a part of this list. + +0. In the `pvc-installer` directory, run the `buildiso.sh` script to generate an installer ISO. This script requires `debootstrap`, `isolinux`, and `xorriso` to function. The resulting file will, by default, be named `pvc-installer_.iso` in the current directory. For additional options, use the `-h` flag to show help information for the script. + +### Part Two - Preparing and installing the physical hosts + +0. Prepare 3 physical servers with IPMI. The servers should match the specifications and requirements outlined in the [Cluster Architecture documentation](/cluster-architecture). Connect their networking based on the configuration set in the `base.yml` group vars file for your cluster. + +0. Load the installer ISO generated in step 6 of the previous section onto a USB stick, or using IPMI virtual media, on the physical servers. + +0. Boot the physical servers off of the installer ISO. Use UEFI mode - if available - for maximum flexibility and longevity. + +0. Follow the prompts from the installer ISO. It will ask for a hostname, the system disk device to use, the initial network interface to configure as well as vLANs and either DHCP or static IP information, and finally either an HTTP URL containing an SSH `authorized_keys` to use for the `deploy` user, or a password for this user if key auth is unavailable. + +0. Wait for the installer to complete. This may take several minutes. + +0. At the end of the install process, follow the prompts carefully; it is usually prudent to pre-see the `/etc/network/interfaces` configuration based on your expected final physical network config (e.g. set up bonding, etc.) before proceeding, especially if you use DHCP, as the bonding configuration applied later could affect the address. The `chroot` is likely unneeded unless you have good reason to edit the system in this way. + +0. Make note of the (temporary and insecure!) root password set by the installer; you may need it to troubleshoot the system if it does not come up properly. This will be overwritten later in the setup process. + +0. Press "Enter" to reboot the system and confirm it is reachable. + +0. Repeat the above steps for all 3 initial nodes. On boot, they will display their configured IP address to be used in the next steps. + +### Part Three - Initial bootstrap with Ansible + +0. Make note of the IP addresses of all 3 initial nodes, and configure DNS, `/etc/hosts`, or Ansible `ansible_host=` hostvars to map these IP addresses to the hostnames set in the Ansible `hosts` and `group_vars` files. + +0. Verify connectivity from your administrative host to the 3 initial nodes, including SSH access as the `deploy` user. Accept their host keys as required before proceeding as Ansible does not like those prompts. If you did not configure SSH key auth during the PVC installer process, configure it now, as it greatly simplifies Ansible configuration. + +0. Verify your `group_vars` setup from part 1, as errors here may require a re-installation and restart of the bootstrap process. + +0. Perform the initial bootstrap. From your local configuration repository directory, execute the following `ansible-playbook` command, replacing `` with the Ansible group name from the `hosts` file. Make special note of the additional `bootstrap=yes` variable, which tells the playbook that this is an initial bootstrap run. + `$ ansible-playbook -v -i hosts pvc.yml -l -e bootstrap=yes` + + **WARNING:** Never run this playbook with the `-e bootstrap=yes` option against an active, already-bootstrapped cluster. This will have **disastrous consequences** including the **loss of all data** in the Ceph system as well as any configured networks, VMs, etc. + +0. Wait for the Ansible playbook run to finish. Once completed, the cluster bootstrap will be finished, and all 3 nodes will have rebooted into a working PVC cluster. If any errors occur, carefully evaluate them and re-run the playbook (with `-o bootstrap=yes` - your cluster is not active yet!) as required. + +0. Download and install the CLI client package (`pvc-client-cli.deb`) on your administrative host, and add and verify connectivity to the cluster; this will also verify that the API is working. You will need to know the cluster upstream floating IP address you configured in the `networks` section of the `base.yml` playbook, and if you configured SSL or authentication for the API in your `group_vars`, adjust the first command as needed (see `pvc cluster add -h` for details). A human-readable description can also be specified, which is useful if you manage multiple clusters and their names become unweildy. + `$ pvc cluster add -a -d "My first PVC cluster" mycluster` + `$ pvc -c mycluster node list` + + You can also set a default cluster by exporting the `PVC_CLUSTER` environment variable to avoid requiring `-c cluster` with every subsequent command: + `$ export PVC_CLUSTER="mycluster"` + + **Note:** It is fully possible to administer the cluster from the nodes themselves via SSH should you so choose, to avoid requiring the PVC client on your local machine. + +### Part Four - Configuring the Ceph storage cluster + +0. Determine the Ceph OSD block devices on each host via an `ssh` shell. For instance, use `lsblk` or check `/dev/disk/by-path` to show the block devices by their physical SAS/SATA bus location, and obtain the relevant `/dev/sdX` name for each disk you wish to be a Ceph OSD on each host. + +0. Cofigure an OSD device for each data disk in each host. The general command is: + `$ pvc storage osd add --weight ` + + For example, if each node has two data disks, as `/dev/sdb` and `/dev/sdc`, run the commands as follows to add the first disk to each node, then the second disk to each node: + `$ pvc storage osd add --weight 1.0 pvchv1 /dev/sdb` + `$ pvc storage osd add --weight 1.0 pvchv2 /dev/sdb` + `$ pvc storage osd add --weight 1.0 pvchv3 /dev/sdb` + `$ pvc storage osd add --weight 1.0 pvchv1 /dev/sdc` + `$ pvc storage osd add --weight 1.0 pvchv2 /dev/sdc` + `$ pvc storage osd add --weight 1.0 pvchv3 /dev/sdc` + + **NOTE:** On the CLI, the `--weight` argument is optional, and defaults to `1.0`. In the API, it must be specified explicitly, but the CLI sets a default value. OSD weights determine the relative amount of data which can fit onto each OSD. Under normal circumstances, you would want all OSDs to be of identical size, and hence all should have the same weight. If your OSDs are instead different sizes, the weight should be proportional to the size, e.g. `1.0` for a 100GB disk, `2.0` for a 200GB disk, etc. For more details, see the [Cluster Architecture](/cluster-architecture) and Ceph documentation. + + **NOTE:** OSD commands wait for the action to complete on the node, and can take some time (up to 30 seconds). + + **NOTE:** You can add OSDs in any order you wish, for instance you can add the first OSD to each node and then add the second to each node, or you can add all nodes' OSDs together at once like the example. This ordering does not affect the cluster in any way. + +0. Verify that the OSDs were added and are functional (`up` and `in`): + `$ pvc storage osd list` + +0. Create an RBD pool to store VM images on. The general command is: + `$ pvc storage pool add ` + + **NOTE:** Ceph placement groups are a complex topic; as a general rule it's easier to grow than shrink, so start small and grow as your cluster grows. The following are some good starting numbers for 3-node clusters, though the Ceph documentation and the [Ceph placement group calculator](https://ceph.com/pgcalc/) are advisable for anything more complex. There is a trade-off between CPU usage and the number of total PGs for all pools in the cluster, with more PGs meaning more CPU usage. + + * 3 OSDs total: 128 PGs (1 pool) or 64 PGs (2 or more pools, each) + * 6 OSDs total: 256 PGs (1 pool) or 128 PGs (2 or more pools, each) + * 9+ OSDs total: 256 PGs + + For example, to create a pool named `vms` with 256 placement groups, run the command as follows: + `$ pvc storage pool add vms 256` + + **NOTE:** As detailed in the [cluster architecture documentation](/cluster-architecture), you can also set a custom replica configuration for each pool if the default of 3 replica copies with 2 minimum copies is not acceptable. See `pvc storage pool add -h` or that document for full details. + +0. Verify that the pool was added: + `$ pvc storage pool list` + +### Part Five - Creating virtual networks + +0. Determine a domain name and IPv4, and/or IPv6 network for your first client network, and any other client networks you may wish to create. These networks must not overlap with the cluster networks. For full details on the client network types, see the [cluster architecture documentation](/cluster-architecture). + +0. Create the virtual network. There are many options here, so see `pvc network add -h` for details. + + For example, to create the managed (EVPN VXLAN) network `100` with subnet `10.100.0.0/24`, gateway `.1` and DHCP from `.100` to `.199`, run the command as follows: + `$ pvc network add 100 --type managed --description my-managed-network --domain myhosts.local --ipnet 10.100.0.0/24 --gateway 10.100.0.1 --dhcp --dhcp-start 10.100.0.100 --dhcp-end 10.100.0.199` + + For another example, to create the static bridged (switch-configured, tagged VLAN, with no PVC management of IPs) network `200`, run the command as follows: + `$ pvc network add 200 --type bridged --description my-bridged-network` + + **NOTE:** Network descriptions cannot contain spaces or special characters; keep them short, sweet, and dash or underscore delimited. + +0. Verify that the network(s) were added: + `$ pvc network list` + +0. On the upstream router, configure one of: + + a) A BGP neighbour relationship with the cluster upstream floating address to automatically learn routes. + + b) Static routes for the configured client IP networks towards the cluster upstream floating address. + +0. On the upstream router, if required, configure NAT for the configured client IP networks. + +0. Verify the client networks are reachable by pinging the managed gateway from outside the cluster. + + +### You're Done! + +0. Set all 3 nodes to `ready` state, allowing them to run virtual machines. The general command is: + `$ pvc node ready ` + +Congratulations, you now have a basic PVC storage cluster, ready to run your VMs. + +For next steps, see the [Provisioner manual](/manuals/provisioner) for details on how to use the PVC provisioner to create new Virtual Machines, as well as the [CLI manual](/manuals/cli) and [API manual](/manuals/api) for details on day-to-day usage of PVC. diff --git a/docs/images/georedundancy-caveats.png b/docs/images/georedundancy-caveats.png new file mode 100644 index 0000000..937a11a Binary files /dev/null and b/docs/images/georedundancy-caveats.png differ diff --git a/docs/images/pvc-3-node-cluster.png b/docs/images/pvc-3-node-cluster.png new file mode 100644 index 0000000..ed82393 Binary files /dev/null and b/docs/images/pvc-3-node-cluster.png differ diff --git a/docs/images/pvc-8-node-cluster.png b/docs/images/pvc-8-node-cluster.png new file mode 100644 index 0000000..3198c05 Binary files /dev/null and b/docs/images/pvc-8-node-cluster.png differ diff --git a/docs/images/pvc-migration.png b/docs/images/pvc-migration.png new file mode 100644 index 0000000..e9daf31 Binary files /dev/null and b/docs/images/pvc-migration.png differ diff --git a/docs/images/pvc-networks.png b/docs/images/pvc-networks.png new file mode 100644 index 0000000..afa7732 Binary files /dev/null and b/docs/images/pvc-networks.png differ diff --git a/docs/images/pvc-nodelog.png b/docs/images/pvc-nodelog.png new file mode 100644 index 0000000..a0eda9c Binary files /dev/null and b/docs/images/pvc-nodelog.png differ diff --git a/docs/images/pvc-nodes.png b/docs/images/pvc-nodes.png new file mode 100644 index 0000000..1a6bf8e Binary files /dev/null and b/docs/images/pvc-nodes.png differ diff --git a/docs/images/pvc_icon.png b/docs/images/pvc_icon.png new file mode 100644 index 0000000..a4544d9 Binary files /dev/null and b/docs/images/pvc_icon.png differ diff --git a/docs/images/pvc_logo_black.png b/docs/images/pvc_logo_black.png new file mode 100644 index 0000000..507a736 Binary files /dev/null and b/docs/images/pvc_logo_black.png differ diff --git a/docs/images/pvc_logo_black.xcf b/docs/images/pvc_logo_black.xcf new file mode 100644 index 0000000..d6aaeb7 Binary files /dev/null and b/docs/images/pvc_logo_black.xcf differ diff --git a/docs/images/pvc_logo_white.png b/docs/images/pvc_logo_white.png new file mode 100644 index 0000000..44467dc Binary files /dev/null and b/docs/images/pvc_logo_white.png differ diff --git a/docs/images/pvc_logo_white.xcf b/docs/images/pvc_logo_white.xcf new file mode 100644 index 0000000..d1240df Binary files /dev/null and b/docs/images/pvc_logo_white.xcf differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..fe9210d --- /dev/null +++ b/docs/index.md @@ -0,0 +1,58 @@ +

+Logo banner +

+License +Release +Documentation Status +

+ +## What is PVC? + +PVC is a Linux KVM-based hyperconverged infrastructure (HCI) virtualization cluster solution that is fully Free Software, scalable, redundant, self-healing, self-managing, and designed for administrator simplicity. It is an alternative to other HCI solutions such as Ganeti, Harvester, Nutanix, and VMWare, as well as to other common virtualization stacks such as ProxMox and OpenStack. + +PVC is a complete HCI solution, built from well-known and well-trusted Free Software tools, to assist an administrator in creating and managing a cluster of servers to run virtual machines, as well as self-managing several important aspects including storage failover, node failure and recovery, virtual machine failure and recovery, and network plumbing. It is designed to act consistently, reliably, and unobtrusively, letting the administrator concentrate on more important things. + +PVC is highly scalable. From a minimum (production) node count of 3, up to 12 or more, and supporting many dozens of VMs, PVC scales along with your workload and requirements. Deploy a cluster once and grow it as your needs expand. + +As a consequence of its features, PVC makes administrating very high-uptime VMs extremely easy, featuring VM live migration, built-in always-enabled shared storage with transparent multi-node replication, and consistent network plumbing throughout the cluster. Nodes can also be seamlessly removed from or added to service, with zero VM downtime, to facilitate maintenance, upgrades, or other work. + +PVC also features an optional, fully customizable VM provisioning framework, designed to automate and simplify VM deployments using custom provisioning profiles, scripts, and CloudInit userdata API support. + +Installation of PVC is accomplished by two main components: a [Node installer ISO](https://github.com/parallelvirtualcluster/pvc-installer) which creates on-demand installer ISOs, and an [Ansible role framework](https://github.com/parallelvirtualcluster/pvc-ansible) to configure, bootstrap, and administrate the nodes. Installation can also be fully automated with a companion [cluster bootstrapping system](https://github.com/parallelvirtualcluster/pvc-bootstrap). Once up, the cluster is managed via an HTTP REST API, accessible via a Python Click CLI client or WebUI. + +Just give it physical servers, and it will run your VMs without you having to think about it, all in just an hour or two of setup time. + + +## What is it based on? + +The core node and API daemons, as well as the CLI API client, are written in Python 3 and are fully Free Software (GNU GPL v3). In addition to these, PVC makes use of the following software tools to provide a holistic hyperconverged infrastructure solution: + + * Debian GNU/Linux as the base OS. + * Linux KVM, QEMU, and Libvirt for VM management. + * Linux `ip`, FRRouting, NFTables, DNSMasq, and PowerDNS for network management. + * Ceph for storage management. + * Apache Zookeeper for the primary cluster state database. + * Patroni PostgreSQL manager for the secondary relation databases (DNS aggregation, Provisioner configuration). + + +## Getting Started + +To get started with PVC, please see the [About](https://parallelvirtualcluster.readthedocs.io/en/latest/about/) page for general information about the project, and the [Getting Started](https://parallelvirtualcluster.readthedocs.io/en/latest/getting-started/) page for details on configuring your first cluster. + + +## Changelog + +View the changelog in [CHANGELOG.md](https://github.com/parallelvirtualcluster/pvc/blob/master/CHANGELOG.md). + + +## Screenshots + +While PVC's API and internals aren't very screenshot-worthy, here is some example output of the CLI tool. + +

Node listing
Listing the nodes in a cluster

+ +

Network listing
Listing the networks in a cluster, showing 3 bridged and 1 IPv4-only managed networks

+ +

VM listing and migration
Listing a limited set of VMs and migrating one with status updates

+ +

Node logs
Viewing the logs of a node (keepalives and VM [un]migration)

diff --git a/docs/manuals/api-reference.html b/docs/manuals/api-reference.html new file mode 100644 index 0000000..7b7b7eb --- /dev/null +++ b/docs/manuals/api-reference.html @@ -0,0 +1,13 @@ + + + + PVC Client API Documentation + + + + + + + + + diff --git a/docs/manuals/api.md b/docs/manuals/api.md new file mode 100644 index 0000000..7f4087e --- /dev/null +++ b/docs/manuals/api.md @@ -0,0 +1,355 @@ +# PVC API architecture + +The PVC API is a standalone client application for PVC. It interfaces directly with the Zookeeper database to manage state. + +The API is built using Flask and is packaged in the Debian package `pvc-client-api`. The API depends on the common client functions of the `pvc-client-common` package as does the CLI client. + +Details of the API interface can be found in [the manual](/manuals/api). + +# PVC HTTP API manual + +The PVC HTTP API client is built with Flask, a Python framework for creating API interfaces, and run directly with the PyWSGI framework. It interfaces directly with the Zookeeper cluster to send and receive information about the cluster. It supports authentication configured statically via tokens in the configuration file as well as SSL. It also includes the provisioner client, an optional section that can be used to create VMs automatically using a set of templates and standardized scripts. + +The [`pvc-ansible`](https://github.com/parallelvirtualcluster/pvc-ansible) framework will install and configure the API by default, and enable the node daemon option for an instance of the API to follow the primary node, thus ensuring the API is listening on the upstream floating IP at all times. + +## API Details + +### SSL + +The API accepts SSL certificate and key files via the `pvcapid.yaml` configuration to enable SSL support for the API, which protects the data and query values from snooping or tampering. SSL is strongly recommended if using the API outside of a trusted local area network. + +### API authentication + +Authentication for the API is available using a static list of tokens. These tokens can be any long string, but UUIDs are typical and simple to use. Within `pvc-ansible`, the list of tokens can be specified in the `pvc.yaml` `group_vars` file. Usually, you'd want one token for each user of the API, such as a WebUI, a 3rd-party client, or an administrative user. Within the configuration, each token can have a description; this is mostly for administrative clarity and is not actually used within the API itself. + +The API provides session-based login using the `/api/v1/auth/login` and `/api/v1/auth/logout` options. If authentication is not enabled, these endpoints return a temporary redirect to the root (version) endpoint. + +For one-time authentication, the `token` value can be specified to any API endpoint via the `X-Api-Key` header value. This is only checked if there is no valid session already established. If authentication is enabled, there is no valid session, and no `token` value is specified, the API will return a JSON `message` of `Authentication required` and HTTP code 401. + +### Data formats + +The PVC API consistently accepts HTTP POST commands of HTML form documents. + +The PCI API consistently returns JSON bodies as its responses. For (most) POST endpoints and any failures (400, 401, 404, etc.), this body contains a "message" field with a text message indicating the result. For (most) GET endpoints, this body is a JSON representation of the data being provided, subject to the schema outlined in the API endpoint documentation. + +## Provisioner + +The provisioner subsection (`/api/v1/provisioner`) is used to create new virtual machines on a PVC cluster. By creating templates and scripts, then grouping these into profiles, VMs can be created based on dynamic, declarative configurations via direct installation or templating. Administrators can use this facility to automate the creation of VMs running most UNIX-like operating systems that can be installed in a parent host. It can also create VMs based on existing templates or ISO images to facilitate installing alternate operating systems such as Microsoft Windows. + +### Templates + +Templates are used to configure the four components that define a VM configuration. Templates can be created and managed via the API, then grouped into profiles. + +#### System Templates + +System templates define the basic configuration of a VM. This includes the number of vCPUs and amount vRAM, as well as console access (either VNC or serial) and several pieces of PVC metadata. + +Generally, a system template is usable across multiple VM profiles, so there will generally be a small number of system templates defining several standard resource profiles that can then be reused. + +Some elements of the system template are mandatory, but most are optional. + +###### Example: Creating a system template + +* Note: vRAM sizes are always specified in MB. + +``` +curl -X POST http://localhost:7370/api/v1/provisioner/template/system?name=2cpu-1gb-serial\&vcpus=2\&vram=1024\&serial=true\&vnc=false\&node_limit='pvchv1,pvchv2'\&node_selector=mem\&start_with_node=false +curl -X GET http://localhost:7370/api/v1/provisioner/template/system/2cpu-1gb-serial +``` + +#### Network Templates + +Network templates define the network configuration of a VM. These are tied into the PVC networking facility, and are quite simple. A MAC template is assigned to each template, which defines how MAC addresses are generated (either randomly, or via a simple templating system for static MAC addresses). + +With a network template, various "nets" can be configured. A "net" defines a PVC virtual network VNI, which must be valid on the PVC cluster. The first net is assigned to the first Ethernet device (usually eth0 or ens2 in Linux), with each subsequent network being added as an additional interface in order. + +###### Example: Creating a network template with two networks + +``` +curl -X POST http://localhost:7370/api/v1/provisioner/template/network?name=net200+net300 +curl -X POST http://localhost:7370/api/v1/provisioner/template/network/net200+net300/net?vni=200 +curl -X POST http://localhost:7370/api/v1/provisioner/template/network/net200+net300/net/300 +curl -X GET http://localhost:7370/api/v1/provisioner/template/net200+net300 +``` + +#### Storage Templates + +Storage templates define the Ceph RBD disks, as well as optional filesystems and mountpoints for Linux-based guests, of a VM. The template itself consists only of a name; disk or image entries are configured as additional elements similar to network templates. + +Each disk in a storage template is identified by a sequential ID, usually "sda"/"vda", "sdb"/"vdb", etc., a size, and a Ceph RBD pool within the PVC cluster. These alone are all that are required, and will create raw, unformatted images of the specified size, on the specified pool, and attached to the VM at the ID value. In addition to these basics, filesystems (with argument support) and mountpoints can also be specified. Filesystems specified here will be used to format the volume during the provisioning process, and mountpoints will mount the volume at the specified mountpoint during provisioning, so that a guest operating system can be installed on them during the process with a provisioning script. + +In addition to disks, storage templates can also contain image entries. Like disk entries, they are identified by a sequential ID, as well as a source Ceph RBD pool and volume name. The specified volume may belong to a (shutdown) VM or be a dedicated template uploaded to the Ceph cluster. + +###### Example: Creating a storage template with three mounted disks + +* Note: You can also include the template name during creation. +* Note: Disk sizes are always specified in GB. +* Note: Filesystem arguments are passed as-is to the `mkfs` command and must use an `--opt=val` format to prevent splitting. + +``` +curl -X POST http://localhost:7370/api/v1/provisioner/template/storage/ext4-root-var-log +curl -X POST http://localhost:7370/api/v1/provisioner/template/storage/ext4-root-var-log/disk?disk_id=sda\&disk_size=4\&filesystem=ext4\&mountpoint=/\&pool=vms\&filesystem_arg='-L=root' +curl -X POST http://localhost:7370/api/v1/provisioner/template/storage/ext4-root-var-log/disk/sdb?disk_size=4\&filesystem=ext4\&mountpoint=/var\&pool=vms\&filesystem_arg='-L=var' +curl -X POST http://localhost:7370/api/v1/provisioner/template/storage/ext4-root-var-log/disk/sdc -d "disk_size=4\&filesystem=ext4\&mountpoint=/var/log\&pool=vms\&filesystem_arg='-L=log'\&filesystem_arg='-m=1'" +curl -X GET http://localhost:7370/api/v1/provisioner/template/storage/ext4-root-var-log +``` + +#### Userdata Templates + +Userdata templates contain cloud-init metadata that can be provided to VMs on their first boot. It is accessible via an EC2-compatible API running on the PVC cluster to VMs. A userdata template contains the full text of the userdata, including optional multi-part sections if desired. + +A default userdata template called "empty" is created by default, and this can be used for any profile which does not require cloud-init userdata, since a template must always be specified. + +Examples of userdata templates can be found in `/usr/share/pvc/provisioner/examples` when the API is installed. + +###### Example: Creating a userdata template from the `userdata.yaml` example file + +* Note: For the block text commands (userdata and scripts), using the HTTP POST body for the data is always better than a URL argument. + +``` +curl -X POST http://localhost:7370/api/v1/provisioner/template/userdata?name=example-userdata -d "data=$( cat /usr/share/pvc/provisioner/examples/userdata.yaml )" +curl -X GET http://localhost:7370/api/v1/provisioner/template/userdata?name=example-userdata +``` + +### Scripts + +Scripts automate the installation of VMs with Python. To make use of a script, at least one disk volume must be both formatted with a Linux-compatible filesyste, and have a mountpoint (very likely `/`) configured. The specified disk is then mounted in a temporary directory on the active coordinator, and the script run against it. This script can then do any task required to set up and configure the VM, such as installing a Debian or Ubuntu system with debootstrap, obtaining a chroot and configuring GRUB, or almost any other task that the administrator may wish. All scripts are written in Python 3, which is then integrated into the provisioner's worker during VM creation and executed at the appropriate point. + +Each script must contain a function called `install()` which accepts `**kwargs` and no other arguments. A number of default arguments are provided, including `vm_name`, the `temporary_directory`, and dictionaries of the `disks` and `networks`. Additional arguments can be specified in VM profiles to facilitate advanced configurations specific to particular VM types. + +Examples of scripts can be found in `/usr/share/pvc/provisioner/examples` when the API is installed. + +###### Example: Creating a script from the `debootstrap_script.py` example file + +* Note: For the block text commands (userdata and scripts), using the HTTP POST body for the data is always better than a URL argument. + +``` +curl -X POST http://localhost:7370/api/v1/provisioner/script/debootstrap-example -d "data=$( cat /usr/share/pvc/provisioner/examples/userdata.yaml )" +curl -X GET http://localhost:7370/api/v1/provisioner/script/debootstrap-example +``` + +### Profiles + +Profiles group together the four template types and scripts, as well as optional script arguments, into a named profile which can be assigned to VMs on creation. When creating a VM, templates and scripts themselves are not explicitly specified; rather a profile is specified which then maps to these other values. This allows maximum flexibility, allowing a VM profile to combine the various templates and scripts in an arbitrary way. One potential usecase is to create a profile for a particular VM role, for instance a webserver, which will have a specific system, disk, network, and userdata configuration; multiple VMs can then be created with this profile to ensure they all contain the same resources and configuration. + +###### Example: Creating a profile with the previously-created templates and some script arguments + +* Note: Script arguments are specified as `name=value` pairs after the `arg=` argument. + +``` +curl -X POST http://localhost:7370/api/v1/provisioner/profile/test-profile?system_template=2cpu-1gb-serial\&network_template=net200+net300\&disk_template=ext4-root-var-log\&userdata_template=example-userdata\&script=debootstrap-example\&arg=deb_release=buster\&arg=deb_mirror=http://deb.debian.org/debian\&arg=deb_packages=linux-image-amd64,grub-pc,cloud-init,python3-cffi-backend,wget +curl -X GET http://localhost:7370/api/v1/provisioner/profile/test-profile +``` + +### Creating VMs + +VMs are created by specifying a name and a profile value. The provisioner API will then collect the details of the profile, and trigger the Celery worker (`pvc-provisioner-worker.service`) to begin creating the VM. The administrator can, at any point, obtain the status of the process via the Task ID, which is returned in the JSON body of the creation command. Once completed, by default, the resulting VM will be defined and started on the cluster, ready to use. If the VM uses cloud-init, it will then hit the Metadata API on startup to obtain the details of the VM as well as the userdata specified in the profile. + +Additional options can also be specified at install time. Automatic definition of the VM and automatic startup of the VM can both be disabled via options to the creation command. The former is most useful when creating disk images from an installed set of VM disks, and the latter provides flexibility for the administrator to edit or review the final VM before starting it for the first time. + +###### Example: Creating a VM and viewing its status + +``` +curl -X POST http://localhost:7370/api/v1/provisioner/create?name=test1\&profile=test-profile +curl -X GET http://localhost:7370/api/v1/provisioner/status/ +``` + +## API Daemon Configuration + +The API is configured using a YAML configuration file which is passed in to the API process by the environment variable `PVC_CONFIG_FILE`. When running with the default package and SystemD unit, this file is located at `/etc/pvc/pvcapid.yaml`. + +### Conventions + +* Settings may be `required`, `optional`, or `ignored`. + +* Settings may `depends` on other settings. This indicates that, if one setting is enabled, the other setting is very likely `required` by that setting. + +### `pvcapid.yaml` + +Example configuration: + +``` +--- +pvc: + debug: True + coordinators: + - pvchv1 + - pvchv2 + - pvchv3 + api: + listen_address: "127.0.0.1" + listen_port: "7370" + authentication: + enabled: False + secret_key: "aSuperLong&SecurePasswordString" + tokens: + - description: "testing" + token: "" + ssl: + enabled: False + cert_file: "" + key_file: "" + provisioner: + database: + host: 10.100.0.252 + port: 5432 + name: pvcapi + user: pvcapi + pass: pvcapi + queue: + host: localhost + port: 6379 + path: /0 + ceph_cluster: + storage_hosts: + - pvchv1 + - pvchv2 + - pvchv3 + storage_domain: "s.bonilan.net" + ceph_monitor_port: 6789 + ceph_storage_secret_uuid: "c416032b-2ce9-457f-a5c2-18704a3485f4" +``` + +#### `debug` + +* *required* + +Whether to enable Debug mode or not. If enabled, the API will use the Flask debug runtime instead of the PyWSGI framework and will log additional output. Should not be enabled in production. + +#### `coordinators` + +* *required* + +A list of coordinator hosts, used to generate the Zookeeper connection string. + +#### `api` → `listen_address` + +* *required* + +The IP address for the API to listen on. Use `0.0.0.0` to specify "all interfaces". + +#### `api` → `listen_port` + +The port for the API to listen on. + +#### `api` → `authentication` → `enabled` + +* *required* + +Whether to enable API authentication or not. Should usually be enabled in production deployments, especially if the API is available on untrusted networks. + +#### `api` → `authentication` → `secret_key` + +* *optional* +* *requires* `authentication` → `enabled` + +The Flask authentication secret key used to salt session credentials. Should be a long (>32-character) random string generated with `pwgen` or a similar tool. + +#### `api` → `authentication` → `tokens` + +* *optional* +* *requires* `authentication` → `enabled` + +A list of API authentication tokens that can be passed via the `X-Api-Key` header to authorize access to the API. Each list element contains the following fields: + +##### `description` + +* *ignored* + +A text description of the token function or use. Not parsed by the API, but used for administrator reference in the configuration file. + +##### `token` + +* *required* + +The token itself, usually a UUID created with `uuidegen` or a similar tool. + +#### `api` → `ssl` → `enabled` + +* *required* + +Whether to enable SSL for the API or not. Should usually be enabled in production deployments, especially if the API is available on untrusted networks. + +#### `api` → `ssl` → `cert_file` + +The path to the SSL certificate file for the API to use. + +#### `api` → `ssl` → `key_file` + +The path to the SSL private key file for the API to use. + +##### `provisioner` → `database` → `host` + +* *required* + +The hostname of the PostgreSQL instance for the Provisioner database. Should always be `localhost` except in advanced deployment scenarios. + +##### `provisioner` → `database` → `port` + +* *required* + +The port of the PostgreSQL instance for the Provisioner database. Should always be `5432`. + +##### `provisioner` → `database` → `name` + +* *required* + +The database name for the Provisioner database. Should always be `pvcapi`. + +##### `provisioner` → `database` → `user` + +* *required* + +The username for the PVC API client to access the Provisioner database. + +##### `provisioner` → `database` → `pass` + +* *required* + +The password for the PVC API client to access the Provisioner database. + +#### `provisioner` → `queue` → `host` + +* *required* + +The hostname of the Redis instance for the Provisioner queue. Should always be `localhost` except in advanced deployment scenarios. + +#### `provisioner` → `queue` → `port` + +* *required* + +The port of the Redis innstance for the Provisioner queue. Should always be `6379`. + +#### `provisioner` → `queue` → `path` + +* *required* + +The Redis path for the Provisioner queue. Should always be `/0`. + +#### `provisioner` → `ceph_cluster` → `storage_hosts` + +* *required* + +A list of hosts which run the Ceph monitors for VM disks. Should usually be identical to the list of `coordinators` except in advanced deployments. + +#### `provisioner` → `ceph_cluster` → `storage_domain` + +* *required* + +The storage domain of the cluster, used with the `storage_hosts` entires to form FQDNs for the Ceph monitors. Should usually be identical to the cluster `storage_domain` except in advanced deployments. + +#### `provisioner` → `ceph_cluster` → `ceph_monitor_port` + +* *required* + +The Ceph monitor port. Should always be `6789`. + +#### `provisioner` → `ceph_cluster` → `ceph_storage_secret_uuid` + +* *required* + +The Libvirt storage secret UUID for the Ceph cluster. + +## API Endpoint Documentation + +The full API endpoint and schema documentation [can be found here](/manuals/api-reference.html). diff --git a/docs/manuals/cli.md b/docs/manuals/cli.md new file mode 100644 index 0000000..07fdb2d --- /dev/null +++ b/docs/manuals/cli.md @@ -0,0 +1,19 @@ +# PVC CLI architecture + +The PVC CLI is a standalone client application for PVC. It interfaces with the PVC API, via a configurable list of clusters with customizable hosts, ports, addresses, and authentication. + +The CLI is build using Click and is packaged in the Debian package `pvc-client-cli`. The CLI does not depend on any other PVC components and can be used independently on arbitrary systems. + +The CLI is self-documenting, however [the manual](/manuals/cli) details the required configuration. + +# PVC CLI client manual + +The PVC CLI client is built with Click, a Python framework for creating self-documenting CLI applications. It interfaces with the PVC API. + +Use the `-h` option at any level of the `pvc` CLI command to receive help about the available commands and options. + +Before using the CLI on a non-PVC node system, at least one cluster must be added using the `pvc cluster` subcommands. Running the CLI on hosts which also run the PVC API (via its configuration at `/etc/pvc/pvcapid.yaml`) uses the special `local` cluster, reading information from the API configuration, by default. + +## Configuration + +The CLI client requires no configuration file. The only optional external environment variable is `PVC_CLUSTER`, which can be used to specify a cluster to connect to. diff --git a/docs/manuals/daemon.md b/docs/manuals/daemon.md new file mode 100644 index 0000000..bd0f98b --- /dev/null +++ b/docs/manuals/daemon.md @@ -0,0 +1,510 @@ +# PVC Node Daemon architecture + +The PVC Node Daemon is the heart of the PVC system and runs on each node to manage the state of the node and its configured resources. The daemon connects directly to the Zookeeper cluster for coordination and state. + +The node daemon is build using Python 3.X and is packaged in the Debian package `pvc-daemon`. + +Configuration of the daemon is documented in [the manual](/manuals/daemon), however it is recommended to use the [Ansible configuration system](https://github.com/parallelvirtualcluster/pvc-ansible) to configure the PVC cluster for you from scratch. + +## Overall architecture + +The PVC daemon is object-oriented - each cluster resource is represented by an Object, which is then present on each node in the cluster. This allows state changes to be reflected across the entire cluster should their data change. + +During startup, the system scans the Zookeeper database and sets up the required objects. The database is then watched in real-time for additional changes to the database information. + +## Startup sequence + +The daemon startup sequence is documented below. The main daemon entry-point is `Daemon.py` inside the `pvcnoded` folder, which is called from the `pvcnoded.py` stub file. + +0. The configuration is read from `/etc/pvc/pvcnoded.yaml` and the configuration object set up. + +0. Any required filesystem directories, mostly dynamic directories, are created. + +0. The logger is set up. If file logging is enabled, this is the state when the first log messages are written. + +0. Host networking is configured based on the `pvcnoded.yaml` configuration file. In a normal cluster, this is the point where the node will become reachable on the network as all networking is handled by the PVC node daemon. + +0. Sysctl tweaks are applied to the host system, to enable routing/forwarding between nodes via the host. + +0. The node determines its coordinator state and starts the required daemons if applicable. In a normal cluster, this is the point where the dependent services such as Zookeeper, FRR, and Ceph become available. After this step, the daemon waits 5 seconds before proceeding to give these daemons a chance to start up. + +0. The daemon connects to the Zookeeper cluster and starts its listener. If the Zookeeper cluster is unavailable, it will wait some time before abandoning the attempt and starting again from step 1. + +0. Termination handling/cleanup is configured. + +0. The node checks if it is already present in the Zookeeper cluster; if not, it will add itself to the database. Initial static options are also updated in the database here. The daemon state transitions from `stop` to `init`. + +0. The node checks if Libvirt is accessible. + +0. The node starts up the NFT firewall if applicable and configures the base rule-set. + +0. The node ensures that `dnsmasq` is stopped (legacy check, might be safe to remove eventually). + +0. The node begins setting up the object representations of resources, in order: + + a. Node entries + + b. Network entries, creating client networks and starting them as required. + + c. Domain (VM) entries, starting up the VMs as required. + + d. Ceph storage entries (OSDs, Pools, Volumes, Snapshots). + +0. The node activates its keepalived timer and begins sending keepalive updates to the cluster. The daemon state transitions from `init` to `run` and the system has started fully. + +## Node health plugins + +The PVC node daemon includes a node health plugin system. These plugins are run during keepalives to check various aspects of node health and adjust the overall node and cluster health accordingly. For example, a plugin might check that all configured network interfaces are online and operating at their correct speed, or that all operating system packages are up-to-date. + +For the full details of the health and node health plugin system, see the [node health plugin manual](/manuals/health-plugins). + +## Configuration + +The Daemon is configured using a YAML configuration file which is passed in to the API process by the environment variable `PVCD_CONFIG_FILE`. When running with the default package and SystemD unit, this file is located at `/etc/pvc/pvcnoded.yaml`. + +For most deployments, the management of the configuration file is handled entirely by the [PVC Ansible framework](https://github.com/parallelvirtualcluster/pvc-ansible) and should not be modified directly. Many options from the Ansible framework map directly into the configuration options in this file. + +### Conventions + +* Settings may be `required`, `optional`, or `ignored`. + +* Settings may `depends` on other settings. This indicates that, if one setting is enabled, the other setting is very likely `required` by that setting. + +### `pvcnoded.yaml` + +Example configuration: + +``` +pvc: + node: pvchv1 + debug: False + functions: + enable_hypervisor: True + enable_networking: True + enable_storage: True + enable_api: True + cluster: + coordinators: + - pvchv1 + - pvchv2 + - pvchv3 + networks: + upstream: + domain: "mydomain.net" + network: "1.1.1.0/24" + floating_ip: "1.1.1.10/24" + gateway: "1.1.1.1" + cluster: + domain: "pvc.local" + network: "10.255.0.0/24" + floating_ip: "10.255.0.254/24" + storage: + domain: "pvc.storage" + network: "10.254.0.0/24" + floating_ip: "10.254.0.254/24" + coordinator: + dns: + database: + host: localhost + port: 5432 + name: pvcdns + user: pvcdns + pass: pvcdnsPassw0rd + metadata: + database: + host: localhost + port: 5432 + name: pvcapi + user: pvcapi + pass: pvcapiPassw0rd + system: + fencing: + intervals: + keepalive_interval: 5 + fence_intervals: 6 + suicide_intervals: 0 + actions: + successful_fence: migrate + failed_fence: None + ipmi: + host: pvchv1-lom + user: admin + pass: Passw0rd + migration: + target_selector: mem + configuration: + directories: + plugin_directory: "/usr/share/pvc/plugins" + dynamic_directory: "/run/pvc" + log_directory: "/var/log/pvc" + console_log_directory: "/var/log/libvirt" + logging: + file_logging: True + stdout_logging: True + log_colours: True + log_dates: True + log_keepalives: True + log_keepalive_cluster_details: True + log_keepalive_plugin_details: True + console_log_lines: 1000 + networking: + bridge_device: ens4 + bridge_mtu: 1500 + sriov_enable: True + sriov_device: + - phy: ens1f0 + mtu: 9000 + vfcount: 7 + upstream: + device: ens4 + mtu: 1500 + address: None + cluster: + device: ens4 + mtu: 1500 + address: by-id + storage: + device: ens4 + mtu: 1500 + address: by-id +``` + +#### `node` + +* *required* + +The (short) hostname of the node; host-specific. + +#### `debug` + +* *required* + +Whether to enable or disable debug mode. Debug mode enables additional logging of subtasks throughout the system. + +#### `functions` → `enable_hypervisor` + +* *required* + +Whether to enable the hypervisor functionality of the PVC Daemon or not. This should usually be enabled except in advanced deployment scenarios (such as a dedicated quorum-keeping micro-node or dedicated network routing node). + +#### `functions` → `enable_networking` + +* *required* + +Whether to enable the client network functionality of the PVC Daemon or not. This should usually be enabled except in deployment scenarios where networking is completely unmanaged by PVC. + +#### `functions` → `enable_storage` + +* *required* + +Whether to enable the virtual storage functionality of the PVC Daemon or not. This should usually be enabled except in advanced deployment scenarios featuring unmanaged external storage. + +#### `functions` → `enable_api` + +Whether to enable the PVC API client on the cluster floating IPs or not. + +#### `cluster` → `coordinators` + +* *required* + +A list of coordinator hosts, used to generate the Zookeeper connection string and determine if the current host is a coordinator or not +. + +#### `cluster` → `networks` + +* *optional* +* *requires* `functions` → `enable_networking` + +Contains a dictionary of networks and their configurations for the PVC cluster. Optional only if `enable_networking` is `False`. The three required network types/names are `upstream`, `cluster`, and `storage`. Each network type contains the following entries. + +##### `domain` + +* *required* + +The domain name for the network. Should be a valid domain name, or `None`. Specifically for the `upstream` network, this should match the domain portion of the node hostname. + +##### `network` + +The CIDR-formatted IPv4 address block for the network. + +##### `floating_ip` + +The CIDR-formatted IPv4 address for the floating IP within the network. This IP will belong exclusively to the `primary` coordinator node to provide a central entrypoint for functionality on the cluster. + +##### `gateway` + +The IPv4 address for the gateway of the network. Usually applicable only to the `upstream` network, as the other two are normally unrouted and local to the cluster. + +#### `coordinator` + +* *optional* +* *requires* `functions` → `enable_networking` + +Configuration for coordinator functions on the node. Optional only if `enable_networking` is `False`. Not optional on non-coordinator hosts, though unused. Contains the following sub-entries. + +##### `dns` → `database` → `host` + +* *required* + +The hostname of the PostgreSQL instance for the DNS aggregator database. Should always be `localhost` except in advanced deployment scenarios. + +##### `dns` → `database` → `port` + +* *required* + +The port of the PostgreSQL instance for the DNS aggregator database. Should always be `5432`. + +##### `dns` → `database` → `name` + +* *required* + +The database name for the DNS aggregator database. Should always be `pvcdns`. + +##### `dns` → `database` → `user` + +* *required* + +The username for the PVC node daemon to access the DNS aggregator database. + +##### `dns` → `database` → `pass` + +* *required* + +The password for the PVC node daemon to access the DNS aggregator database. + +##### `metadata` → `database` → `host` + +* *required* + +The hostname of the PostgreSQL instance for the Provisioner database. Should always be `localhost` except in advanced deployment scenarios. + +##### `metadata` → `database` → `port` + +* *required* + +The port of the PostgreSQL instance for the Provisioner database. Should always be `5432`. + +##### `metadata` → `database` → `name` + +* *required* + +The database name for the Provisioner database. Should always be `pvcapi`. + +##### `metadata` → `database` → `user` + +* *required* + +The username for the PVC node daemon to access the Provisioner database. + +##### `metadata` → `database` → `pass` + +* *required* + +The password for the PVC node daemon to access the Provisioner database. + +#### `system` → `intervals` → `keepalive_interval` + +* *required* + +The number of seconds between keepalive messages to the cluster. The default is 5 seconds; for slow cluster nodes, 10-30 seconds may be more appropriate however this will result in slower responses to changes in the cluster and less accurate/up-to-date information in the clients. + +#### `system` → `intervals` → `fence_intervals` + +* *required* + +The number of keepalive messages that can be missed before a node is considered dead and the fencing cycle triggered on it. The default is 6, or 30 seconds of inactivity with a 5 second `keepalive_interval`. Can be set to 0 to disable fencing as the timeout will never trigger. + +#### `system` → `intervals` → `suicide_intervals` + +* *required* + +The number of keepalive message that can be missed before a node considers itself dead and forcibly resets itself. Note that, due to the large number of reasons a node could become unresponsive, the suicide interval alone should not be relied upon. The default is 0, which disables this functionality. If set, should usually be equal to or less than `fence_intervals` for maximum safety. + +#### `system` → `fencing` → `actions` → `successful_fence` + +* *required* + +The action to take regarding VMs once a node is *successfully* fenced, i.e. the IPMI command to restart the node reports a success. Can be one of `migrate`, to migrate and start all failed VMs on other nodes and the default, or `None` to perform no action. + +#### `system` → `fencing` → `actions` → `failed_fence` + +* *required* + +The action to take regarding VMs once a node fencing *fails*, i.e. the IPMI command to restart the node reports a failure. Can be one of `None`, to perform no action and the default, or `migrate` to migrate and start all failed VMs on other nodes. + +**WARNING:** This functionality is potentially **dangerous** and can result in data loss or corruption in the VM disks; the post-fence migration process *explicitly clears RBD locks on the disk volumes*. It is designed only for specific and advanced use-cases, such as servers that do not reliably report IPMI responses or servers without IPMI (not recommended; see the [cluster architecture documentation](/architecture/cluster)). If this is set to `migrate`, the `suicide_intervals` **must** be set to provide at least some guarantee that the VMs on the node will actually be terminated before this condition triggers. The administrator should think very carefully about their setup and potential failure modes before enabling this option. + +#### `system` → `fencing` → `ipmi` → `host` + +* *required* + +The hostname or IP address of this node's IPMI interface. Must be reachable from the nodes. + +#### `system` → `fencing` → `ipmi` → `user` + +* *required* + +The username for the PVC node daemon to log in to the IPMI interface. Must have permission to reboot the host (command `ipmitool chassis power reset`). + +#### `system` → `fencing` → `ipmi` → `pass` + +* *required* + +The password for the PVC node daemon to log in to the IPMI interface. + +#### `system` → `migration` → `target_selector` + +* *required* + +The default selector algorithm to use when migrating VMs away from a node; individual VMs can override this default. + +Valid `target_selector` values are: + * `mem`: choose the node with the most (real) free memory + * `memprov`: choose the node with the least provisioned VM memory + * `vcpus`: choose the node with the least allocated VM vCPUs + * `load`: choose the node with the lowest current load average + * `vms`: choose the node with the least number of provisioned VMs + +For most clusters, `mem` should be sufficient, but others may be used based on the cluster workload and available resources. The following caveats should be considered: + * `mem` looks at the free memory of the node in general, ignoring the amount provisioned to VMs; if any VM's internal memory usage changes, this value would be affected. + * `memprov` looks at the provisioned memory, not the allocated memory; thus, stopped or disabled VMs are counted towards a node's memory for this selector, even though their memory is not actively in use. + * `load` looks at the system load of the node in general, ignoring load in any particular VMs; if any VM's CPU usage changes, this value would be affected. This might be preferable on clusters with some very CPU intensive VMs. + +#### `system` → `configuration` → `directories` → `plugin_directory` + +* *optional* + +The directory to load node health plugins from. Defaults to `/usr/share/pvc/plugins` if unset as per default packaging; should only be overridden by advanced users. + +#### `system` → `configuration` → `directories` → `dynamic_directory` + +* *required* + +The directory to store ephemeral configuration files. Usually `/run/pvc` or a similar temporary directory. + +#### `system` → `configuration` → `directories` → `log_directory` + +* *required* + +The directory to store log files for `file_logging`. Usually `/var/log/pvc` or a similar directory. Must be specified even if `file_logging` is `False`, though ignored. + +#### `system` → `configuration` → `directories` → `console_log_directory` + +* *required* + +The directory to store VM console logs. Usually `/var/log/libvirt` or a similar directory. + +#### `system` → `configuration` → `logging` → `file_logging` + +* *required* + +Whether to enable direct logging to a file in `log_directory` or not. + +#### `system` → `configuration` → `logging` → `stdout_logging` + +* *required* + +Whether to enable logging to stdout or not; captured by SystemD and JournalD by default. + +#### `system` → `configuration` → `logging` → `log_colours` + +* *required* + +Whether to log ANSI colour sequences in the log output or not. + +#### `system` → `configuration` → `logging` → `log_dates` + +* *required* + +Whether to log the current date and time in the log output or not. + +#### `system` → `configuration` → `logging` → `log_keepalives` + +* *required* + +Whether to log keepalive messages or not. + +#### `system` → `configuration` → `logging` → `log_keepalive_cluster_details` + +* *required* + +Whether to log node status information during keepalives or not. + +#### `system` → `configuration` → `logging` → `log_keepalive_plugin_details` + +* *required* + +Whether to log node health plugin status information during keepalives or not. + +#### `system` → `configuration` → `logging` → `console_log_lines` + +* *required* + +How many lines of VM console logs to keep in the Zookeeper database for each VM. + +#### `system` → `configuration` → `networking` → `bridge_device` + +* *optional* +* *requires* `functions` → `enable_networking` + +The network interface device used to create Bridged client network vLANs on. For most clusters, should match the underlying device of the various static networks (e.g. `ens4` or `bond0`), though may also use a separate network interface. + +#### `system` → `configuration` → `networking` → `bridge_mtu` + +* *optional* +* *requires* `functions` → `enable_networking` + +The network interface MTU for the Bridged client network device. This is the maximum MTU a bridged client network can use. + +#### `system` → `configuration` → `networking` → `sriov_enable` + +* *optional*, defaults to `False` +* *requires* `functions` → `enable_networking` + +Enables (or disables) SR-IOV functionality in PVC. If enabled, at least one `sriov_device` entry should be specified. + +#### `system` → `configuration` → `networking` → `sriov_device` + +* *optional* +* *requires* `functions` → `enable_networking` + +Contains a list of SR-IOV PF (physical function) devices and their basic configuration. Each element contains the following entries: + +##### `phy`: + +* *required* + +The raw Linux network device with SR-IOV PF functionality. + +##### `mtu` + +The MTU of the PF device, set on daemon startup. + +##### `vfcount` + +The number of VF devices to create on this PF. VF devices are then managed via PVC on a per-node basis. + +#### `system` → `configuration` → `networking` + +* *optional* +* *requires* `functions` → `enable_networking` + +Contains a dictionary of networks and their configurations on this node. Optional only if `enable_networking` is `False`. The three required network types/names are `upstream`, `cluster`, and `storage`. Each network type contains the following entries. + +##### `device` + +* *required* + +The raw Linux network device that the network exists on. + +##### `mtu` + +* *required* + +The MTU of the network device. + +##### `address` + +* *required* + +The IPv4 address of the interface. Can be one of: `None`, for no IP address; `by-id`, to automatically select an address in the relevant `networks` section via the host ID (e.g. node1 will get `.1`, node2 will get `.2`, etc.); or a static CIDR-formatted IP address. diff --git a/docs/manuals/health-plugins.md b/docs/manuals/health-plugins.md new file mode 100644 index 0000000..a4ee970 --- /dev/null +++ b/docs/manuals/health-plugins.md @@ -0,0 +1,210 @@ +# Node health plugins + +The PVC node daemon includes a node health plugin system. These plugins are run during keepalives to check various aspects of node health and adjust the overall node and cluster health accordingly. For example, a plugin might check that all configured network interfaces are online and operating at their correct speed, or that all operating system packages are up-to-date. + +## Configuration + +### Plugin Directory + +The PVC node configuration includes a configuration option at `system` → `configuration` → `directories` → `plugin_directory` to configure the location of health plugin files on the system. By default if unset, this directory is `/usr/share/pvc/plugins`. An administrator can override this directory if they wish, though custom plugins can be installed to this directory without problems, and thus it is not recommended that it be changed. + +### Plugin Logging + +Plugin output is logged by default during keepalive messages. This is controlled by the node configuration option at `system` → `configuration` → `logging` → `log_keepalive_plugin_details`. Regardless of this setting, the overall node health is logged at the end of the plugin run. + +### Disabling Node Plugins + +Node plugins cannot be disabled; at best, a suite of zero plugins can be specified by pointing the above plugin directory to an empty folder. This will effectively render the node at a permanent 100% health. Note however that overall cluster health will still be affected by cluster-wide events (e.g. nodes or VMs being stopped, OSDs going out, etc.). + +## Health Plugin Architecture + +### Node and Cluster Health + +A core concept leveraged by the PVC system is that of node and cluster health. Starting with PVC version 0.9.61, these two health statistics are represented as percentages, with 100% representing optimal health, 51-90% representing a "warning" degraded state, and 0-50% representing a "critical" degraded state. + +While a cluster is in maintenance mode (set via `pvc maintenance on` and unset via `pvc maintenance off`), the health values continue to aggregate, but the value is ignored for the purposes of "health" output, i.e. its output colour will not change, and the reference monitoring plugins (for CheckMK and Munin) will not trigger alerting. This allows the administrator to specify that abnormal conditions are OK for some amount of time without triggering upstream alerting. Additionally, while a node is not in `run` Daemon state, its health will be reported as `N/A`, which is treated as 100% but displayed as such to make clear that the node has not initialized and run its health check plugins (yet). + +The node health is affected primarily by health plugins as discussed in this manual. Any plugin that adjusts node health lowers the node's health by its `health_delta` value, as well as the cluster health by its `health_delta` value. For example, a plugin might have a `health_delta` in a current state of `10`, which reduces its own node's health value to 90%, and the overall cluster health value to 90%. + +In addition, cluster health is affected by several fixed states within the PVC system. These are: + +* A node in `flushed` Domain state lowers the cluster health by 10; a node in `stop` Daemon state lowers the cluster health by 50. + +* A VM in `stop` state lowers the cluster health by 10 (hint: use `disable` state to avoid this). + +* An OSD in `down` state lowers the cluster health by 10; an OSD in `out` state lowers the cluster health by 50. + +* Memory overprovisioning (total provisioned and running guest memory allocation exceeds the total N-1 cluster memory availability) lowers the cluster health by 50. + +* Each Ceph health check message lowers the cluster health by 10 for a `HEALTH_WARN` severity or by 50 for a `HEALTH_ERR` severity. For example, the `OSDMAP_FLAGS` check (reporting, e.g. `noout` state) reports as a `HEALTH_WARN` severity and will thus decrease the cluster health by 10; if an additional `PG_DEGRADED` check fires (also reporting as `HEALTH_WARN` severity), this will decrease the cluster health by a further 10, or 20 total for both. This cumulative effect ensures that multiple simultaneous Ceph issues escalate in severity. For a full list of possible Ceph health check messages, [please see the Ceph documentation](https://docs.ceph.com/en/nautilus/rados/operations/health-checks/). + +### Built-in Health Plugins + +PVC ships with several node health plugins installed and loaded by default, to ensure several common aspects of node operation are validated and checked. The following plugins are included: + +#### `disk` + +This plugin checks all SATA/SAS and NVMe block devices for SMART health, if available, and reports any errors. + +For SATA/SAS disks reporting standard ATA SMART attributes, a health delta of 10 is raised for each SMART error on each disk, based on the `when_failed` value being set to true. Note that due to this design, several disks with multiple errors can quickly escalate to a critical condition, quickly alerting the administrator of possible major faults. + +For NVMe disks, only 3 specific NVMe health information messages are checked: `critical_warning`, `media_errors`, and `percentage_used` at > 90. Each check can only be reported once per disk and each raises a health delta of 10. + +#### `dpkg` + +This plugin checks for Debian package updates, invalid package states (i.e. not `ii` state), and obsolete configuration files that require cleanup. It will raise a health delta of 1 for each type of inconsistency, for a maximum of 3. It will thus never, on its own, trigger a node or cluster to be in a warning or critical state, but will show the errors for administrator analysis, as an example of a more "configuration anomaly"-type plugin. + +#### `edac` + +This plugin checks the EDAC utility for messages about errors, primarily in the ECC memory subsystem. It will raise a health delta of 50 if any `Uncorrected` EDAC errors are detected, possibly indicating failing memory. + +#### `ipmi` + +This plugin checks whether the daemon can reach its own IPMI address and connect. If it cannot, it raises a health delta of 10. + +#### `lbvt` + +This plugin checks whether the daemon can connect to the local Libvirt daemon instance. If it cannot, it raises a health delta of 50. + +#### `load` + +This plugin checks the current 1-minute system load (as reported during keepalives) against the number of total CPU threads available on the node. If the load average is greater, i.e. the node is overloaded, it raises a health delta of 50. + +#### `nics` + +This plugin checks that all NICs underlying PVC networks and bridges are operating correctly, specifically that bond interfaces have at least 2 active slaves and that all physical NICs are operating at their maximum possible speed. It takes into account several possible options to determine this. + +* For each device defined (`bridge_dev`, `upstream_dev`, `cluster_dev`, and `storage_dev`), it determines the type of device. If it is a vLAN, it obtains the underlying device; otherwise, it uses the specified device. It then adds this device to a list of core NICs. Ideally, this list will contain either bonding interfaces or actual ethernet NICs. + +* For each core NIC, it checks its type. If it is a `bond` device, it checks the bonding state to ensure that at least 2 slave interfaces are up and operating. If there are not, it raises a health delta of 10. + +* For each core NIC, it checks its maximum possible speed as reported by `ethtool` as well as the current active speed. If the NIC is operating at less than its maximum possible speed, it raises a health delta of 10. + +Note that this check may pose problems in some deployment scenarios (e.g. running 25GbE NICs at 10GbE by design). Currently the plugin logic cannot handle this and manual modifications may be required. This is left to the administrator if applicable. + +#### `psql` + +This plugin checks whether the daemon can connect to the local PostgreSQL/Patroni daemon instance. If it cannot, it raises a health delta of 50. + +#### `zkpr` + +This plugin checks whether the daemon can connect to the local Zookeeper daemon instance. If it cannot, it raises a health delta of 50. + +### Custom Health Plugins + +In addition to the included health plugins, the plugin architecture allows administrators to write their own plugins as required to check specific node details that might not be checked by the default plugins. While the author has endeavoured to cover as many important aspects as possible with the default plugins, there is always the possibility that some other condition becomes important and thus the system is flexible to this need. That said, we would welcome pull requests of new plugins to future version of PVC should they be widely applicable. + +As a warning, health plugins are run in a `root` context by PVC. They must therefore be carefully vetted to avoid damaging the system. DO NOT run untrusted health plugins. + +To create a health plugin, first reference the existing health plugins and create a base template. + +Each health plugin consists of three main parts: + +* An import, which must at least include the `MonitoringPlugin` class from the `pvcnoded.objects.MonitoringInstance` library. You can also load additional imports here, or import them within the functions (which is recommended for namespace simplicity). + +``` +# This import is always required here, as MonitoringPlugin is used by the MonitoringPluginScript class +from pvcnoded.objects.MonitoringInstance import MonitoringPlugin +``` + + +* A `PLUGIN_NAME` variable which defines the name of the plugin. This must match the filename. Generally, a plugin name will be 4 characters, but this is purely a convention and not a requirement. + +``` +# A monitoring plugin script must always expose its nice name, which must be identical to the file name +PLUGIN_NAME = "nics" +``` + +* An instance of a `MonitoringPluginScript` class which extends the `MonitoringPlugin` class. + +``` +# The MonitoringPluginScript class must be named as such, and extend MonitoringPlugin. +class MonitoringPluginScript(MonitoringPlugin): + ... +``` + +Within the `MonitoringPluginScript` class must be 3 primary functions as detailed below. While it is possible to do nothing except `pass` in these functions, or even exclude them (the parent includes empty defaults), all 3 should be included for consistency. + +#### `def setup(self):` + +This function is run once during the node daemon startup, when the plugin is loaded. It can be used to get one-time setup information, populate plugin instance variables, etc. + +The function must take no arguments except `self` and anything returned is ignored. + +A plugin can also be disabled live in the setup function by throwing any `Exception`. Such exceptions will be caught and the plugin will not be loaded in such a case. + +#### `def cleanup(self):` + +This function mirrors the setup function, and is run once during the node daemon shutdown process. It can be used to clean up any lingering items (e.g. temporary files) created by the setup or run functions, if required; generally plugins do not need to do any cleanup. + +#### `def run(self):` + +This function is run each time the plugin is called during a keepalive. It performs the main work of the plugin before returning the end result in a specific format. + +Note that this function runs once for each keepalive, which by default is every 5 seconds. It is thus important to keep the runtime as short as possible and avoid doing complex calculations, file I/O, etc. during the plugin run. Do as much as possible in the setup function to keep the run function as quick as possible. A good safe maximum time for any plugin (e.g. if implementing an internal timeout) is 2 seconds. + +What happens during the run function is of course completely up to the plugin, but it must return a standardized set of details upon completing the run. + +An instance of the `PluginResult` object is helpfully created by the caller and passed in via `self.plugin_result`. This can be used to set the results as follows: + +* The `self.plugin_result.set_health_delta()` function can be used to set the current health delta of the result. This should be `0` unless the plugin detects a fault, at which point it can be any integer value below 100, and affects the node and cluster health as detailed above. + +* The `self.plugin_result.set_message()` function can be used to set the message text of the result, explaining in a short but human-readable way what the plugin result is. This will be shown in several places, including the node logs (if enabled), the node info output, and for results that have a health delta above 0, in the cluster status output. + +Finally, the `PluginResult` instance stored as `self.plugin_result` must be returned by the run function to the caller upon completion so that it can be added to the node state. + +### Logging + +The MonitoringPlugin class provides a helper logging method (usable as `self.log()`) to assist a plugin author in logging messages to the node daemon console log. This function takes one primary argument, a string message, and an optional `state` keyword argument for alternate states. + +The default state is `d` for debug, e.g. `state="d"`. The possible states for log messages are: + +* `"d"`: Debug, only printed when the administrator has debug logging enabled. Useful for detailed analysis of the plugin run state. +* `"i"`: Informational, printed at all times but with no intrinsic severity. Use these very sparingly if at all. +* `"t"`: Tick, matches the output of the keepalive itself. Use these very sparingly if at all. +* `"w"`: Warning, prints a warning message. Use these for non-fatal error conditions within the plugin. +* `"e"`: Error, prints an error message. Use these for fatal error conditions within the plugin. + +None of the example plugins make use of the logging interface, but it is available for custom plugins should it be required. + +The final output message of each plugin is automatically logged to the node daemon console log with `"t"` state at the completion of all plugins, if the `log_keepalive_plugin_details` configuration option is true. Otherwise, no final output is displayed. This setting does not affect messages printed from within a plugin. + +### Example Health Plugin + +This is a terse example of the `load` plugin, which is an extremely simple example that shows all the above requirements clearly. Comments are omitted here for simplicity, but these can be seen in the actual plugin file (at `/usr/share/pvc/plugins/load` on any node). + +``` +#!/usr/bin/env python3 + +# load.py: PVC monitoring plugin example + +from pvcnoded.objects.MonitoringInstance import MonitoringPlugin + +PLUGIN_NAME = "load" + +class MonitoringPluginScript(MonitoringPlugin): + def setup(self): + pass + + def cleanup(self): + pass + + def run(self): + from os import getloadavg + from psutil import cpu_count + + load_average = getloadavg()[0] + cpu_cores = cpu_count() + + if load_average > float(cpu_cores): + health_delta = 50 + else: + health_delta = 0 + + message = f"Current load is {load_average} out pf {cpu_cores} CPU cores" + + self.plugin_result.set_health_delta(health_delta) + self.plugin_result.set_message(message) + + return self.plugin_result +``` diff --git a/docs/manuals/provisioner.md b/docs/manuals/provisioner.md new file mode 100644 index 0000000..757cf4b --- /dev/null +++ b/docs/manuals/provisioner.md @@ -0,0 +1,441 @@ +# PVC Provisioner Manual + +The PVC provisioner is a subsection of the main PVC API. It interfaces directly with the Zookeeper database using the common client functions, and with the Patroni PostgreSQL database to store details. The provisioner also interfaces directly with the Ceph storage cluster, for mapping volumes, creating filesystems, and installing guests. + +Details of the Provisioner API interface can be found in [the API manual](/manuals/api). + +- [PVC Provisioner Manual](#pvc-provisioner-manual) + * [Overview](#overview) + * [PVC Provisioner concepts](#pvc-provisioner-concepts) + + [Templates](#templates) + + [Userdata](#cloud-init-userdata) + + [Scripts](#provisioning-scripts) + + [Profiles](#profiles) + * [Deploying VMs from provisioner scripts](#deploying-vms-from-provisioner-scripts) + * [Deploying VMs from OVA images](#deploying-vms-from-ova-images) + + [Uploading an OVA](#uploading-an-ova) + + [The OVA Provisioning Script](#the-ova-provisioning-script) + + [OVA limitations](#ova-limitations) + +## Overview + +The purpose of the Provisioner API is to provide a convenient way for administrators to automate the creation of new virtual machines on the PVC cluster. + +The Provisioner allows the administrator to construct descriptions of VMs, called profiles, which include system resource specifications, network interfaces, disks, cloud-init userdata, and installation scripts. These profiles are highly modular, allowing the administrator to specify arbitrary combinations of the mentioned VM features with which to build new VMs. + +The provisioner supports creating VMs based off of installation scripts, by cloning existing volumes, and by uploading OVA image templates to the cluster. + +Examples in the following sections use the CLI exclusively for demonstration purposes. For details of the underlying API calls, please see the [API interface reference](/manuals/api-reference.html). + +Use of the PVC Provisioner is not required. Administrators can always perform their own installation tasks, and the provisioner is not specially integrated, calling various other API commands as though they were run from the CLI or API. + +# PVC Provisioner concepts + +Before explaining how to create VMs using either OVA images or installer scripts, we must discuss the concepts used to construct the PVC provisioner system. + +## Templates + +Templates are the building blocks of VMs. Each template type specifies part of the configuration of a VM, and when combined together later into profiles, provide a full description of the VM resources. + +Templates are used to provide flexibility for the administrator. For instance, one could specify some standard core resources for different VMs, but then specify a different set of storage devices and networks for each one. This flexibility is at the heart of this system, allowing the administrator to construct a complex set of VM configurations from a few basic templates. + +The PVC Provisioner features three types of templates: System Templates, Network Templates, and Disk Templates. + +### System Templates + +System templates specify the basic resources of the virtual machine: vCPUs, memory, serial/VNC consoles, and PVC configuration metadata (migration methods, node limits, etc.). Each profile requires a single system template. + +The simplest valid template will specify a number of vCPUs and an amount of vRAM; additional details are optional and can be specified if required. + +Serial consoles are required to make use of the `pvc vm log` functionality, via console logfiles in `/var/log/libvirt` on the nodes. VMs without a serial console show an empty log. Note that the guest operating system must also be configured to provide output to this serial console for this functionality to work as expected. + +VNC consoles permit graphical access to the VM. By default, the VNC interface listens only on 127.0.0.1 on its parent node; the VNC bind configuration can override this to listen on other interfaces, including `0.0.0.0` for all. + +PVC does not currently support SPICE or any other non-VNC consoles. + +#### Examples + +``` +$ pvc provisioner template system list +Using cluster "local" - Host: "10.0.0.1:7370" Scheme: "http" Prefix: "/api/v1" + +System templates: + +Name ID vCPUs vRAM [MB] Consoles: Serial VNC VNC bind Metadata: Limit Selector Autostart Migration +ext-lg 80 4 8192 False False None None None False None +ext-lg-ser 81 4 8192 True False None None None False None +ext-lg-vnc 82 4 8192 False True 0.0.0.0 None None False None +ext-sm-lim 83 1 1024 True False None pvchv1,pvchv2 mem True live +``` + +* The first example specifies a template with 4 vCPUs and 8GB of RAM. It has no serial or VNC consoles, and no non-default metadata, forming the most basic possible system template. + +* The second example specifies a template with the same vCPU and RAM quantities as the first, but with a serial console as well. VMs using this template will be able to make use of `pvc vm log` as long as their guest operating system is configured to use it. + +* The third example specifies a template with an alternate console to the second, in this case a VNC console bound to `0.0.0.0` (all interfaces). VNC ports are always auto-selected due to the dynamic nature of PVC, and the administrator can connect to them once the VM is running by determining the port on the hosting hypervisor (e.g. with `netstat -tl`). + +* The fourth example shows the ability to set PVC cluster metadata in a system template. VMs with this template will be forcibly limited to running on the hypervisors `pvchv1` and `pvchv2`, but no others, will explicitly use the `mem` (free memory) selector when choosing migration or deployment targets, will be set to automatically start on reboot of its hypervisor, and will be limited to live migration between nodes. For full details on what these options mean, see `pvc vm meta -h`. + +### Network Templates + +Network template specify which PVC networks the virtual machine will be bound to, as well as the method used to calculate MAC addresses for VM interfaces. Networks are specified by their VNI ID within PVC. + +A network template requires at least one network VNI to be valid, and is created in two stages. First, `pvc provisioner template network add` adds the template itself, along with the optional MAC template. Second, `pvc provisioner template network vni add` adds a VNI into the network template. VNIs are always shown and created in the order added; to move networks around they must be removed then re-added in the proper order; this will not affect existing VMs provisioned with the template. + +In some cases, it may be useful for the administrator to specify a static MAC address pattern for a set of VMs, for instance if they must get consistent DHCP reservations between rebuilds. Such a MAC address template can be specified when adding a new network template, using a standardized layout and set of interpolated variables. This is an optional feature; if no MAC template is specified, VMs will be configured with random MAC addresses for each interface at deploy time. + +#### Examples + +``` +$ pvc provisioner template network list +Using cluster "local" - Host: "10.0.0.1:7370" Scheme: "http" Prefix: "/api/v1" + +Network templates: + +Name ID MAC template Network VNIs +ext-101 80 None 101 +ext-11X 81 None 110,1101 +fixed-mac 82 {prefix}:ff:ff:{vmid}{netid} 1000,1001,1002 +``` + +* The first example shows a simple single-VNI network with no MAC template. + +* The second example shows a dual-VNI network with no MAC template. Note the ordering; as mentioned, the first VNI will be provisioned on `eth0`, the second VNI on `eth1`, etc. + +* The third example shows a triple-VNI network with a MAC template. The variable names shown are literal, while the `f` values are user-configurable and must be set to valid hexadecimal values by the administrator to uniquely identify the MAC address (in this case, using `ff:ff` for that segment). The variables are interpolated at deploy time as follows: + + * The `{prefix}` variable is replaced by the provisioner with a standard prefix (`52:54:01`), which is different from the randomly-generated MAC prefix (`52:54:00`) to avoid accidental overlap of MAC addresses. These OUI prefixes are not assigned to any vendor by the IEEE and thus should not conflict with any (real, standards-compliant) devices on the network. + + * The `{vmid}` variable is replaced by a single hexadecimal digit representing the VM's ID, the numerical suffix portion of its name (e.g. `myvm2` will have ID 2); VMs without a suffix numeral in their names have ID 0. VMs with IDs greater than 15 (hexadecimal `f`) will wrap back to 0, so a single MAC template should never be used by more than 16 VMs (numbered 0-15). + + * The `{netid}` variable is replaced by a single hexadecimal digit representing the sequential identifier, starting at 0, of the interface within the template (i.e. the first interface is 0, the second is 1, etc.). Like the VM ID, network IDs greater than 15 (hexadecimal `f`) will wrap back to 0, so a single VM should never have more than 16 interfaces. + + * The location of the two per-VM variables can be adjusted at the administrator's discretion, or removed if not required (e.g. a single-network template, or template for a single VM). In such situations, be careful to avoid accidental overlap with other templates' variable portions. + +### Disk Templates + +Disk templates specify the disk layout, including filesystem and mountpoint for scripted deployments, for the VM. Disks are specified by their virtual disk ID in Libvirt, in either `sdX` or `vdX` format, and sizes are always specified in GB. Disks may also reference other storage volumes, which will then be cloned during provisioning. + +For additional flexibility, the volume filesystem and mountpoint are optional; such volumes will be created and attached to the VM but will not be modified during provisioning. + +All storage volumes created by the provisioner at deploy time, regardless of source or type, will be named in the format `_`, for instance `myvm_sda`. + +#### Examples + +``` +$ pvc provisioner template storage list +Using cluster "local" - Host: "10.0.0.1:7370" Scheme: "http" Prefix: "/api/v1" + +Storage templates: + +Name ID Disk ID Pool Source Volume Size [GB] Filesystem Arguments Mountpoint +standard-ext4 21 + sda vms None 2 ext4 -L=root / + sdb vms None 4 ext4 -L=var /var + sdc vms None 4 ext4 -L=log /var/log +large-cloned 22 + sda vms template_sda None None None None + sdb vms None 40 None None None +``` + +* The first example shows a volume with a simple 3-disk layout suitable for most Linux distributions. Each volume is in pool `vms`, with an `ext4` filesystem, an argument specifying a disk label, and a mountpoint to which the volume will be mounted when deploying the VM. All 3 volumes will be created at deploy time. When deploying VMs using Scripts detailed below, this is the normal format that storage templates should take to ensure that all block devices are formatted and mounted in the proper place for the script to take over and install the operating system to them. + +* The second example shows both a cloned volume and a blank volume. At deploy time, the Source Volume for the `sda` device will be cloned and attached to the VM at `sda`. The second volume will be created at deploy time, but will not be formatted or mounted, and will thus show as an empty block device inside the VM. This type of storage template is more suited to devices that do not use the Script install method, and are instead cloned from a source volume, either another running VM, or a manually-uploaded disk image. + +* Unformatted block devices as shown in the second example can be used in any type of storage template, though care should be taken to consider their purpose; unformatted block devices are completely ignored by the Script at deploy time. + +## Cloud-Init Userdata + +PVC allows the sending of arbitrary cloud-init userdata to VMs on boot-up. It uses an Amazon AWS EC2-style metadata service, listening at the link-local IP `169.254.169.254` on port `80`, to delivery basic VM information and this userdata to the VMs. The metadata to be sent is based dynamically on the assigned profile of the VM at boot time. + +Both single-function and multipart cloud-init userdata is supported. Full examples can be found under `/usr/share/pvc/provisioner/examples` on any PVC coordinator node. + +The default userdata document "empty" can be used to skip userdata for a profile. + +#### Examples + +``` +$ pvc provisioner userdata list +Using cluster "local" - Host: "10.0.0.1:7370" Scheme: "http" Prefix: "/api/v1" + +Name ID Document +empty 10 +basic-ssh 11 Content-Type: text/cloud-config; charset="us-ascii" + MIME-Version: 1.0 + + #cloud-config + [...] +``` + +* The first example is the default, always-present `empty` document, which is sent to invalid VMs if requested, or can be configured explicitly for profiles that do not require cloud-init userdata, instead of leaving that section of the profile as `None`. + +* The second, truncated, example is the start of a normal single-function userdata document. For full details on the contents of these documents, see the cloud-init documentation. + +## Provisioning Scripts + +The PVC provisioner provides a scripting framework in order to automate VM installation. This is generally the most useful with UNIX-like systems which can be installed over the network via shell scripts. For instance, the script might install a Debian VM using `debootstrap`, which is automatically installed by default. However all deployment profiles require some provisioning script, minimally to craft their Libvirt configuration. + +Several example scripts are provided in the `/usr/share/pvc/provisioner/examples/scripts` directory of all PVC hypervisors. These can be imported into the provisioner system as-is to help get you started, or you are of course free to modify or extend these as you wish, or write your own based on them to suit your needs. + +Provisioner scripts are written in Python 3 and are implemented as a class, `VMBuilderScript`, which extends the built-in `VMBuilder` class, for example: + +```python +#!/usr/bin/env python3 +# I am an example provisioner script + +from pvcapid.vmbuilder import VMBuilder + +class VMBuilderScript(VMBuilder): + def setup(self): + ... +``` + +Each `VMBuilderScript` class instance should provide the 5 functions defined by the VMBuilder class (or they will be noops). All 5 functions should take no arguments except `self`; data is passed to them from the parent `VMBuilder` class as outlined below. Each function provides a specific part of the installation process to automate each step with maximum flexibility: + +* `setup()`: Performs any special initial setup (e.g. fetching scripts or configs from the Internet) and validation of the environment (e.g. checking if particular binaries are available) before proceeding with the install. + +* `create()`: Creates the VM libvirt XML definition based on the information provided by the VM profile and arguments. This is the only function that returns data (namely, the string representation of the XML config). + +* `prepare()`: Creates and prepares any RBD storage volumes, filesystems, and mountpoints for the next step. + +* `install()`: Performs any install steps required; note that the lines between `prepare()` and `install()` are fuzzy; the main point is that these are delineated in the sequence as discrete steps. + +* `cleanup()`: Performs any "inner" cleanup of things done in the `prepare()` or `install()` steps (e.g. unmounting and unmapping RBD volumes, removing temporary files, etc.); also called on any *failure* of those steps. + +Each step is described in more detail in the various examples, and those should be consulted to get a full understanding of how the steps work. + +Note that no `__init__` should be provided by a script: doing so could result in failing scripts and should not be required. + +As mentioned above, the `VMBuilderScript` instance includes several instance variables inherited from the parent `VMBuilder` definition. These consist of: + +* `self.vm_name`: The name of the VM as provided to `pvc provisioner create`. + +* `self.vm_id`: The numeral at the end of the `vm_name` (e.g. 2 for `web2`), or `0` if no numeral is present. Mostly useful when combined with network MAC address templates or preseeding clustered hosts. + +* `self.vm_uuid`: An automatically, randomly-generated universal unique ID for the VM to use in its Libvirt XML definition (or elsewhere, if required). + +* `self.vm_profile`: The name of the PVC provisioner profile used to create the VM. Mostly useful for VM descriptions. + +* `self.vm_data`: A full dictionary representation of the data provided by the PVC provisioner about the VM. Includes many useful details for crafting the VM configuration and setting up disks and networks. An example, in JSON format: + + ``` + { + "ceph_monitor_list": [ + "hv1.pvcstorage.tld", + "hv2.pvcstorage.tld", + "hv3.pvcstorage.tld" + ], + "ceph_monitor_port": "6789", + "ceph_monitor_secret": "96721723-8650-4a72-b8f6-a93cd1a20f0c", + "mac_template": null, + "networks": [ + { + "eth_bridge": "vmbr1001", + "id": 72, + "network_template": 69, + "vni": "1001" + }, + { + "eth_bridge": "vmbr101", + "id": 73, + "network_template": 69, + "vni": "101" + } + ], + "script": [contents of this file] + "script_arguments": { + "deb_mirror": "http://ftp.debian.org/debian", + "deb_release": "bullseye" + }, + "system_architecture": "x86_64", + "system_details": { + "id": 78, + "migration_method": "live", + "name": "small", + "node_autostart": false, + "node_limit": null, + "node_selector": null, + "ova": null, + "serial": true, + "vcpu_count": 2, + "vnc": false, + "vnc_bind": null, + "vram_mb": 2048 + }, + "volumes": [ + { + "disk_id": "sda", + "disk_size_gb": 4, + "filesystem": "ext4", + "filesystem_args": "-L=root", + "id": 9, + "mountpoint": "/", + "pool": "vms", + "source_volume": null, + "storage_template": 67 + }, + { + "disk_id": "sdb", + "disk_size_gb": 4, + "filesystem": "ext4", + "filesystem_args": "-L=var", + "id": 10, + "mountpoint": "/var", + "pool": "vms", + "source_volume": null, + "storage_template": 67 + }, + { + "disk_id": "sdc", + "disk_size_gb": 4, + "filesystem": "ext4", + "filesystem_args": "-L=log", + "id": 11, + "mountpoint": "/var/log", + "pool": "vms", + "source_volume": null, + "storage_template": 67 + } + ] + } + ``` + +Since the `VMBuilderScript` runs within its own context but within the PVC Provisioner/API system, it is possible to use many helper libraries from the PVC system itself, including both the built-in daemon libraries (used by the API itself) and several explicit provisioning script helpers. The following are commonly-used (in the examples) imports that can be leveraged: + +* `pvcapid.vmbuilder.VMBuilder`: Required, provides the parent class for the `VMBuilderScript` class. +* `pvcapid.vmbuilder.ProvisioningError`: An exception that should be used within the `VMBuilderScript` to raise exceptions (though you can of course raise any other exception you wish or define your own). +* `pvcapid.vmbuilder.open_zk`: A context manager that can be used to open a Zookeeper connection, providing a `zkhandler` that can be passed to other PVC daemon library functions below. +* `pvcapid.vmbuilder.chroot`: A context manager that can be used to easily `chroot` into a given directory. +* `pvcapid.Daemon.config`: A configuration variable that *must* be passed to `open_zk` if it is used. +* `pvcapid.libvirt_schema`: A library providing a number of helpful Libvirt XML snippits that can be used to aid in building a working VM config for PVC. See the examples for a full usecase. +* `daemon_lib.common`: Part of the PVC daemon libraries, provides several common functions, including, most usefully, `run_os_command` which provides a wrapped, convenient method to call arbitrary shell/OS commands while returning a POSIX returncode, stdout, and stderr (a tuple of the 3 in that order). +* `daemon_lib.ceph`: Part of the PVC daemon libraries, provides several commands for managing Ceph RBD volumes, including, but not limited to, `clone_volume`, `add_volume`, `map_volume`, and `unmap_volume`. See the `debootstrap` example for a detailed usage example. + +For safety reasons, the script runs in a modified chroot environment on the hypervisor. It will have full access to the entire / (root partition) of the hypervisor, but read-only. In addition it has read-write access to /dev, /sys, /run, and a fresh /tmp to write to; use /tmp/target (as convention) as the destination for any mounting of volumes and installation. Thus it is not possible to do things like `apt-get install`ing additional programs within a script; any such requirements must be set up before running the script (e.g. via `pvc-ansible`). + +**WARNING**: Of course, despite this "safety" mechanism, it is VERY IMPORTANT to be cognizant that this script runs AS ROOT ON THE HYPERVISOR SYSTEM with FULL ACCESS to the cluster. You should NEVER allow arbitrary, untrusted users the ability to add or modify provisioning scripts. It is trivially easy to write scripts which will do destructive things - for example writing to arbitrary /dev objects, running arbitrary root-level commands, or importing PVC library functions to delete VMs, RBD volumes, or pools. Thus, ensure you vett and understand every script on the system, audit them regularly for both intentional and accidental malicious activity, and of course (to reiterate), do not allow untrusted script creation! + +## Profiles + +Provisioner profiles combine the templates, userdata, and scripts together into dynamic configurations which are then applied to the VM when provisioned. The VM retains the record of this profile name in its configuration for the full lifetime of the VM on the cluster; this is primarily used for cloud-init functionality, but may also serve as a convenient administrator reference. + +Additional arguments to the installation script can be specified along with the profile, to allow further customization of the installation if required. + +#### Examples + +``` +$ pvc provisioner profile list +Using cluster "local" - Host: "10.0.0.1:7370" Scheme: "http" Prefix: "/api/v1" + +Name ID Templates: System Network Storage Data: Userdata Script Script Arguments +std-large 41 ext-lg-ser ext-101 standard-ext4 basic-ssh debootstrap deb_release=buster +``` + +# Deploying VMs from provisioner scripts + +Once a profile with a Script value is defined, creating VMs with the provisioner is as simple as specifying a VM name and a profile to use. + +``` +$ pvc provisioner create test1 std-large +Using cluster "local" - Host: "10.0.0.1:7370" Scheme: "http" Prefix: "/api/v1" + +Task ID: af1d0682-53e8-4141-982f-f672e2f23261 +``` + +This will create a worker job on the current primary node, and status can be queried by providing the job ID. + +``` + $ pvc provisioner status af1d0682-53e8-4141-982f-f672e2f23261 +Using cluster "local" - Host: "10.0.0.1:7370" Scheme: "http" Prefix: "/api/v1" + +Job state: RUNNING +Stage: 4/10 +Status: Running script setup() step +``` + +A list of all running and queued jobs can be obtained by requesting the provisioner status without an ID. + +``` +$ pvc provisioner status +Using cluster "local" - Host: "10.0.0.1:7370" Scheme: "http" Prefix: "/api/v1" + +Job ID Status Worker VM: Name Profile Define? Start? +af1d0682-53e8-4141-982f-f672e2f23261 active celery@pvchv1 test1 std-large True True +94abb7fe-41f5-42be-b984-de92854f4b3f pending celery@pvchv1 test2 std-large True True +43d57a2d-8d0d-42f6-90df-cc39956825a9 pending celery@pvchv1 testX std-large False False +``` + +The `--wait` option can be given to the create command. This will cause the command to block and providing a visual progress indicator while the provisioning occurs. + +``` +$ pvc provisioner create --wait test2 std-large +Using cluster "local" - Host: "10.0.0.1:7370" Scheme: "http" Prefix: "/api/v1" + +Task ID: 94abb7fe-41f5-42be-b984-de92854f4b3f + +Waiting for task to start..... done. + + [####################################] 100% Starting VM + +SUCCESS: VM "test2" with profile "std-large" has been provisioned and started successfully +``` + +The administrator can also specify whether or not to automatically define and start the VM when launching a provisioner job, using the `--define`/`--no-define` and `--start`/`--no-start` options. The default is to define and start a VM. `--no-define` implies `--no-start` as there would be no VM to start. Using `--no-start` can be useful if other tasks must be performed before starting the VM for the first time, and `--no-define` can be useful for creating "template" VMs which would then be cloned by other profiles. + +``` +$ pvc provisioner create test3 std-large --no-define +Using cluster "local" - Host: "10.0.0.1:7370" Scheme: "http" Prefix: "/api/v1" + +Task ID: 43d57a2d-8d0d-42f6-90df-cc39956825a9 +``` + +Finally, the administrator may specify further, one-time script arguments at install time, to further tune the VM installation (e.g. setting an FQDN or some conditional to trigger additional steps in the script). + +``` +$ pvc provisioner create test4 std-large --script-arg vm_fqdn=testhost.example.tld --script-arg my_foo=True +Using cluster "local" - Host: "10.0.0.1:7370" Scheme: "http" Prefix: "/api/v1" + +Task ID: 39639f8c-4866-49de-8c51-4179edec0194 +``` + +**NOTE**: A VM that is set to do so will be defined on the cluster early in the provisioning process, before creating disks or executing the provisioning script, with the special status `provision`. Once completed, if the VM is not set to start automatically, the state will remain `provision`, with the VM not running, until its state is explicitly changed with the client (or via autostart when its node returns to `ready` state). + +**NOTE**: Provisioning jobs are tied to the node that spawned them. If the primary node changes, provisioning jobs will continue to run against that node until they are completed, interrupted, or fail, but the active API (now on the new primary node) will not have access to any status data from these jobs, until the primary node status is returned to the original host. The CLI will warn the administrator of this if there are active jobs while running `node primary` or `node secondary` commands. + +**NOTE**: Provisioning jobs cannot be cancelled, either before they start or during execution. The administrator should always let an invalid job either complete or fail out automatically, then remove the erroneous VM with the `vm remove` command. + +# Deploying VMs from OVA images + +PVC supports deploying virtual machines from industry-standard OVA images. OVA images can be uploaded to the cluster with the `pvc provisioner ova` commands, and deployed via the created profile(s) using the `pvc provisioner create` command detailed above for scripted installs; the process is the same in both cases. Additionally, the profile(s) can be modified to suite your specific needs after creation. + +## Uploading an OVA + +Once the OVA is uploaded to the cluster with the `pvc provisioner ova upload` command, it will be visible in two different places: + +* In `pvc provisioner ova list`, one can see all uploaded OVA images as well as details on their disk configurations. + +* In `pvc profile list`, a new profile will be visible which matches the OVA `NAME` from the upload. This profile will have a "Source" of `OVA `, and a system template of the same name. This system template will contain the basic configuration of the VM. You may notice that the other templates and data are set to `N/A`. For full details on this, see the next section. + +## The OVA Provisioner Script + +OVA installs leverage a special provisioner script to handle the VM creation, identical to any other provisioner profile type. This (example) script, or a replacement, must be installed prior to uploading an OVA, and handles the actual VM configuration creation and cloning of the OVA volumes. + +## OVA limitations + +PVC does not implement a *complete* OVA framework. While all basic elements of the OVA are included, the following areas require special attention. + +### Networks + +Because the PVC provisioner has its own conception of networks separate from the OVA profiles, the administrator must perform this mapping themselves, by first creating a network template, and the required networks on the PVC cluster, and then modifying the profile of the resulting OVA. + +The provisioner profile for the OVA can be safely modified to include this new network template at any time, and the resulting VM will be provisioned with these networks. + +This setup was chosen specifically to eliminate corner cases. Most OVA images include a single, "default" network interface, and expect the administrator of the hypervisor to modify this later. You can of course do this, but since PVC has its own conception of networks already in the provisioner, it makes more sense to ignore what the OVA specifies, and allow the administrator full control over this portion of the VM config, before deployment. It is thus always important to be aware of the network requirements of your OVA images, especially if they require specific network configurations, and then create a network template to match. + +### Storage + +During import, PVC splits the OVA into its constituent parts, including any disk images (usually VMDK-formatted). It will then create a separate PVC storage volume for each disk image. These storage volumes are then converted at deployment time from the VMDK format to the PVC native raw format based on their included size in the OVA. Once the storage volume for an actual VM deployment is created, it can then be resized as needed. + +Because of this, OVA profiles do not include storage templates like other PVC profiles. A storage template can still be added to such a profile, and the block devices will be added after the main block devices. However, this is generally not recommended; it is far better to modify the OVA to add additional volume(s) before uploading it instead. + +**WARNING**: Never adjust the sizes of the OVA VMDK-formatted storage volumes (named `ova__sdX`) or remove them without removing the OVA itself in the provisioner; doing so will prevent the deployment of the OVA, specifically the conversion of the images to raw format at deploy time, and render the OVA profile useless. diff --git a/docs/manuals/swagger.json b/docs/manuals/swagger.json new file mode 100644 index 0000000..9096fb7 --- /dev/null +++ b/docs/manuals/swagger.json @@ -0,0 +1,7015 @@ +{ + "definitions": { + "API-Version": { + "properties": { + "message": { + "description": "A text message", + "example": "PVC API version 1.0", + "type": "string" + } + }, + "type": "object" + }, + "Cluster Data": { + "type": "object" + }, + "ClusterStatus": { + "properties": { + "cluster_health": { + "properties": { + "health": { + "description": "The overall health (%) of the cluster", + "example": 100, + "type": "integer" + }, + "messages": { + "description": "A list of health event strings", + "items": { + "example": "hv1: plugin 'nics': bond0 DEGRADED with 1 active slaves, bond0 OK at 10000 Mbps", + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "maintenance": { + "description": "Whether the cluster is in maintenance mode or not (string boolean)", + "example": true, + "type": "string" + }, + "networks": { + "description": "The total number of networks in the cluster", + "type": "integer" + }, + "node_health": { + "properties": { + "hvX": { + "description": "A node entry for per-node health details, one per node in the cluster", + "properties": { + "health": { + "description": "The health (%) of the node", + "example": 100, + "type": "integer" + }, + "messages": { + "description": "A list of health event strings", + "items": { + "example": "'nics': bond0 DEGRADED with 1 active slaves, bond0 OK at 10000 Mbps", + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "nodes": { + "properties": { + "state-combination": { + "description": "The total number of nodes in {state-combination} state, where {state-combination} is the node daemon and domain states in CSV format, e.g. \"run,ready\", \"stop,flushed\", etc.", + "type": "integer" + }, + "total": { + "description": "The total number of nodes in the cluster", + "example": 3, + "type": "integer" + } + }, + "type": "object" + }, + "osds": { + "properties": { + "state-combination": { + "description": "The total number of OSDs in {state-combination} state, where {state-combination} is the OSD up and in states in CSV format, e.g. \"up,in\", \"down,out\", etc.", + "type": "integer" + }, + "total": { + "description": "The total number of OSDs in the storage cluster", + "example": 3, + "type": "integer" + } + }, + "type": "object" + }, + "pools": { + "description": "The total number of pools in the storage cluster", + "type": "integer" + }, + "primary_node": { + "description": "The current primary coordinator node", + "example": "pvchv1", + "type": "string" + }, + "pvc_version": { + "description": "The PVC version of the current primary coordinator node", + "example": "0.9.61", + "type": "string" + }, + "snapshots": { + "description": "The total number of snapshots in the storage cluster", + "type": "integer" + }, + "upstream_ip": { + "description": "The cluster upstream IP address in CIDR format", + "example": "10.0.0.254/24", + "type": "string" + }, + "vms": { + "properties": { + "state": { + "description": "The total number of VMs in {state} state, e.g. \"start\", \"stop\", etc.", + "type": "integer" + }, + "total": { + "description": "The total number of VMs in the cluster", + "example": 6, + "type": "integer" + } + }, + "type": "object" + }, + "volumes": { + "description": "The total number of volumes in the storage cluster", + "type": "integer" + } + }, + "type": "object" + }, + "Message": { + "properties": { + "message": { + "description": "A text message", + "type": "string" + } + }, + "type": "object" + }, + "NodeCoordinatorState": { + "properties": { + "coordinator_state": { + "description": "The current coordinator state", + "type": "string" + }, + "name": { + "description": "The name of the node", + "type": "string" + } + }, + "type": "object" + }, + "NodeDaemonState": { + "properties": { + "daemon_state": { + "description": "The current daemon state", + "type": "string" + }, + "name": { + "description": "The name of the node", + "type": "string" + } + }, + "type": "object" + }, + "NodeDomainState": { + "properties": { + "domain_state": { + "description": "The current domain state", + "type": "string" + }, + "name": { + "description": "The name of the node", + "type": "string" + } + }, + "type": "object" + }, + "NodeLog": { + "properties": { + "data": { + "description": "The recent log text", + "type": "string" + }, + "name": { + "description": "The name of the Node", + "type": "string" + } + }, + "type": "object" + }, + "VMLog": { + "properties": { + "data": { + "description": "The recent console log text", + "type": "string" + }, + "name": { + "description": "The name of the VM", + "type": "string" + } + }, + "type": "object" + }, + "VMMetadata": { + "properties": { + "migration_method": { + "description": "The preferred migration method (live, shutdown, none)", + "type": "string" + }, + "name": { + "description": "The name of the VM", + "type": "string" + }, + "node_autostart": { + "description": "Whether to autostart the VM when its node returns to ready domain state", + "type": "string" + }, + "node_limit": { + "description": "The node(s) the VM is permitted to be assigned to", + "items": { + "type": "string" + }, + "type": "array" + }, + "node_selector": { + "description": "The selector used to determine candidate nodes during migration; see 'target_selector' in the node daemon configuration reference", + "type": "string" + } + }, + "type": "object" + }, + "VMNode": { + "properties": { + "last_node": { + "description": "The last node the VM was assigned to before migrating", + "type": "string" + }, + "name": { + "description": "The name of the VM", + "type": "string" + }, + "node": { + "description": "The node the VM is currently assigned to", + "type": "string" + } + }, + "type": "object" + }, + "VMState": { + "properties": { + "name": { + "description": "The name of the VM", + "type": "string" + }, + "state": { + "description": "The current state of the VM", + "type": "string" + } + }, + "type": "object" + }, + "VMTags": { + "properties": { + "name": { + "description": "The name of the VM", + "type": "string" + }, + "tags": { + "description": "The tag(s) of the VM", + "items": { + "id": "VMTag", + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "acl": { + "properties": { + "description": { + "description": "The description of the rule", + "type": "string" + }, + "direction": { + "description": "The direction the rule applies in", + "type": "string" + }, + "order": { + "description": "The order of the rule in the chain", + "type": "integer" + }, + "rule": { + "description": "The NFT-format rule string", + "type": "string" + } + }, + "type": "object" + }, + "all-templates": { + "properties": { + "network-templates": { + "items": { + "$ref": "#/definitions/network-template" + }, + "type": "array" + }, + "storage-templates": { + "items": { + "$ref": "#/definitions/storage-template" + }, + "type": "array" + }, + "system-templates": { + "items": { + "$ref": "#/definitions/system-template" + }, + "type": "array" + } + }, + "type": "object" + }, + "lease": { + "properties": { + "hostname": { + "description": "The (short) hostname of the lease", + "type": "string" + }, + "ip4_address": { + "description": "The IPv4 address of the lease", + "type": "string" + }, + "mac_address": { + "description": "The MAC address of the lease", + "type": "string" + }, + "timestamp": { + "description": "The UNIX timestamp of the lease creation", + "type": "integer" + } + }, + "type": "object" + }, + "network": { + "properties": { + "description": { + "description": "The description of the network", + "type": "string" + }, + "domain": { + "description": "The DNS domain of the network (\"managed\" networks only)", + "type": "string" + }, + "ip4": { + "description": "The IPv4 details of the network (\"managed\" networks only)", + "properties": { + "dhcp_end": { + "description": "The IPv4 DHCP pool end address", + "type": "string" + }, + "dhcp_flag": { + "description": "Whether DHCP is enabled", + "type": "boolean" + }, + "dhcp_start": { + "description": "The IPv4 DHCP pool start address", + "type": "string" + }, + "gateway": { + "description": "The IPv4 default gateway address", + "type": "string" + }, + "network": { + "description": "The IPv4 network subnet in CIDR format", + "type": "string" + } + }, + "type": "object" + }, + "ip6": { + "description": "The IPv6 details of the network (\"managed\" networks only)", + "properties": { + "dhcp_flag": { + "description": "Whether DHCPv6 is enabled", + "type": "boolean" + }, + "gateway": { + "description": "The IPv6 default gateway address", + "type": "string" + }, + "network": { + "description": "The IPv6 network subnet in CIDR format", + "type": "string" + } + }, + "type": "object" + }, + "mtu": { + "description": "The MTU of the network, if set; empty otherwise", + "type": "integer" + }, + "name_servers": { + "description": "The configured DNS nameservers of the network for NS records (\"managed\" networks only)", + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "description": "The type of network", + "enum": [ + "managed", + "bridged" + ], + "type": "string" + }, + "vni": { + "description": "The VNI of the network", + "type": "integer" + } + }, + "type": "object" + }, + "network-template": { + "properties": { + "id": { + "description": "Internal provisioner template ID", + "type": "integer" + }, + "mac_template": { + "description": "MAC address template for VM", + "type": "string" + }, + "name": { + "description": "Template name", + "type": "string" + }, + "networks": { + "items": { + "$ref": "#/definitions/network-template-net" + }, + "type": "array" + } + }, + "type": "object" + }, + "network-template-net": { + "properties": { + "id": { + "description": "Internal provisioner template ID", + "type": "integer" + }, + "network_template": { + "description": "Internal provisioner network template ID", + "type": "integer" + }, + "vni": { + "description": "PVC network VNI", + "type": "integer" + } + }, + "type": "object" + }, + "node": { + "properties": { + "arch": { + "description": "The architecture of the CPU", + "type": "string" + }, + "coordinator_state": { + "description": "The current coordinator state", + "type": "string" + }, + "cpu_count": { + "description": "The number of available CPU cores", + "type": "integer" + }, + "daemon_state": { + "description": "The current daemon state", + "type": "string" + }, + "domain_state": { + "description": "The current domain (VM) state", + "type": "string" + }, + "domains_count": { + "description": "The number of running domains (VMs)", + "type": "integer" + }, + "health": { + "description": "The overall health (%) of the node", + "example": 100, + "type": "integer" + }, + "health_details": { + "description": "A list of health plugin results", + "items": { + "properties": { + "health_delta": { + "description": "The health delta (negatively applied to the health percentage) of the plugin's current state", + "example": 10, + "type": "integer" + }, + "last_run": { + "description": "The UNIX timestamp (s) of the last plugin run", + "example": 1676786078, + "type": "integer" + }, + "message": { + "description": "The output message of the plugin", + "example": "bond0 DEGRADED with 1 active slaves, bond0 OK at 10000 Mbps", + "type": "string" + }, + "name": { + "description": "The name of the health plugin", + "example": "nics", + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "health_plugins": { + "description": "A list of health plugin names currently loaded on the node", + "items": { + "example": "nics", + "type": "string" + }, + "type": "array" + }, + "kernel": { + "desription": "The running kernel version from uname", + "type": "string" + }, + "load": { + "description": "The current 5-minute CPU load", + "format": "float", + "type": "number" + }, + "memory": { + "properties": { + "allocated": { + "description": "The total amount of RAM allocated to running domains in MB", + "type": "integer" + }, + "free": { + "description": "The total free RAM on the node in MB", + "type": "integer" + }, + "provisioned": { + "description": "The total amount of RAM provisioned to all domains (regardless of state) on this node in MB", + "type": "integer" + }, + "total": { + "description": "The total amount of node RAM in MB", + "type": "integer" + }, + "used": { + "description": "The total used RAM on the node in MB", + "type": "integer" + } + }, + "type": "object" + }, + "name": { + "description": "The name of the node", + "type": "string" + }, + "os": { + "description": "The current operating system type", + "type": "string" + }, + "pvc_version": { + "description": "The current running PVC node daemon version", + "type": "string" + }, + "running_domains": { + "description": "The list of running domains (VMs) by UUID", + "type": "string" + }, + "vcpu": { + "properties": { + "allocated": { + "description": "The total number of allocated vCPU cores", + "type": "integer" + }, + "total": { + "description": "The total number of real CPU cores available", + "type": "integer" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "osd": { + "properties": { + "db_device": { + "description": "The OSD database/WAL block device (logical volume); empty if not applicable", + "type": "string" + }, + "device": { + "description": "The OSD data block device", + "type": "string" + }, + "id": { + "description": "The Ceph ID of the OSD", + "type": "string (containing integer)" + }, + "stats": { + "properties": { + "avail": { + "description": "The free space on the OSD in human-readable format", + "type": "string" + }, + "in": { + "description": "Whether OSD is in \"in\" state", + "type": "boolean integer" + }, + "kb": { + "description": "Size of the OSD in KB", + "type": "integer" + }, + "node": { + "description": "The PVC node the OSD resides on", + "type": "string" + }, + "pgs": { + "description": "The number of placement groups on this OSD", + "type": "integer" + }, + "primary_affinity": { + "description": "The Ceph primary affinity of the OSD", + "type": "integer" + }, + "rd_data": { + "description": "Cluster-lifetime read size from OSD", + "type": "integer" + }, + "rd_ops": { + "description": "Cluster-lifetime read operations from OSD", + "type": "integer" + }, + "reweight": { + "description": "The active cluster weight of the OSD", + "type": "number" + }, + "state": { + "description": "CSV of the current state of the OSD", + "type": "string" + }, + "up": { + "description": "Whether OSD is in \"up\" state", + "type": "boolean integer" + }, + "used": { + "description": "The used space on the OSD in human-readable format", + "type": "string" + }, + "utilization": { + "description": "The utilization percentage of the OSD", + "type": "number" + }, + "uuid": { + "description": "The Ceph OSD UUID", + "type": "string" + }, + "var": { + "description": "The usage variability among OSDs", + "type": "number" + }, + "weight": { + "description": "The weight of the OSD in the CRUSH map", + "type": "number" + }, + "wr_data": { + "description": "Cluster-lifetime write size to OSD", + "type": "integer" + }, + "wr_ops": { + "description": "Cluster-lifetime write operations to OSD", + "type": "integer" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "ova": { + "properties": { + "id": { + "description": "Internal provisioner OVA ID", + "type": "integer" + }, + "name": { + "description": "OVA name", + "type": "string" + }, + "volumes": { + "items": { + "id": "ova_volume", + "properties": { + "disk_id": { + "description": "Disk identifier", + "type": "string" + }, + "disk_size_gb": { + "description": "Disk size in GB", + "type": "string" + }, + "pool": { + "description": "Pool containing the OVA volume", + "type": "string" + }, + "volume_format": { + "description": "OVA image format", + "type": "string" + }, + "volume_name": { + "description": "Storage volume containing the OVA image", + "type": "string" + } + }, + "type": "object" + }, + "type": "list" + } + }, + "type": "object" + }, + "pool": { + "properties": { + "name": { + "description": "The name of the pool", + "type": "string" + }, + "pgs": { + "description": "The number of PGs (placement groups) for the pool", + "type": "integer" + }, + "stats": { + "properties": { + "free_bytes": { + "description": "The total free space (in bytes. post-replicas)", + "type": "integer" + }, + "id": { + "description": "The Ceph pool ID", + "type": "integer" + }, + "num_object_clones": { + "description": "The total number of cloned Ceph objects", + "type": "integer" + }, + "num_object_copies": { + "description": "The total number of Ceph objects after replication", + "type": "integer" + }, + "num_objects": { + "description": "The number of Ceph objects before replication", + "type": "integer" + }, + "num_objects_degraded": { + "description": "The total number of degraded Ceph objects", + "type": "integer" + }, + "num_objects_missing_on_primary": { + "description": "The total number of missing-on-primary Ceph objects", + "type": "integer" + }, + "num_objects_unfound": { + "description": "The total number of unfound Ceph objects", + "type": "integer" + }, + "read_bytes": { + "description": "The total read bytes on the pool (pool-lifetime)", + "type": "integer" + }, + "read_ops": { + "description": "The total read operations on the pool (pool-lifetime)", + "type": "integer" + }, + "stored_bytes": { + "description": "The stored data size (in bytes, post-replicas)", + "type": "integer" + }, + "used_bytes": { + "description": "The total used space (in bytes, pre-replicas)", + "type": "integer" + }, + "used_percent": { + "description": "The ratio of used space to free space", + "type": "number" + }, + "write_bytes": { + "description": "The total write bytes on the pool (pool-lifetime)", + "type": "integer" + }, + "write_ops": { + "description": "The total write operations on the pool (pool-lifetime)", + "type": "integer" + } + }, + "type": "object" + }, + "tier": { + "description": "The device class/tier of the pool", + "type": "string" + }, + "volume_count": { + "description": "The number of volumes in the pool", + "type": "integer" + } + }, + "type": "object" + }, + "profile": { + "properties": { + "arguments": { + "items": { + "description": "Script install() function keyword arguments in \"arg=data\" format", + "type": "string" + }, + "type": "array" + }, + "id": { + "description": "Internal provisioner profile ID", + "type": "integer" + }, + "name": { + "description": "Profile name", + "type": "string" + }, + "network_template": { + "description": "Network template name", + "type": "string" + }, + "script": { + "description": "Script name", + "type": "string" + }, + "storage_template": { + "description": "Storage template name", + "type": "string" + }, + "system_template": { + "description": "System template name", + "type": "string" + }, + "userdata": { + "description": "Userdata template name", + "type": "string" + } + }, + "type": "object" + }, + "script": { + "properties": { + "id": { + "description": "Internal provisioner script ID", + "type": "integer" + }, + "name": { + "description": "Script name", + "type": "string" + }, + "script": { + "description": "Raw Python script document", + "type": "string" + } + }, + "type": "object" + }, + "snapshot": { + "properties": { + "pool": { + "description": "The name of the pool", + "type": "string" + }, + "snapshot": { + "description": "The name of the snapshot", + "type": "string" + }, + "volume": { + "description": "The name of the volume", + "type": "string" + } + }, + "type": "object" + }, + "sriov_pf": { + "properties": { + "mtu": { + "description": "The MTU of the SR-IOV PF device", + "type": "string" + }, + "phy": { + "description": "The name of the SR-IOV PF device", + "type": "string" + }, + "vfs": { + "items": { + "description": "The PHY name of a VF of this PF", + "type": "string" + }, + "type": "list" + } + }, + "type": "object" + }, + "sriov_vf": { + "properties": { + "config": { + "id": "sriov_vf_config", + "properties": { + "link_state": { + "description": "The current SR-IOV VF link state (either enabled, disabled, or auto)", + "type": "string" + }, + "query_rss": { + "description": "Whether VF RSS querying is enabled or disabled", + "type": "boolean" + }, + "spoof_check": { + "description": "Whether device spoof checking is enabled or disabled", + "type": "boolean" + }, + "trust": { + "description": "Whether guest device trust is enabled or disabled", + "type": "boolean" + }, + "tx_rate_max": { + "description": "The maximum TX rate of the SR-IOV VF device", + "type": "string" + }, + "tx_rate_min": { + "description": "The minimum TX rate of the SR-IOV VF device", + "type": "string" + }, + "vlan_id": { + "description": "The tagged vLAN ID of the SR-IOV VF device", + "type": "string" + }, + "vlan_qos": { + "description": "The QOS group of the tagged vLAN", + "type": "string" + } + }, + "type": "object" + }, + "mac": { + "description": "The current MAC address of the VF device", + "type": "string" + }, + "mtu": { + "description": "The current MTU of the VF device", + "type": "integer" + }, + "pf": { + "description": "The name of the SR-IOV PF parent of this VF device", + "type": "string" + }, + "phy": { + "description": "The name of the SR-IOV VF device", + "type": "string" + }, + "usage": { + "id": "sriov_vf_usage", + "properties": { + "domain": { + "description": "The UUID of the domain the SR-IOV VF is currently used by", + "type": "boolean" + }, + "used": { + "description": "Whether the SR-IOV VF is currently used by a VM or not", + "type": "boolean" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "storage-template": { + "properties": { + "disks": { + "items": { + "$ref": "#/definitions/storage-template-disk" + }, + "type": "array" + }, + "id": { + "description": "Internal provisioner template ID", + "type": "integer" + }, + "name": { + "description": "Template name", + "type": "string" + } + }, + "type": "object" + }, + "storage-template-disk": { + "properties": { + "disk_id": { + "description": "Disk identifier", + "type": "string" + }, + "disk_size_gb": { + "description": "Disk size in GB", + "type": "string" + }, + "filesystem": { + "description": "Filesystem for disk", + "type": "string" + }, + "filesystem_args": { + "items": { + "description": "Filesystem mkfs arguments", + "type": "string" + }, + "type": "array" + }, + "id": { + "description": "Internal provisioner disk ID", + "type": "integer" + }, + "mountpoint": { + "description": "In-VM mountpoint for disk", + "type": "string" + }, + "pool": { + "description": "Ceph storage pool for disk", + "type": "string" + }, + "storage_template": { + "description": "Internal provisioner storage template ID", + "type": "integer" + } + }, + "type": "object" + }, + "storagebenchmark": { + "properties": { + "benchmark_result": { + "description": "A format 0 test result", + "properties": { + "test_name": { + "properties": { + "bandwidth": { + "properties": { + "max": { + "description": "The maximum bandwidth (KiB/s) measurement", + "type": "string (integer)" + }, + "mean": { + "description": "The mean bandwidth (KiB/s) measurement", + "type": "string (float)" + }, + "min": { + "description": "The minimum bandwidth (KiB/s) measurement", + "type": "string (integer)" + }, + "numsamples": { + "description": "The number of samples taken during the test", + "type": "string (integer)" + }, + "stdev": { + "description": "The standard deviation of bandwidth", + "type": "string (float)" + } + }, + "type": "object" + }, + "cpu": { + "properties": { + "ctxsw": { + "description": "The number of context switches during the test", + "type": "string (integer)" + }, + "majfault": { + "description": "The number of major page faults during the test", + "type": "string (integer)" + }, + "minfault": { + "description": "The number of minor page faults during the test", + "type": "string (integer)" + }, + "system": { + "description": "The percentage of test time spent in system (kernel) space", + "type": "string (float percentage)" + }, + "user": { + "description": "The percentage of test time spent in user space", + "type": "string (float percentage)" + } + }, + "type": "object" + }, + "iops": { + "properties": { + "max": { + "description": "The maximum IOPS measurement", + "type": "string (integer)" + }, + "mean": { + "description": "The mean IOPS measurement", + "type": "string (float)" + }, + "min": { + "description": "The minimum IOPS measurement", + "type": "string (integer)" + }, + "numsamples": { + "description": "The number of samples taken during the test", + "type": "string (integer)" + }, + "stdev": { + "description": "The standard deviation of IOPS", + "type": "string (float)" + } + }, + "type": "object" + }, + "latency": { + "properties": { + "max": { + "description": "The maximum latency measurement", + "type": "string (integer)" + }, + "mean": { + "description": "The mean latency measurement", + "type": "string (float)" + }, + "min": { + "description": "The minimum latency measurement", + "type": "string (integer)" + }, + "stdev": { + "description": "The standard deviation of latency", + "type": "string (float)" + } + }, + "type": "object" + }, + "overall": { + "properties": { + "bandwidth": { + "description": "The average bandwidth (KiB/s)", + "type": "string (integer)" + }, + "iops": { + "description": "The average IOPS", + "type": "string (integer)" + }, + "iosize": { + "description": "The total size of the benchmark data", + "type": "string (integer)" + }, + "runtime": { + "description": "The total test time in milliseconds", + "type": "string (integer)" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "id": { + "description": "The database ID of the test result", + "type": "string (containing integer)" + }, + "job": { + "description": "The job name (an ISO date) of the test result", + "type": "string" + }, + "test_format": { + "description": "The PVC benchmark format of the results", + "type": "integer" + } + }, + "type": "object" + }, + "system-template": { + "properties": { + "id": { + "description": "Internal provisioner template ID", + "type": "integer" + }, + "migration_method": { + "description": "The preferred migration method (live, shutdown, none)", + "type": "string" + }, + "name": { + "description": "Template name", + "type": "string" + }, + "node_autostart": { + "description": "Whether to start VM with node ready state (one-time)", + "type": "boolean" + }, + "node_limit": { + "description": "CSV list of node(s) to limit VM assignment to", + "type": "string" + }, + "node_selector": { + "description": "Selector to use for VM node assignment on migration/move", + "type": "string" + }, + "serial": { + "description": "Whether to enable serial console for VM", + "type": "boolean" + }, + "vcpu_count": { + "description": "vCPU count for VM", + "type": "integer" + }, + "vnc": { + "description": "Whether to enable VNC console for VM", + "type": "boolean" + }, + "vnc_bind": { + "description": "VNC bind address when VNC console is enabled", + "type": "string" + }, + "vram_mb": { + "description": "vRAM size in MB for VM", + "type": "integer" + } + }, + "type": "object" + }, + "userdata": { + "properties": { + "id": { + "description": "Internal provisioner ID", + "type": "integer" + }, + "name": { + "description": "Userdata name", + "type": "string" + }, + "userdata": { + "description": "Raw userdata configuration document", + "type": "string" + } + }, + "type": "object" + }, + "vm": { + "properties": { + "arch": { + "description": "The architecture of the VM", + "type": "string" + }, + "console": { + "descritpion": "The serial console type of the VM", + "type": "string" + }, + "controllers": { + "description": "The device controllers attached to the VM", + "items": { + "properties": { + "model": { + "description": "The model of the controller", + "type": "string" + }, + "type": { + "description": "The type of the controller", + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "description": { + "description": "The description of the VM", + "type": "string" + }, + "disks": { + "description": "The PVC storage volumes attached to the VM", + "items": { + "properties": { + "bus": { + "description": "The virtual bus of the volume in the VM", + "type": "string" + }, + "dev": { + "description": "The device ID of the volume in the VM", + "type": "string" + }, + "name": { + "description": "The full name of the volume in \"pool/volume\" format", + "type": "string" + }, + "rd_bytes": { + "description": "The number of read bytes from the volume", + "type": "integer" + }, + "rd_req": { + "description": "The number of read requests from the volume", + "type": "integer" + }, + "type": { + "description": "The type of volume", + "type": "string" + }, + "wr_bytes": { + "description": "The number of write bytes to the volume", + "type": "integer" + }, + "wr_req": { + "description": "The number of write requests to the volume", + "type": "integer" + } + }, + "type": "object" + }, + "type": "array" + }, + "emulator": { + "description": "The binary emulator of the VM", + "type": "string" + }, + "failed_reason": { + "description": "Information about why the VM failed to start", + "type": "string" + }, + "features": { + "description": "The available features of the VM", + "items": { + "type": "string" + }, + "type": "array" + }, + "last_node": { + "description": "The last node the VM was assigned to before migrating", + "type": "string" + }, + "machine": { + "description": "The QEMU machine type of the VM", + "type": "string" + }, + "memory": { + "description": "The assigned RAM of the VM in MB", + "type": "integer" + }, + "memory_stats": { + "properties": { + "actual": { + "description": "The total active memory of the VM in kB", + "type": "integer" + }, + "available": { + "description": "The total amount of usable memory as seen by the domain in kB", + "type": "integer" + }, + "last_update": { + "description": "Timestamp of the last update of statistics, in seconds", + "type": "integer" + }, + "major_fault": { + "description": "The number of major page faults", + "type": "integer" + }, + "minor_fault": { + "description": "The number of minor page faults", + "type": "integer" + }, + "rss": { + "description": "The Resident Set Size of the process running the domain in kB", + "type": "integer" + }, + "swap_in": { + "description": "The amount of swapped in data in kB", + "type": "integer" + }, + "swap_out": { + "description": "The amount of swapped out data in kB", + "type": "integer" + }, + "unused": { + "description": "The amount of memory left completely unused by the system in kB", + "type": "integer" + }, + "usable": { + "description": "How much the balloon can be inflated without pushing the guest system to swap in kB", + "type": "integer" + } + }, + "type": "object" + }, + "migrated": { + "description": "Whether the VM has been migrated, either \"no\" or \"from \"", + "type": "string" + }, + "migration_method": { + "description": "The preferred migration method (live, shutdown, none)", + "type": "string" + }, + "name": { + "description": "The name of the VM", + "type": "string" + }, + "networks": { + "description": "The PVC networks attached to the VM", + "items": { + "properties": { + "mac": { + "description": "The MAC address of the VM network interface", + "type": "string" + }, + "model": { + "description": "The virtual network device model", + "type": "string" + }, + "rd_bytes": { + "description": "The number of read bytes on the interface", + "type": "integer" + }, + "rd_drops": { + "description": "The number of read drops on the interface", + "type": "integer" + }, + "rd_errors": { + "description": "The number of read errors on the interface", + "type": "integer" + }, + "rd_packets": { + "description": "The number of read packets on the interface", + "type": "integer" + }, + "source": { + "description": "The parent network bridge on the node", + "type": "string" + }, + "type": { + "description": "The PVC network type", + "type": "string" + }, + "vni": { + "description": "The VNI (PVC network) of the network bridge", + "type": "integer" + }, + "wr_bytes": { + "description": "The number of write bytes on the interface", + "type": "integer" + }, + "wr_drops": { + "description": "The number of write drops on the interface", + "type": "integer" + }, + "wr_errors": { + "description": "The number of write errors on the interface", + "type": "integer" + }, + "wr_packets": { + "description": "The number of write packets on the interface", + "type": "integer" + } + }, + "type": "object" + }, + "type": "array" + }, + "node": { + "description": "The node the VM is currently assigned to", + "type": "string" + }, + "node_autostart": { + "description": "Whether to autostart the VM when its node returns to ready domain state", + "type": "boolean" + }, + "node_limit": { + "description": "The node(s) the VM is permitted to be assigned to", + "items": { + "type": "string" + }, + "type": "array" + }, + "node_selector": { + "description": "The selector used to determine candidate nodes during migration; see 'target_selector' in the node daemon configuration reference", + "type": "string" + }, + "profile": { + "description": "The provisioner profile used to create the VM", + "type": "string" + }, + "state": { + "description": "The current state of the VM", + "type": "string" + }, + "tags": { + "description": "The tag(s) of the VM", + "items": { + "id": "VMTag", + "properties": { + "name": { + "description": "The name of the tag", + "type": "string" + }, + "protected": { + "description": "Whether the tag is protected or not", + "type": "boolean" + }, + "type": { + "description": "The type of the tag (user, system)", + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "type": { + "description": "The type of the VM", + "type": "string" + }, + "uuid": { + "description": "The UUID of the VM", + "type": "string" + }, + "vcpu": { + "description": "The assigned vCPUs of the VM", + "type": "integer" + }, + "vcpu_stats": { + "properties": { + "cpu_time": { + "description": "The active CPU time for all vCPUs", + "type": "integer" + }, + "system_time": { + "description": "vCPU system time", + "type": "integer" + }, + "user_time": { + "description": "vCPU user time", + "type": "integer" + } + }, + "type": "object" + }, + "vcpu_topology": { + "description": "The topology of the assigned vCPUs in Sockets/Cores/Threads format", + "type": "string" + }, + "vnc": { + "properties": { + "listen": { + "description": "The active VNC listen address or 'None'", + "type": "string" + }, + "port": { + "description": "The active VNC port or 'None'", + "type": "string" + } + }, + "type": "object" + }, + "xml": { + "description": "The raw Libvirt XML definition of the VM", + "type": "string" + } + }, + "type": "object" + }, + "volume": { + "properties": { + "name": { + "description": "The name of the volume", + "type": "string" + }, + "pool": { + "description": "The name of the pool containing the volume", + "type": "string" + }, + "stats": { + "properties": { + "access_timestamp": { + "description": "The volume access timestamp", + "type": "string" + }, + "block_name_prefix": { + "description": "The Ceph-internal block name prefix", + "type": "string" + }, + "create_timestamp": { + "description": "The volume creation timestamp", + "type": "string" + }, + "features": { + "items": { + "description": "The Ceph RBD feature", + "type": "string" + }, + "type": "array" + }, + "flags": { + "items": { + "description": "The Ceph RBD volume flags", + "type": "string" + }, + "type": "array" + }, + "format": { + "description": "The Ceph RBD volume format", + "type": "integer" + }, + "id": { + "description": "The Ceph volume ID", + "type": "string" + }, + "modify_timestamp": { + "description": "The volume modification timestamp", + "type": "string" + }, + "name": { + "description": "The name of the volume", + "type": "string" + }, + "object_size": { + "description": "The size of each object in bytes", + "type": "integer" + }, + "objects": { + "description": "The number of Ceph objects making up the volume", + "type": "integer" + }, + "op_features": { + "items": { + "description": "The Ceph RBD operational features", + "type": "string" + }, + "type": "array" + }, + "order": { + "description": "The Ceph volume order ID", + "type": "integer" + }, + "size": { + "description": "The size of the volume (human-readable values)", + "type": "string" + }, + "snapshot_count": { + "description": "The number of snapshots of the volume", + "type": "integer" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "host": "pvc.local:7370", + "info": { + "title": "PVC Client and Provisioner API", + "version": "1.0" + }, + "paths": { + "/api/v1/": { + "get": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/API-Version" + } + } + }, + "summary": "Return the PVC API version string", + "tags": [ + "root" + ] + } + }, + "/api/v1/backup": { + "get": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Cluster Data" + } + }, + "400": { + "description": "Bad request" + } + }, + "summary": "Back up the Zookeeper data of a cluster in JSON format", + "tags": [ + "root" + ] + } + }, + "/api/v1/initialize": { + "post": { + "description": "
If the 'overwrite' option is not True, the cluster will return 400 if the `/config/primary_node` key is found. If 'overwrite' is True, the existing cluster
data will be erased and new, empty data written in its place.

All node daemons should be stopped before running this command, and the API daemon started manually to avoid undefined behavior.", + "parameters": [ + { + "description": "A flag to enable or disable (default) overwriting existing data", + "in": "query", + "name": "overwrite", + "required": false, + "type": "bool" + }, + { + "description": "A confirmation string to ensure that the API consumer really means it", + "in": "query", + "name": "yes-i-really-mean-it", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "400": { + "description": "Bad request" + } + }, + "summary": "Initialize a new PVC cluster", + "tags": [ + "root" + ] + } + }, + "/api/v1/login": { + "post": { + "description": "", + "parameters": [ + { + "in": "query", + "name": "token", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "302": { + "description": "Authentication disabled" + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Log in to the PVC API with an authentication key", + "tags": [ + "root" + ] + } + }, + "/api/v1/logout": { + "post": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "302": { + "description": "Authentication disabled" + } + }, + "summary": "Log out of an existing PVC API session", + "tags": [ + "root" + ] + } + }, + "/api/v1/network": { + "get": { + "description": "", + "parameters": [ + { + "description": "A VNI or description search limit; fuzzy by default, use ^/$ to force exact matches", + "in": "query", + "name": "limit", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/network" + }, + "type": "array" + } + } + }, + "summary": "Return a list of networks in the cluster", + "tags": [ + "network" + ] + }, + "post": { + "description": "", + "parameters": [ + { + "description": "The VNI of the network", + "in": "query", + "name": "vni", + "required": true, + "type": "integer" + }, + { + "description": "The description of the network", + "in": "query", + "name": "description", + "required": true, + "type": "string" + }, + { + "description": "The type of network", + "enum": [ + "managed", + "bridged" + ], + "in": "query", + "name": "nettype", + "required": true, + "type": "string" + }, + { + "description": "The MTU of the network; defaults to the underlying interface MTU if not set", + "in": "query", + "name": "mtu", + "type": "integer" + }, + { + "description": "The DNS domain of the network (\"managed\" networks only)", + "in": "query", + "name": "domain", + "type": "string" + }, + { + "description": "The CSV list of DNS nameservers for network NS records (\"managed\" networks only)", + "in": "query", + "name": "name_servers", + "type": "string" + }, + { + "description": "The IPv4 network subnet of the network in CIDR format; IPv4 disabled if unspecified (\"managed\" networks only)", + "in": "query", + "name": "ip4_network", + "type": "string" + }, + { + "description": "The IPv4 default gateway address of the network (\"managed\" networks only)", + "in": "query", + "name": "ip4_gateway", + "type": "string" + }, + { + "description": "Whether to enable DHCPv4 for the network (\"managed\" networks only)", + "in": "query", + "name": "dhcp4", + "type": "boolean" + }, + { + "description": "The DHCPv4 pool start address of the network (\"managed\" networks only)", + "in": "query", + "name": "dhcp4_start", + "type": "string" + }, + { + "description": "The DHCPv4 pool end address of the network (\"managed\" networks only)", + "in": "query", + "name": "dhcp4_end", + "type": "string" + }, + { + "description": "The IPv6 network subnet of the network in CIDR format; IPv6 disabled if unspecified; DHCPv6 is always used in IPv6 managed networks (\"managed\" networks only)", + "in": "query", + "name": "ip6_network", + "type": "string" + }, + { + "description": "The IPv6 default gateway address of the network (\"managed\" networks only)", + "in": "query", + "name": "ip6_gateway", + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Create a new network", + "tags": [ + "network" + ] + } + }, + "/api/v1/network/{vni}": { + "delete": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Remove network {vni}", + "tags": [ + "network" + ] + }, + "get": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/network" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Return information about network {vni}", + "tags": [ + "network" + ] + }, + "post": { + "description": "", + "parameters": [ + { + "description": "The description of the network", + "in": "query", + "name": "description", + "required": true, + "type": "string" + }, + { + "description": "The type of network", + "enum": [ + "managed", + "bridged" + ], + "in": "query", + "name": "nettype", + "required": true, + "type": "string" + }, + { + "description": "The MTU of the network; defaults to the underlying interface MTU if not set", + "in": "query", + "name": "mtu", + "type": "integer" + }, + { + "description": "The DNS domain of the network (\"managed\" networks only)", + "in": "query", + "name": "domain", + "type": "string" + }, + { + "description": "The CSV list of DNS nameservers for network NS records (\"managed\" networks only)", + "in": "query", + "name": "name_servers", + "type": "string" + }, + { + "description": "The IPv4 network subnet of the network in CIDR format; IPv4 disabled if unspecified (\"managed\" networks only)", + "in": "query", + "name": "ip4_network", + "type": "string" + }, + { + "description": "The IPv4 default gateway address of the network (\"managed\" networks only)", + "in": "query", + "name": "ip4_gateway", + "type": "string" + }, + { + "description": "Whether to enable DHCPv4 for the network (\"managed\" networks only)", + "in": "query", + "name": "dhcp4", + "type": "boolean" + }, + { + "description": "The DHCPv4 pool start address of the network (\"managed\" networks only)", + "in": "query", + "name": "dhcp4_start", + "type": "string" + }, + { + "description": "The DHCPv4 pool end address of the network (\"managed\" networks only)", + "in": "query", + "name": "dhcp4_end", + "type": "string" + }, + { + "description": "The IPv6 network subnet of the network in CIDR format; IPv6 disabled if unspecified; DHCPv6 is always used in IPv6 managed networks (\"managed\" networks only)", + "in": "query", + "name": "ip6_network", + "type": "string" + }, + { + "description": "The IPv6 default gateway address of the network (\"managed\" networks only)", + "in": "query", + "name": "ip6_gateway", + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Create a new network {vni}", + "tags": [ + "network" + ] + }, + "put": { + "description": "Note: A network's type cannot be changed; the network must be removed and recreated as the new type", + "parameters": [ + { + "description": "The description of the network", + "in": "query", + "name": "description", + "type": "string" + }, + { + "description": "The MTU of the network", + "in": "query", + "name": "mtu", + "type": "integer" + }, + { + "description": "The DNS domain of the network (\"managed\" networks only)", + "in": "query", + "name": "domain", + "type": "string" + }, + { + "description": "The CSV list of DNS nameservers for network NS records (\"managed\" networks only)", + "in": "query", + "name": "name_servers", + "type": "string" + }, + { + "description": "The IPv4 network subnet of the network in CIDR format; IPv4 disabled if unspecified (\"managed\" networks only)", + "in": "query", + "name": "ip4_network", + "type": "string" + }, + { + "description": "The IPv4 default gateway address of the network (\"managed\" networks only)", + "in": "query", + "name": "ip4_gateway", + "type": "string" + }, + { + "description": "Whether to enable DHCPv4 for the network (\"managed\" networks only)", + "in": "query", + "name": "dhcp4", + "type": "boolean" + }, + { + "description": "The DHCPv4 pool start address of the network (\"managed\" networks only)", + "in": "query", + "name": "dhcp4_start", + "type": "string" + }, + { + "description": "The DHCPv4 pool end address of the network (\"managed\" networks only)", + "in": "query", + "name": "dhcp4_end", + "type": "string" + }, + { + "description": "The IPv6 network subnet of the network in CIDR format; IPv6 disabled if unspecified; DHCPv6 is always used in IPv6 managed networks (\"managed\" networks only)", + "in": "query", + "name": "ip6_network", + "type": "string" + }, + { + "description": "The IPv6 default gateway address of the network (\"managed\" networks only)", + "in": "query", + "name": "ip6_gateway", + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Update details of network {vni}", + "tags": [ + "network" + ] + } + }, + "/api/v1/network/{vni}/acl": { + "get": { + "description": "", + "parameters": [ + { + "description": "A description search limit; fuzzy by default, use ^/$ to force exact matches", + "in": "query", + "name": "limit", + "required": false, + "type": "string" + }, + { + "description": "The direction of rules to display; both directions shown if unspecified", + "in": "query", + "name": "direction", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/acl" + }, + "type": "array" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Return a list of ACLs in network {vni}", + "tags": [ + "network" + ] + }, + "post": { + "description": "", + "parameters": [ + { + "description": "A whitespace-free description/name for the ACL", + "in": "query", + "name": "description", + "required": true, + "type": "string" + }, + { + "description": "The direction of the ACL; defaults to \"in\" if unspecified", + "enum": [ + "in", + "out" + ], + "in": "query", + "name": "direction", + "required": false, + "type": "string" + }, + { + "description": "The order of the ACL in the chain; defaults to the end", + "in": "query", + "name": "order", + "type": "integer" + }, + { + "description": "The raw NFT firewall rule string", + "in": "query", + "name": "rule", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Create a new ACL in network {vni}", + "tags": [ + "network" + ] + } + }, + "/api/v1/network/{vni}/acl/{description}": { + "delete": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Delete ACL {description} in network {vni}", + "tags": [ + "network" + ] + }, + "get": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/acl" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Return information about ACL {description} in network {vni}", + "tags": [ + "network" + ] + }, + "post": { + "description": "", + "parameters": [ + { + "description": "The direction of the ACL; defaults to \"in\" if unspecified", + "enum": [ + "in", + "out" + ], + "in": "query", + "name": "direction", + "required": false, + "type": "string" + }, + { + "description": "The order of the ACL in the chain; defaults to the end", + "in": "query", + "name": "order", + "type": "integer" + }, + { + "description": "The raw NFT firewall rule string", + "in": "query", + "name": "rule", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Create a new ACL {description} in network {vni}", + "tags": [ + "network" + ] + } + }, + "/api/v1/network/{vni}/lease": { + "get": { + "description": "", + "parameters": [ + { + "description": "A MAC address search limit; fuzzy by default, use ^/$ to force exact matches", + "in": "query", + "name": "limit", + "required": false, + "type": "string" + }, + { + "default": false, + "description": "Whether to show only static leases", + "in": "query", + "name": "static", + "required": false, + "type": "boolean" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/lease" + }, + "type": "array" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Return a list of DHCP leases in network {vni}", + "tags": [ + "network" + ] + }, + "post": { + "description": "", + "parameters": [ + { + "description": "A MAC address for the lease", + "in": "query", + "name": "macaddress", + "required": false, + "type": "string" + }, + { + "description": "An IPv4 address for the lease", + "in": "query", + "name": "ipaddress", + "required": false, + "type": "string" + }, + { + "description": "An optional hostname for the lease", + "in": "query", + "name": "hostname", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Create a new static DHCP lease in network {vni}", + "tags": [ + "network" + ] + } + }, + "/api/v1/network/{vni}/lease/{mac}": { + "delete": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Delete static DHCP lease {mac}", + "tags": [ + "network" + ] + }, + "get": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/lease" + }, + "type": "array" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Return information about DHCP lease {mac} in network {vni}", + "tags": [ + "network" + ] + }, + "post": { + "description": "", + "parameters": [ + { + "description": "A MAC address for the lease", + "in": "query", + "name": "macaddress", + "required": false, + "type": "string" + }, + { + "description": "An IPv4 address for the lease", + "in": "query", + "name": "ipaddress", + "required": false, + "type": "string" + }, + { + "description": "An optional hostname for the lease", + "in": "query", + "name": "hostname", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Create a new static DHCP lease {mac} in network {vni}", + "tags": [ + "network" + ] + } + }, + "/api/v1/node": { + "get": { + "description": "", + "parameters": [ + { + "description": "A search limit in the name, tags, or an exact UUID; fuzzy by default, use ^/$ to force exact matches", + "in": "query", + "name": "limit", + "required": false, + "type": "string" + }, + { + "description": "Limit results to nodes in the specified daemon state", + "in": "query", + "name": "daemon_state", + "required": false, + "type": "string" + }, + { + "description": "Limit results to nodes in the specified coordinator state", + "in": "query", + "name": "coordinator_state", + "required": false, + "type": "string" + }, + { + "description": "Limit results to nodes in the specified domain state", + "in": "query", + "name": "domain_state", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/node" + }, + "type": "array" + } + } + }, + "summary": "Return a list of nodes in the cluster", + "tags": [ + "node" + ] + } + }, + "/api/v1/node/{node}": { + "get": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/node" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Return information about {node}", + "tags": [ + "node" + ] + } + }, + "/api/v1/node/{node}/coordinator-state": { + "get": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/NodeCoordinatorState" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Return the coordinator state of {node}", + "tags": [ + "node" + ] + }, + "post": { + "description": "", + "parameters": [ + { + "description": "The new coordinator state of the node", + "enum": [ + "primary", + "secondary" + ], + "in": "query", + "name": "action", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Set the coordinator state of {node}", + "tags": [ + "node" + ] + } + }, + "/api/v1/node/{node}/daemon-state": { + "get": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/NodeDaemonState" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Return the daemon state of {node}", + "tags": [ + "node" + ] + } + }, + "/api/v1/node/{node}/domain-state": { + "get": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/NodeDomainState" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Return the domain state of {node}", + "tags": [ + "node" + ] + }, + "post": { + "description": "", + "parameters": [ + { + "description": "The new domain state of the node", + "enum": [ + "flush", + "ready" + ], + "in": "query", + "name": "action", + "required": true, + "type": "string" + }, + { + "description": "Whether to block waiting for the full flush/ready state", + "in": "query", + "name": "wait", + "type": "boolean" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Set the domain state of {node}", + "tags": [ + "node" + ] + } + }, + "/api/v1/node/{node}/log": { + "get": { + "description": "", + "parameters": [ + { + "description": "The number of lines to retrieve", + "in": "query", + "name": "lines", + "required": false, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/NodeLog" + } + }, + "404": { + "description": "Node not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Return the recent logs of {node}", + "tags": [ + "node" + ] + } + }, + "/api/v1/provisioner/create": { + "post": { + "description": "Note: Starts a background job in the pvc-provisioner-worker Celery worker while returning a task ID; the task ID can be used to query the \"GET /provisioner/status/\" endpoint for the job status", + "parameters": [ + { + "description": "Virtual machine name", + "in": "query", + "name": "name", + "required": true, + "type": "string" + }, + { + "description": "Profile name", + "in": "query", + "name": "profile", + "required": true, + "type": "string" + }, + { + "description": "Whether to define the VM on the cluster during provisioning", + "in": "query", + "name": "define_vm", + "required": false, + "type": "boolean" + }, + { + "description": "Whether to start the VM after provisioning", + "in": "query", + "name": "start_vm", + "required": false, + "type": "boolean" + }, + { + "description": "Script install() function keywork argument in \"arg=data\" format; may be specified multiple times to add multiple arguments", + "in": "query", + "name": "arg", + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "properties": { + "task_id": { + "description": "Task ID for the provisioner Celery worker", + "type": "string" + } + }, + "type": "object" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Create a new virtual machine", + "tags": [ + "provisioner" + ] + } + }, + "/api/v1/provisioner/ova": { + "get": { + "description": "", + "parameters": [ + { + "description": "An OVA name search limit; fuzzy by default, use ^/$ to force exact matches", + "in": "query", + "name": "limit", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/ova" + }, + "type": "list" + } + } + }, + "summary": "Return a list of OVA sources", + "tags": [ + "provisioner" + ] + }, + "post": { + "description": "
The API client is responsible for determining and setting the ova_size value, as this value cannot be determined dynamically before the upload proceeds.", + "parameters": [ + { + "description": "Storage pool name", + "in": "query", + "name": "pool", + "required": true, + "type": "string" + }, + { + "description": "OVA name on the cluster (usually identical to the OVA file name)", + "in": "query", + "name": "name", + "required": true, + "type": "string" + }, + { + "description": "Size of the OVA file in bytes", + "in": "query", + "name": "ova_size", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Upload an OVA image to the cluster", + "tags": [ + "provisioner" + ] + } + }, + "/api/v1/provisioner/ova/{ova}": { + "delete": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Remove ova {ova}", + "tags": [ + "provisioner" + ] + }, + "get": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/ova" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Return information about OVA image {ova}", + "tags": [ + "provisioner" + ] + }, + "post": { + "description": "
The API client is responsible for determining and setting the ova_size value, as this value cannot be determined dynamically before the upload proceeds.", + "parameters": [ + { + "description": "Storage pool name", + "in": "query", + "name": "pool", + "required": true, + "type": "string" + }, + { + "description": "Size of the OVA file in bytes", + "in": "query", + "name": "ova_size", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Upload an OVA image to the cluster", + "tags": [ + "provisioner" + ] + } + }, + "/api/v1/provisioner/profile": { + "get": { + "description": "", + "parameters": [ + { + "description": "A profile name search limit; fuzzy by default, use ^/$ to force exact matches", + "in": "query", + "name": "limit", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/profile" + }, + "type": "list" + } + } + }, + "summary": "Return a list of profiles", + "tags": [ + "provisioner" + ] + }, + "post": { + "description": "", + "parameters": [ + { + "description": "Profile name", + "in": "query", + "name": "name", + "required": true, + "type": "string" + }, + { + "description": "Profile type", + "enum": [ + "provisioner", + "ova" + ], + "in": "query", + "name": "profile_type", + "required": true, + "type": "string" + }, + { + "description": "Script name", + "in": "query", + "name": "script", + "required": true, + "type": "string" + }, + { + "description": "System template name", + "in": "query", + "name": "system_template", + "required": true, + "type": "string" + }, + { + "description": "Network template name", + "in": "query", + "name": "network_template", + "required": false, + "type": "string" + }, + { + "description": "Storage template name", + "in": "query", + "name": "storage_template", + "required": false, + "type": "string" + }, + { + "description": "Userdata template name", + "in": "query", + "name": "userdata", + "required": false, + "type": "string" + }, + { + "description": "OVA image source", + "in": "query", + "name": "ova", + "required": false, + "type": "string" + }, + { + "description": "Script install() function keywork argument in \"arg=data\" format; may be specified multiple times to add multiple arguments", + "in": "query", + "name": "arg", + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Create a new profile", + "tags": [ + "provisioner" + ] + } + }, + "/api/v1/provisioner/profile/{profile}": { + "delete": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Remove profile {profile}", + "tags": [ + "provisioner" + ] + }, + "get": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/profile" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Return information about profile {profile}", + "tags": [ + "provisioner" + ] + }, + "post": { + "description": "", + "parameters": [ + { + "description": "Profile type", + "enum": [ + "provisioner", + "ova" + ], + "in": "query", + "name": "profile_type", + "required": true, + "type": "string" + }, + { + "description": "Script name", + "in": "query", + "name": "script", + "required": true, + "type": "string" + }, + { + "description": "System template name", + "in": "query", + "name": "system_template", + "required": true, + "type": "string" + }, + { + "description": "Network template name", + "in": "query", + "name": "network_template", + "required": false, + "type": "string" + }, + { + "description": "Storage template name", + "in": "query", + "name": "storage_template", + "required": false, + "type": "string" + }, + { + "description": "Userdata template name", + "in": "query", + "name": "userdata", + "required": false, + "type": "string" + }, + { + "description": "OVA image source", + "in": "query", + "name": "ova", + "required": false, + "type": "string" + }, + { + "description": "Script install() function keywork argument in \"arg=data\" format; may be specified multiple times to add multiple arguments", + "in": "query", + "name": "arg", + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Create a new profile {profile}", + "tags": [ + "provisioner" + ] + }, + "put": { + "description": "", + "parameters": [ + { + "description": "Script name", + "in": "query", + "name": "script", + "required": false, + "type": "string" + }, + { + "description": "System template name", + "in": "query", + "name": "system_template", + "required": false, + "type": "string" + }, + { + "description": "Network template name", + "in": "query", + "name": "network_template", + "required": false, + "type": "string" + }, + { + "description": "Storage template name", + "in": "query", + "name": "storage_template", + "required": false, + "type": "string" + }, + { + "description": "Userdata template name", + "in": "query", + "name": "userdata", + "required": false, + "type": "string" + }, + { + "description": "Script install() function keywork argument in \"arg=data\" format; may be specified multiple times to add multiple arguments", + "in": "query", + "name": "arg", + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Modify profile {profile}", + "tags": [ + "provisioner" + ] + } + }, + "/api/v1/provisioner/script": { + "get": { + "description": "", + "parameters": [ + { + "description": "A script name search limit; fuzzy by default, use ^/$ to force exact matches", + "in": "query", + "name": "limit", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/script" + }, + "type": "list" + } + } + }, + "summary": "Return a list of scripts", + "tags": [ + "provisioner" + ] + }, + "post": { + "description": "", + "parameters": [ + { + "description": "Script name", + "in": "query", + "name": "name", + "required": true, + "type": "string" + }, + { + "description": "Raw Python script document", + "in": "query", + "name": "data", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Create a new script", + "tags": [ + "provisioner" + ] + } + }, + "/api/v1/provisioner/script/{script}": { + "delete": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Remove script {script}", + "tags": [ + "provisioner" + ] + }, + "get": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/script" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Return information about script {script}", + "tags": [ + "provisioner" + ] + }, + "post": { + "description": "", + "parameters": [ + { + "description": "Raw Python script document", + "in": "query", + "name": "data", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Create a new script {script}", + "tags": [ + "provisioner" + ] + }, + "put": { + "description": "", + "parameters": [ + { + "description": "Raw Python script document", + "in": "query", + "name": "data", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Update script {script}", + "tags": [ + "provisioner" + ] + } + }, + "/api/v1/provisioner/status": { + "get": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "properties": { + "active": { + "description": "Celery app.control.inspect active tasks", + "type": "object" + }, + "reserved": { + "description": "Celery app.control.inspect reserved tasks", + "type": "object" + }, + "scheduled": { + "description": "Celery app.control.inspect scheduled tasks", + "type": "object" + } + }, + "type": "object" + } + } + }, + "summary": "View status of provisioner Celery queue", + "tags": [ + "provisioner" + ] + } + }, + "/api/v1/provisioner/status/{task_id}": { + "get": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "properties": { + "current": { + "description": "Current steps completed", + "type": "integer" + }, + "state": { + "description": "Current job state", + "type": "string" + }, + "status": { + "description": "Status details about job", + "type": "string" + }, + "total": { + "description": "Total number of steps", + "type": "integer" + } + }, + "type": "object" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "View status of a provisioner Celery worker job {task_id}", + "tags": [ + "provisioner" + ] + } + }, + "/api/v1/provisioner/template": { + "get": { + "description": "", + "parameters": [ + { + "description": "A template name search limit; fuzzy by default, use ^/$ to force exact matches", + "in": "query", + "name": "limit", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/all-templates" + } + } + }, + "summary": "Return a list of all templates", + "tags": [ + "provisioner / template" + ] + } + }, + "/api/v1/provisioner/template/network": { + "get": { + "description": "", + "parameters": [ + { + "description": "A template name search limit; fuzzy by default, use ^/$ to force exact matches", + "in": "query", + "name": "limit", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/network-template" + }, + "type": "list" + } + } + }, + "summary": "Return a list of network templates", + "tags": [ + "provisioner / template" + ] + }, + "post": { + "description": "", + "parameters": [ + { + "description": "Template name", + "in": "query", + "name": "name", + "required": true, + "type": "string" + }, + { + "description": "MAC address template", + "in": "query", + "name": "mac_template", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Create a new network template", + "tags": [ + "provisioner / template" + ] + } + }, + "/api/v1/provisioner/template/network/{template}": { + "delete": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Remove network template {template}", + "tags": [ + "provisioner / template" + ] + }, + "get": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/network-template" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Return information about network template {template}", + "tags": [ + "provisioner / template" + ] + }, + "post": { + "description": "", + "parameters": [ + { + "description": "MAC address template", + "in": "query", + "name": "mac_template", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Create a new network template {template}", + "tags": [ + "provisioner / template" + ] + } + }, + "/api/v1/provisioner/template/network/{template}/net": { + "get": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/network-template-net" + }, + "type": "list" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Return a list of networks in network template {template}", + "tags": [ + "provisioner / template" + ] + }, + "post": { + "description": "", + "parameters": [ + { + "description": "PVC network VNI", + "in": "query", + "name": "vni", + "required": false, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Create a new network in network template {template}", + "tags": [ + "provisioner / template" + ] + } + }, + "/api/v1/provisioner/template/network/{template}/net/{vni}": { + "delete": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Remove network {vni} from network template {template}", + "tags": [ + "provisioner / template" + ] + }, + "get": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/network-template-net" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Return information about network {vni} in network template {template}", + "tags": [ + "provisioner / template" + ] + }, + "post": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Create a new network {vni} in network template {template}", + "tags": [ + "provisioner / template" + ] + } + }, + "/api/v1/provisioner/template/storage": { + "get": { + "description": "", + "parameters": [ + { + "description": "A template name search limit; fuzzy by default, use ^/$ to force exact matches", + "in": "query", + "name": "limit", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/storage-template" + }, + "type": "list" + } + } + }, + "summary": "Return a list of storage templates", + "tags": [ + "provisioner / template" + ] + }, + "post": { + "description": "", + "parameters": [ + { + "description": "Template name", + "in": "query", + "name": "name", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Create a new storage template", + "tags": [ + "provisioner / template" + ] + } + }, + "/api/v1/provisioner/template/storage/{template}": { + "delete": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Remove storage template {template}", + "tags": [ + "provisioner / template" + ] + }, + "get": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/storage-template" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Return information about storage template {template}", + "tags": [ + "provisioner / template" + ] + }, + "post": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Create a new storage template {template}", + "tags": [ + "provisioner / template" + ] + } + }, + "/api/v1/provisioner/template/storage/{template}/disk": { + "get": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/storage-template-disk" + }, + "type": "list" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Return a list of disks in network template {template}", + "tags": [ + "provisioner / template" + ] + }, + "post": { + "description": "", + "parameters": [ + { + "description": "Disk identifier in \"sdX\"/\"vdX\" format (e.g. \"sda\", \"vdb\", etc.)", + "in": "query", + "name": "disk_id", + "required": true, + "type": "string" + }, + { + "description": "ceph storage pool for disk", + "in": "query", + "name": "pool", + "required": true, + "type": "string" + }, + { + "description": "Source storage volume; not compatible with other options", + "in": "query", + "name": "source_volume", + "required": false, + "type": "string" + }, + { + "description": "Disk size in GB; not compatible with source_volume", + "in": "query", + "name": "disk_size", + "required": false, + "type": "integer" + }, + { + "description": "Filesystem for disk; not compatible with source_volume", + "in": "query", + "name": "filesystem", + "required": false, + "type": "string" + }, + { + "description": "Filesystem mkfs argument in \"-X=foo\" format; may be specified multiple times to add multiple arguments", + "in": "query", + "name": "filesystem_arg", + "required": false, + "type": "string" + }, + { + "description": "In-VM mountpoint for disk; not compatible with source_volume", + "in": "query", + "name": "mountpoint", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Create a new disk in storage template {template}", + "tags": [ + "provisioner / template" + ] + } + }, + "/api/v1/provisioner/template/storage/{template}/disk/{disk_id}": { + "delete": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Message" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Remove disk {disk_id} from storage template {template}", + "tags": [ + "provisioner / template" + ] + }, + "get": { + "description": "", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/storage-template-disk" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Return information about disk {disk_id} in storage template {template}", + "tags": [ + "provisioner / template" + ] + }, + "post": { + "description": "Alternative to \"POST /provisioner/template/storage/