Skip to content

oxidecomputer/scuffle

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

79 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

scuffle

scuffle

scuffle provides an illumos-only wrapper around libscf, focused on interactions with property groups and properties of existing services, instances, and snapshots. Understanding SMF Properties is highly recommended background reading.

There are large swathes of libscf that are currently not available via scuffle due to its focus on properties, including:

  • The ability to create or delete services
  • Interacting with running instances or services in any way other than refreshing instances
  • All functionality related to snaplevels
  • All functionality related to templates

Examples

Reading all properties from a specific property group in a service instance’s running snapshot:

fn read_from_snapshot(
    service_name: &str,
    instance_name: &str,
    property_group_name: &str,
) -> anyhow::Result<BTreeMap<String, Vec<Value>>> {
    // Get a handle to scf and the local scope.
    let scf = Scf::connect_current_zone()?;
    let scope = scf.scope_local()?;

    // Look up the property group within our snapshot by stepping through
    // each level.
    let Some(service) = scope.service(service_name)? else {
        bail!("service {service_name} not found");
    };
    let Some(instance) = service.instance(instance_name)? else {
        bail!("instance {instance_name} not found within {}", service.fmri());
    };
    let Some(snapshot) = instance.snapshot("running")? else {
        bail!("no running snapshot found for {}", instance.fmri());
    };
    let Some(pg) = snapshot.property_group_composed(property_group_name)? else {
        bail!(
            "property group {property_group_name} not found for {}",
            instance.fmri(),
        );
    };

    let mut all_properties = BTreeMap::new();
    for property in pg.properties()? {
        let property = property?;
        let values = property.values()?.collect::<Result<_, _>>()?;
        all_properties.insert(property.name().to_string(), values);
    }
    Ok(all_properties)
}

Adding a new property to an existing property group of a service instance:

fn add_new_property(
    service_name: &str,
    instance_name: &str,
    property_group_name: &str,
    property_name: &str,
    value: ValueRef<'_>,
) -> anyhow::Result<()> {
    // Get a handle to scf and the local scope.
    let scf = Scf::connect_current_zone()?;
    let scope = scf.scope_local()?;

    // Look up the property group within our instance by stepping through
    // each level.
    let Some(service) = scope.service(service_name)? else {
        bail!("service {service_name} not found");
    };
    let Some(instance) = service.instance(instance_name)? else {
        bail!("instance {instance_name} not found within {}", service.fmri());
    };
    let Some(mut pg) = instance.property_group_direct(property_group_name)? else {
        bail!(
            "property group {property_group_name} not found for {}",
            instance.fmri(),
        );
    };

    // Open a transaction on the property group.
    let tx = pg.transaction()?;

    // Start the transaction. This takes a snapshot of the property group's
    // current version; if it's modified by someone else between this point
    // and our attempt to `commit()` below, we'll get an `OutOfDate` result.
    let mut tx = tx.start()?;

    // Add an entry to the transaction to create the new property.
    tx.property_new(property_name, value)?;

    // Commit the transaction.
    match tx.commit()? {
        TransactionCommitResult::Success(_committed_tx) => Ok(()),
        TransactionCommitResult::OutOfDate(_reset_tx) => {
            // We'll return an error for this example, but real code may
            // want to call `pg.update()` and try again.
            bail!("property group concurrently modified");
        }
    }
}

See the examples/ directory for more complete examples.

Error types

scuffle’s errors aim to provide extensive context; e.g., an error that occurs while operating on an instance will include that instance’s FMRI. These error types make extensive use of source errors as discussed in Defining Error Types and Logging Errors. It is critical that printing or logging of these error types walk the entire error chain as discussed in that document, or the underlying error(s) will not be emitted.

The examples above use anyhow to allow easy ?-propagation despite the multiple errors involved. scuffle does not currently provide a catch-all error type of its own.

Features

scuffle has three optional Cargo features:

  • Enabling the daft feature adds implementations of daft::Diffable to Value, ValueRef, and ValueKind.
  • Enabling the smf-by-instance feature adds several Instance::smf_* methods for controlling the SMF state of an instance, but requires a libscf that includes recently-stabilized APIs.
  • Enabling the testing feature adds types to support writing tests that interact with SMF without needing to modify system-level SMF services / instances / properties; see “Testing Support” below.

Stability

scuffle makes use of some non-public interfaces. Specifically:

  • [Scf::connect_zone()] uses an undocumented SCF handle decoration to connect to svc.configd inside the specified zone. This matches how svcadm and svcprop implement their -z zone flags.
  • If the smf-by-instance Cargo feature is not enabled, [Instance::smf_refresh()] uses a non-public function defined by libscf_priv.h (_smf_refresh_instance_i()).
  • If the testing feature is enabled, it uses several other non-public interfaces; see “Testing Support” below.

Testing Support

If the testing feature is enabled, scuffle exports an isolated::IsolatedConfigd type that can run an instance of svc.configd inside a temporary directory. After creating an instance of this type, tests can connect to it via [Scf::connect_isolated()], and then freely read and write properties within that instance without touching the real system’s svc.configd and without the permissions normally required to do that.

This comes with several caveats:

  • IsolatedConfigd takes advantage of undocumented and uncommitted interfaces in at least svccfg, svc.configd, and libscf itself. These may break in the future.
  • Refreshing instances inside an IsolatedConfigd via libscf does not work. scuffle works around this by refreshing instances via svccfg pointed at the isolated svc.configd, but this is a divergence from what production code will do for refresh.
  • Non-persistent property groups do not work inside IsolatedConfigd.

scuffle uses IsolatedConfigd for its own tests, and therefore does not have test coverage on features that interact with these restrictions.

About

Rust library for interacting with `libscf`

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages