How Resource Backings Manage Storage and Network Resources in Avocado-VT
Resource backings in the Avocado-VT framework isolate virtualization assets from infrastructure specifics by orchestrating pool connections for shared resources and node-local backing instances for concrete operations, exposing a unified command API through the ResourceBackingManager singleton.
The avocado-framework/avocado-vt virtualization testing framework delegates the handling of storage volumes and network interfaces to a specialized resource-backing subsystem. This architecture allows worker nodes to provision local files, NFS mounts, and Linux bridge ports through a consistent interface while the controller node manages logical resource requirements. By decoupling pool connectivity from resource lifecycle operations, the framework supports diverse backend technologies without modifying high-level test logic.
Architecture of the Resource Backing Subsystem
The resource-backing implementation follows a four-layer design that separates connection management, resource operations, orchestration, and class discovery.
| Layer | Core Abstraction | Key Implementation Files |
|---|---|---|
| Pool Connection | ResourcePoolConnection (abstract) |
resource_backings/pool_connection.py |
| Resource Backing | ResourceBacking (abstract) |
resource_backings/backing.py |
| Orchestration | _ResourceBackingManager (singleton rb_mgr) |
resource_backing_manager.py |
| Factory Registry | Type-to-class mapping functions | resource_backings/__init__.py |
ResourcePoolConnection: Linking Workers to Shared Pools
The ResourcePoolConnection abstract base class in resource_backings/pool_connection.py establishes the link between a worker node and a shared resource pool. Concrete implementations include DirPoolConnection for local filesystem directories, NfsPoolConnection for remote NFS exports, and TapNetworkConnection for Linux bridge networking. Each connection class implements an open() method that prepares the pool for use—such as mounting an NFS share or validating a bridge configuration—and maintains a POOL_TYPE identifier for factory registration.
ResourceBacking: Node-Local Resource Operations
Defined in resource_backings/backing.py, the ResourceBacking abstract class represents the node-local object that executes real operations on a single resource instance. Subclasses like DirVolumeBacking, NfsVolumeBacking, and TapPortBacking implement the abstract methods allocate_resource, release_resource, clone_resource, sync_resource_info, and is_resource_allocated. Each backing maintains a _handlers dictionary mapping commands ("allocate", "release", "sync") to their implementation methods, ensuring a uniform API regardless of whether the resource is a storage volume or network port.
ResourceBackingManager: Orchestration and State Persistence
The _ResourceBackingManager class, instantiated as the singleton rb_mgr in resource_backing_manager.py, serves as the central orchestrator. It maintains two critical registries: self._pool_connections (mapping pool UUIDs to connection objects) and self._backings (mapping backing UUIDs to resource instances). The manager persists its state to BACKING_MGR_ENV_FILENAME, enabling worker nodes to reload existing pool connections after a restart via the internal _load() method. It exposes a thin RPC-style API—including create_pool_connection, create_backing_object, and update_resource_by_backing—that the controller node invokes to drive resource lifecycles.
Factory Registry: Dynamic Class Resolution
The resource_backings/__init__.py file implements a factory registry that decouples type strings from class implementations. The get_pool_connection_class(pool_type) function maps pool type strings (e.g., "filesystem", "nfs", "linux_bridge") to their respective connection classes. Similarly, get_resource_backing_class(pool_type, resource_type) resolves the concrete backing class based on both the pool technology and resource category. This registration pattern enables the dynamic instantiation of the correct objects without hardcoding class references in the manager.
Resource Lifecycle Management
The ResourceBackingManager coordinates the complete lifecycle of virtualization resources through five distinct phases, translating high-level controller commands into concrete node operations.
1. Pool Connection Creation
The controller initiates resource availability by submitting a pool configuration containing a type, UUID, and specifications. The manager looks up the connection class using get_pool_connection_class(pool_type), instantiates it, and invokes open().
from avocado_vt.agent.managers.resource_backings.resource_backing_manager import rb_mgr
fs_pool_cfg = {
"meta": {"uuid": "pool-001", "type": "filesystem"},
"spec": {"path": "test_data/fs_root"}
}
rc, out = rb_mgr.create_pool_connection(fs_pool_cfg)
The connection object is stored in self._pool_connections keyed by its UUID, and the state is serialized to BACKING_MGR_ENV_FILENAME.
2. Backing Object Instantiation
When binding a specific resource to a pool, the manager retrieves the active pool connection and resolves the backing class via get_resource_backing_class(). It then creates the backing instance and registers it in self._backings with a generated UUID.
vol_cfg = {
"meta": {"uuid": "vol-123", "type": "volume", "pool": "pool-001"},
"spec": {"size": "1G"}
}
rc, out = rb_mgr.create_backing_object(vol_cfg)
backing_id = out["out"]["backing"]
3. Resource Allocation
The test triggers resource materialization by sending an allocate command. The manager forwards this to the backing's handler, which executes technology-specific provisioning—such as creating a file in a directory or attaching a TAP interface to a bridge.
rc, out = rb_mgr.update_resource_by_backing(backing_id, "allocate", {})
For TapPortBacking, this invokes tap.open_tap() and bridge.add_to_bridge(), while DirVolumeBacking creates a sparse file of the requested size in the pool's root directory.
4. State Synchronization and Querying
Workers or controllers can query current resource attributes through the manager's get_resource_info_by_backing() method. Setting verbose=True includes the parent pool configuration in the response.
info = rb_mgr.get_resource_info_by_backing(backing_id, verbose=True)
The backing's sync_resource_info() method refreshes metadata such as allocation status, file paths, or interface names, ensuring the returned state reflects the actual system condition.
5. Teardown and Cleanup
Resource cleanup follows the reverse sequence. destroy_backing_object(backing_id) removes the specific resource backing after releasing its assets, while destroy_pool_connection(pool_id) closes the pool link and removes it from the persistent state store.
rb_mgr.destroy_backing_object(backing_id)
rb_mgr.destroy_pool_connection("pool-001")
Storage and Network Backing Implementations
The backing architecture accommodates diverse infrastructure technologies through specialized subclasses that inherit common behaviors from ResourceBacking.
Filesystem and NFS Storage Backings
DirVolumeBacking (paired with DirPoolConnection) manages file-based volumes within a local directory. The pool connection's open() method initializes the root directory (init_dir), while the backing resolves URIs relative to this root during allocation. NfsVolumeBacking operates identically but on an NFS mount point established by NfsPoolConnection, allowing tests to use remote storage transparently.
TAP Network Backings
TapPortBacking (using TapNetworkConnection with POOL_TYPE="linux_bridge") handles virtual network interfaces. The connection stores bridge metadata, while the backing's allocate_resource() creates the TAP device, attaches it to the specified bridge, and stores the file descriptor and interface name. The release_resource() method detaches the interface and brings it down, ensuring network isolation between tests.
Practical Implementation Examples
Creating a Filesystem Pool and Volume
from avocado_vt.agent.managers.resource_backings.resource_backing_manager import rb_mgr
# Configure and open a local filesystem pool
fs_pool_cfg = {
"meta": {"uuid": "pool-001", "type": "filesystem"},
"spec": {"path": "test_data/fs_root"}
}
rc, out = rb_mgr.create_pool_connection(fs_pool_cfg)
assert rc == 0, f"Pool creation failed: {out}"
# Create a volume backing in the pool
vol_cfg = {
"meta": {"uuid": "vol-123", "type": "volume", "pool": "pool-001"},
"spec": {"size": "1G"}
}
rc, out = rb_mgr.create_backing_object(vol_cfg)
assert rc == 0, f"Backing creation failed: {out}"
backing_id = out["out"]["backing"]
# Allocate the storage (creates the file)
rc, out = rb_mgr.update_resource_by_backing(backing_id, "allocate", {})
print(out["out"]["spec"]) # Contains path and allocation status
Provisioning a TAP Network Port
# Configure a Linux bridge pool
bridge_cfg = {
"meta": {"uuid": "net-pool-01", "type": "linux_bridge"},
"spec": {"bridge_name": "br0"}
}
rb_mgr.create_pool_connection(bridge_cfg)
# Create a TAP port backing
tap_cfg = {
"meta": {"uuid": "tap-001", "type": "tap", "pool": "net-pool-01"},
"spec": {}
}
rc, out = rb_mgr.create_backing_object(tap_cfg)
tap_backing_id = out["out"]["backing"]
# Allocate creates the interface and attaches to bridge
rc, out = rb_mgr.update_resource_by_backing(tap_backing_id, "allocate", {})
print(out["out"]["spec"]["ifname"]) # e.g., "tap_abcd1234"
Querying Resource State
# Get verbose info including pool configuration
info = rb_mgr.get_resource_info_by_backing(tap_backing_id, verbose=True)
print(info["meta"]["allocated"]) # True
print(info["spec"]["ifname"]) # Interface name
Cleanup Operations
# Release specific resources
rb_mgr.destroy_backing_object(tap_backing_id)
rb_mgr.destroy_backing_object(backing_id)
# Close pool connections
rb_mgr.destroy_pool_connection("net-pool-01")
rb_mgr.destroy_pool_connection("pool-001")
Summary
- Resource backings abstract virtualization assets into node-local objects that implement concrete create, release, and sync operations.
- The
ResourceBackingManagersingleton orchestrates pool connections and backings while persisting state toBACKING_MGR_ENV_FILENAMEfor recovery. - Factory registries in
resource_backings/__init__.pyenable dynamic instantiation of connection and backing classes based on type strings. - Storage resources use volume backings (
DirVolumeBacking,NfsVolumeBacking) that manage files within pool-specific directories. - Network resources use port backings (
TapPortBacking) that manipulate system network interfaces and bridge memberships. - The command-handler pattern (
_handlersdictionary) provides a consistent RPC-style API for allocate, release, and sync operations across all resource types.
Frequently Asked Questions
What is the difference between a pool connection and a resource backing?
A pool connection (ResourcePoolConnection subclass) establishes and maintains the link to a shared resource pool—such as mounting an NFS export or referencing a Linux bridge—while a resource backing (ResourceBacking subclass) performs node-local operations on individual instances within that pool, such as creating a specific volume file or configuring a TAP interface. The connection manages the "where" (the pool), while the backing manages the "what" (the specific resource).
How does Avocado-VT persist resource state across worker restarts?
The ResourceBackingManager serializes the internal registries of pool connections and backing objects to a file defined by BACKING_MGR_ENV_FILENAME whenever state changes occur. Upon initialization, the manager calls its internal _load() method to deserialize this state, allowing workers that restart or run collocated controllers to recover existing pool connections without reconfiguration.
Can a single worker node manage multiple pool types simultaneously?
Yes. The ResourceBackingManager stores pool connections in the self._pool_connections dictionary keyed by UUID, allowing simultaneous connections to filesystem directories, NFS shares, and Linux bridges. Each backing object references its parent pool via the pool UUID, enabling heterogeneous resource allocation from a single worker.
How do I implement a custom resource backing for a new storage type?
To add support for a new storage technology, create a subclass of ResourcePoolConnection implementing open() and other lifecycle methods, and a subclass of ResourceBacking implementing the abstract methods allocate_resource, release_resource, clone_resource, sync_resource_info, and is_resource_allocated. Register both classes in resource_backings/__init__.py using the factory functions get_pool_connection_class() and get_resource_backing_class() to map your custom type strings to these implementations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →