blockdevice

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 3, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

README

Block Device Package

The blockdevice package provides utilities for interacting with raw block devices in Go. This package is specifically designed for SBD (Storage-Based Death) operations that require direct, synchronous access to block devices for reliable fencing in Kubernetes clusters.

Features

  • Direct Block Device Access: Opens raw block devices with O_RDWR and O_SYNC flags for immediate writes
  • Positioned I/O: Implements io.ReaderAt and io.WriterAt interfaces for concurrent, positioned operations
  • Synchronous Operations: Ensures data integrity with explicit sync operations
  • Thread-Safe: Safe for concurrent use across multiple goroutines
  • Comprehensive Error Handling: Detailed error messages for debugging and monitoring

API

Types
Device

Represents a raw block device that can be read from and written to.

type Device struct {
    // Contains filtered or unexported fields
}
Functions
Open(path string) (*Device, error)

Opens a raw block device at the specified path for read/write operations.

Parameters:

  • path: The filesystem path to the block device (e.g., "/dev/sdb1")

Returns:

  • *Device: A new Device instance if successful
  • error: An error if the device cannot be opened
(*Device) ReadAt(p []byte, off int64) (n int, err error)

Reads len(p) bytes from the device starting at byte offset off. Implements io.ReaderAt.

Parameters:

  • p: The buffer to read data into
  • off: The byte offset from the beginning of the device to start reading

Returns:

  • n: The number of bytes actually read
  • err: An error if the read operation fails
(*Device) WriteAt(p []byte, off int64) (n int, err error)

Writes len(p) bytes to the device starting at byte offset off. Implements io.WriterAt.

Parameters:

  • p: The data to write to the device
  • off: The byte offset from the beginning of the device to start writing

Returns:

  • n: The number of bytes actually written
  • err: An error if the write operation fails
(*Device) Sync() error

Flushes any buffered writes to the underlying storage device.

Returns:

  • error: An error if the sync operation fails
(*Device) Close() error

Closes the block device and releases any associated resources.

Returns:

  • error: An error if the close operation fails
(*Device) Path() string

Returns the filesystem path of the block device.

(*Device) IsClosed() bool

Returns true if the device has been closed.

(*Device) String() string

Returns a string representation of the Device for debugging purposes.

Usage Examples

Basic Usage
package main

import (
    "fmt"
    "log"

    "github.com/medik8s/sbd-operator/pkg/blockdevice"
)

func main() {
    // Open a block device
    device, err := blockdevice.Open("/dev/sdb1")
    if err != nil {
        log.Fatalf("Failed to open device: %v", err)
    }
    defer device.Close()

    // Write some data
    data := []byte("SBD message data")
    n, err := device.WriteAt(data, 0)
    if err != nil {
        log.Fatalf("Failed to write: %v", err)
    }
    fmt.Printf("Wrote %d bytes\n", n)

    // Ensure data is flushed to disk
    if err := device.Sync(); err != nil {
        log.Fatalf("Failed to sync: %v", err)
    }

    // Read back the data
    buf := make([]byte, len(data))
    n, err = device.ReadAt(buf, 0)
    if err != nil {
        log.Fatalf("Failed to read: %v", err)
    }
    fmt.Printf("Read %d bytes: %s\n", n, string(buf[:n]))
}
SBD Message Operations
func writeSBDMessage(devicePath string, message []byte, offset int64) error {
    device, err := blockdevice.Open(devicePath)
    if err != nil {
        return fmt.Errorf("failed to open SBD device: %w", err)
    }
    defer device.Close()

    // Write the SBD message
    if _, err := device.WriteAt(message, offset); err != nil {
        return fmt.Errorf("failed to write SBD message: %w", err)
    }

    // Critical: ensure the message is immediately written to disk
    if err := device.Sync(); err != nil {
        return fmt.Errorf("failed to sync SBD message: %w", err)
    }

    return nil
}

func readSBDMessage(devicePath string, messageSize int, offset int64) ([]byte, error) {
    device, err := blockdevice.Open(devicePath)
    if err != nil {
        return nil, fmt.Errorf("failed to open SBD device: %w", err)
    }
    defer device.Close()

    buf := make([]byte, messageSize)
    n, err := device.ReadAt(buf, offset)
    if err != nil && err != io.EOF {
        return nil, fmt.Errorf("failed to read SBD message: %w", err)
    }

    return buf[:n], nil
}
Concurrent Operations
func concurrentSBDOperations(devicePath string) error {
    device, err := blockdevice.Open(devicePath)
    if err != nil {
        return err
    }
    defer device.Close()

    var wg sync.WaitGroup
    
    // Multiple goroutines can safely read/write to different offsets
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            
            offset := int64(id * 512) // 512-byte sectors
            data := fmt.Sprintf("Message from goroutine %d", id)
            
            // Write operation
            if _, err := device.WriteAt([]byte(data), offset); err != nil {
                log.Printf("Write failed for goroutine %d: %v", id, err)
                return
            }
            
            // Read operation
            buf := make([]byte, len(data))
            if _, err := device.ReadAt(buf, offset); err != nil {
                log.Printf("Read failed for goroutine %d: %v", id, err)
                return
            }
        }(i)
    }
    
    wg.Wait()
    return device.Sync() // Final sync to ensure all writes are committed
}

Important Considerations

SBD Operations
  • Synchronous I/O: The package opens devices with O_SYNC flag, ensuring writes are immediately committed to storage
  • Data Integrity: Always call Sync() after critical writes to guarantee data persistence
  • Error Handling: Check all return values; partial writes are treated as errors
  • Device Permissions: Ensure proper permissions to access block devices (typically requires root or specific group membership)
Performance
  • Direct Access: Operations bypass filesystem caches for maximum reliability
  • Positioned I/O: ReadAt and WriteAt don't affect file position, enabling concurrent access
  • Synchronous Writes: Write performance is slower due to O_SYNC, but provides data integrity guarantees
Security
  • Privileged Access: Block device operations typically require elevated privileges
  • Data Safety: Always validate offsets and data sizes to prevent corruption
  • Resource Management: Always close devices to prevent resource leaks

Testing

The package includes comprehensive tests that can be run with:

go test ./pkg/blockdevice -v

For benchmarks:

go test ./pkg/blockdevice -bench=. -benchmem

Error Handling

All operations return detailed errors that include:

  • The device path for context
  • The specific operation that failed
  • The underlying system error

Example error messages:

  • failed to open block device "/dev/sdb1": permission denied
  • failed to write to device "/dev/sdb1" at offset 1024: input/output error
  • device "/dev/sdb1" is closed

Thread Safety

The Device type is safe for concurrent use:

  • ReadAt and WriteAt operations are thread-safe
  • Multiple goroutines can perform positioned I/O concurrently
  • Sync() and Close() operations are also thread-safe

However, be cautious about:

  • Closing a device while other operations are in progress
  • Coordinating writes to overlapping regions between goroutines

License

This package is part of the SBD Operator project and is licensed under the Apache License 2.0.

Documentation

Overview

Package blockdevice provides utilities for interacting with raw block devices. This package is designed specifically for SBD (Storage-Based Death) operations that require direct, synchronous access to block devices for reliable fencing.

Index

Constants

View Source
const (
	// MaxRetryAttempts is the maximum number of retry attempts for transient errors
	MaxRetryAttempts = 3
	// InitialRetryDelay is the initial delay between retry attempts
	InitialRetryDelay = 100 * time.Millisecond
	// MaxRetryDelay is the maximum delay between retry attempts
	MaxRetryDelay = 5 * time.Second
	// RetryBackoffFactor is the exponential backoff factor for retry delays
	RetryBackoffFactor = 2.0

	// DefaultIOTimeout is the default timeout for individual I/O operations
	// This prevents indefinite hanging when storage becomes unresponsive
	DefaultIOTimeout = 30 * time.Second
)

Retry configuration constants for block device operations

Variables

This section is empty.

Functions

This section is empty.

Types

type Device

type Device struct {
	// contains filtered or unexported fields
}

Device represents a raw block device that can be read from and written to. It implements the io.ReaderAt and io.WriterAt interfaces for positioned I/O operations. All operations are performed synchronously to ensure data integrity for SBD operations.

func Open

func Open(path string) (*Device, error)

Open opens a raw block device at the specified path for read/write operations. The device is opened with O_RDWR and O_SYNC flags to ensure synchronous I/O, which is critical for SBD operations where data must be immediately written to disk.

Parameters:

  • path: The filesystem path to the block device (e.g., "/dev/sdb1")

Returns:

  • *Device: A new Device instance if successful
  • error: An error if the device cannot be opened

Example:

device, err := blockdevice.Open("/dev/sdb1")
if err != nil {
    log.Fatalf("Failed to open device: %v", err)
}
defer device.Close()

func OpenWithLogger

func OpenWithLogger(path string, logger logr.Logger) (*Device, error)

OpenWithLogger opens a raw block device with a logger for retry operations

func OpenWithTimeout

func OpenWithTimeout(path string, ioTimeout time.Duration, logger logr.Logger) (*Device, error)

OpenWithTimeout opens a raw block device with custom I/O timeout and logger This allows customization of the timeout for I/O operations to prevent indefinite hanging.

Parameters:

  • path: The filesystem path to the block device (e.g., "/dev/sdb1")
  • ioTimeout: The timeout for individual I/O operations
  • logger: Logger for device operations and retries

Returns:

  • *Device: A new Device instance if successful
  • error: An error if the device cannot be opened

Example:

device, err := blockdevice.OpenWithTimeout("/dev/sdb1", 45*time.Second, logger)
if err != nil {
    log.Fatalf("Failed to open device: %v", err)
}
defer device.Close()

func (*Device) Close

func (d *Device) Close() error

Close closes the block device and releases any associated resources. After calling Close(), the Device instance should not be used for any further operations.

It is safe to call Close() multiple times; subsequent calls will have no effect.

Returns:

  • error: An error if the close operation fails

Example:

device, err := blockdevice.Open("/dev/sdb1")
if err != nil {
    log.Fatalf("Failed to open device: %v", err)
}
defer device.Close()

// Use device...

if err := device.Close(); err != nil {
    log.Printf("Warning: failed to close device: %v", err)
}

func (*Device) IsClosed

func (d *Device) IsClosed() bool

IsClosed returns true if the device has been closed.

func (*Device) Path

func (d *Device) Path() string

Path returns the filesystem path of the block device.

func (*Device) ReadAt

func (d *Device) ReadAt(p []byte, off int64) (n int, err error)

ReadAt reads len(p) bytes from the device starting at byte offset off. It implements the io.ReaderAt interface, allowing for positioned reads without affecting the device's current file position.

This method is safe for concurrent use as it doesn't modify any shared state and uses the positioned read system call. It includes retry logic for transient errors.

Parameters:

  • p: The buffer to read data into
  • off: The byte offset from the beginning of the device to start reading

Returns:

  • n: The number of bytes actually read
  • err: An error if the read operation fails

The method returns an error if:

  • The device has been closed
  • The offset is negative
  • A system-level read error occurs after retries

func (*Device) String

func (d *Device) String() string

String returns a string representation of the Device for debugging purposes.

func (*Device) Sync

func (d *Device) Sync() error

Sync flushes any buffered writes to the underlying storage device. This method explicitly calls the system sync operation to ensure all pending writes are committed to disk before returning.

While the device is opened with O_SYNC flag (meaning writes should be synchronous), calling Sync() provides an additional guarantee that all data has been written to persistent storage. This is particularly important for SBD operations where data integrity is critical. It includes retry logic for transient errors.

Returns:

  • error: An error if the sync operation fails

The method returns an error if:

  • The device has been closed
  • A system-level sync error occurs after retries

func (*Device) WriteAt

func (d *Device) WriteAt(p []byte, off int64) (n int, err error)

WriteAt writes len(p) bytes to the device starting at byte offset off. It implements the io.WriterAt interface, allowing for positioned writes without affecting the device's current file position.

Since the device is opened with O_SYNC, this method ensures that data is immediately written to the underlying storage before returning. It includes retry logic for transient errors.

This method is safe for concurrent use as it doesn't modify any shared state and uses the positioned write system call.

Parameters:

  • p: The data to write to the device
  • off: The byte offset from the beginning of the device to start writing

Returns:

  • n: The number of bytes actually written
  • err: An error if the write operation fails

The method returns an error if:

  • The device has been closed
  • The offset is negative
  • A system-level write error occurs after retries
  • Not all bytes could be written

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL