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 ResourceBackingManager singleton orchestrates pool connections and backings while persisting state to BACKING_MGR_ENV_FILENAME for recovery.
  • Factory registries in resource_backings/__init__.py enable 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 (_handlers dictionary) 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →