Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Storage: PowerFlex fixes and improvements #14865

Merged
merged 10 commits into from
Jan 31, 2025
Merged
160 changes: 160 additions & 0 deletions lxd/storage/block/utils.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,61 @@
package block

import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"time"

"golang.org/x/sys/unix"

"github.com/canonical/lxd/lxd/resources"
"github.com/canonical/lxd/shared"
)

// devicePathFilterFunc is a function that accepts device path and returns true
// if the path matches the required criteria.
type devicePathFilterFunc func(devPath string) bool

// findDiskDevivePath iterates over device names in /dev/disk/by-id directory and
// returns the path to the disk device that matches the given prefix and suffix.
// Disk partitions are skipped, and an error is returned if the device is not found.
func findDiskDevicePath(diskNamePrefix string, diskPathFilter devicePathFilterFunc) (string, error) {
var diskPaths []string

// If there are no other disks on the system by id, the directory might not
// even be there. Returns ENOENT in case the by-id/ directory does not exist.
diskPaths, err := resources.GetDisksByID(diskNamePrefix)
if err != nil {
return "", err
}

for _, diskPath := range diskPaths {
// Skip the disk if it is only a partition of the actual volume.
if strings.Contains(diskPath, "-part") {
continue
}

// Use custom disk path filter, if one is provided.
if diskPathFilter != nil && !diskPathFilter(diskPath) {
continue
}

// The actual device might not already be created.
// Returns ENOENT in case the device does not exist.
devPath, err := filepath.EvalSymlinks(diskPath)
if err != nil {
return "", err
}

return devPath, nil
}

return "", nil
}

// DiskSizeBytes returns the size of a block disk (path can be either block device or raw file).
func DiskSizeBytes(blockDiskPath string) (int64, error) {
if shared.IsBlockdevPath(blockDiskPath) {
Expand Down Expand Up @@ -56,3 +104,115 @@ func DiskBlockSize(path string) (uint32, error) {

return res, nil
}

// WaitDiskDeviceResize waits until the disk device reflects the new size.
func WaitDiskDeviceResize(ctx context.Context, diskPath string, newSizeBytes int64) error {
_, ok := ctx.Deadline()
if !ok {
// Set a default timeout of 30 seconds for the context
// if no deadline is already configured.
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, 30*time.Second)
defer cancel()
}

for {
sizeBytes, err := DiskSizeBytes(diskPath)
if err != nil {
return fmt.Errorf("Error getting disk size: %w", err)
}

if sizeBytes == newSizeBytes {
return nil
}

if ctx.Err() != nil {
return ctx.Err()
}

time.Sleep(500 * time.Millisecond)
}
}

// WaitDiskDeviceGone waits for the disk device to disappear from /dev/disk/by-id.
// It periodically checks for the device to disappear and returns once the device
// is gone. If the device does not disappear within the timeout, an error is returned.
func WaitDiskDeviceGone(ctx context.Context, diskPath string) bool {
_, ok := ctx.Deadline()
if !ok {
// Set a default timeout of 30 seconds for the context
// if no deadline is already configured.
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, 30*time.Second)
defer cancel()
}

for {
if !shared.PathExists(diskPath) {
return true
}

if ctx.Err() != nil {
return false
}

time.Sleep(500 * time.Millisecond)
}
}

// WaitDiskDevicePath waits for the disk device to appear in /dev/disk/by-id.
// It periodically checks for the device to appear and returns the device path
// once it is found. If the device does not appear within the timeout, an error
// is returned.
func WaitDiskDevicePath(ctx context.Context, diskNamePrefix string, diskPathFilter devicePathFilterFunc) (string, error) {
var err error
var diskPath string

_, ok := ctx.Deadline()
if !ok {
// Set a default timeout of 30 seconds for the context
// if no deadline is already configured.
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, 30*time.Second)
defer cancel()
}

for {
// Check if the device is already present.
diskPath, err = findDiskDevicePath(diskNamePrefix, diskPathFilter)
if err != nil && !errors.Is(err, unix.ENOENT) {
return "", err
}

// If the device is found, return the device path.
if diskPath != "" {
break
}

// Check if context is cancelled.
err := ctx.Err()
if err != nil {
return "", err
}

time.Sleep(500 * time.Millisecond)
}

return diskPath, nil
}

// GetDiskDevicePath checks whether the disk device with a given prefix and suffix
// exists in /dev/disk/by-id directory. A device path is returned if the device is
// found, otherwise an error is returned.
func GetDiskDevicePath(diskNamePrefix string, diskPathFilter devicePathFilterFunc) (string, error) {
devPath, err := findDiskDevicePath(diskNamePrefix, diskPathFilter)
if err != nil {
return "", err
}

if devPath == "" {
return "", fmt.Errorf("Device not found")
}

return devPath, nil
}
131 changes: 0 additions & 131 deletions lxd/storage/connectors/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,146 +2,15 @@ package connectors

import (
"context"
"errors"
"fmt"
"path/filepath"
"strings"
"sync"
"time"

"golang.org/x/sys/unix"

"github.com/canonical/lxd/lxd/locking"
"github.com/canonical/lxd/lxd/resources"
"github.com/canonical/lxd/shared"
"github.com/canonical/lxd/shared/logger"
"github.com/canonical/lxd/shared/revert"
)

// devicePathFilterFunc is a function that accepts device path and returns true
// if the path matches the required criteria.
type devicePathFilterFunc func(devPath string) bool

// GetDiskDevicePath checks whether the disk device with a given prefix and suffix
// exists in /dev/disk/by-id directory. A device path is returned if the device is
// found, otherwise an error is returned.
func GetDiskDevicePath(diskNamePrefix string, diskPathFilter devicePathFilterFunc) (string, error) {
devPath, err := findDiskDevicePath(diskNamePrefix, diskPathFilter)
if err != nil {
return "", err
}

if devPath == "" {
return "", fmt.Errorf("Device not found")
}

return devPath, nil
}

// WaitDiskDevicePath waits for the disk device to appear in /dev/disk/by-id.
// It periodically checks for the device to appear and returns the device path
// once it is found. If the device does not appear within the timeout, an error
// is returned.
func WaitDiskDevicePath(ctx context.Context, diskNamePrefix string, diskPathFilter devicePathFilterFunc) (string, error) {
var err error
var diskPath string

_, ok := ctx.Deadline()
if !ok {
// Set a default timeout of 30 seconds for the context
// if no deadline is already configured.
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, 30*time.Second)
defer cancel()
}

for {
// Check if the device is already present.
diskPath, err = findDiskDevicePath(diskNamePrefix, diskPathFilter)
if err != nil && !errors.Is(err, unix.ENOENT) {
return "", err
}

// If the device is found, return the device path.
if diskPath != "" {
break
}

// Check if context is cancelled.
err := ctx.Err()
if err != nil {
return "", err
}

time.Sleep(500 * time.Millisecond)
}

return diskPath, nil
}

// findDiskDevivePath iterates over device names in /dev/disk/by-id directory and
// returns the path to the disk device that matches the given prefix and suffix.
// Disk partitions are skipped, and an error is returned if the device is not found.
func findDiskDevicePath(diskNamePrefix string, diskPathFilter devicePathFilterFunc) (string, error) {
var diskPaths []string

// If there are no other disks on the system by id, the directory might not
// even be there. Returns ENOENT in case the by-id/ directory does not exist.
diskPaths, err := resources.GetDisksByID(diskNamePrefix)
if err != nil {
return "", err
}

for _, diskPath := range diskPaths {
// Skip the disk if it is only a partition of the actual volume.
if strings.Contains(diskPath, "-part") {
continue
}

// Use custom disk path filter, if one is provided.
if diskPathFilter != nil && !diskPathFilter(diskPath) {
continue
}

// The actual device might not already be created.
// Returns ENOENT in case the device does not exist.
devPath, err := filepath.EvalSymlinks(diskPath)
if err != nil {
return "", err
}

return devPath, nil
}

return "", nil
}

// WaitDiskDeviceGone waits for the disk device to disappear from /dev/disk/by-id.
// It periodically checks for the device to disappear and returns once the device
// is gone. If the device does not disappear within the timeout, an error is returned.
func WaitDiskDeviceGone(ctx context.Context, diskPath string) bool {
_, ok := ctx.Deadline()
if !ok {
// Set a default timeout of 30 seconds for the context
// if no deadline is already configured.
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, 30*time.Second)
defer cancel()
}

for {
if !shared.PathExists(diskPath) {
return true
}

if ctx.Err() != nil {
return false
}

time.Sleep(500 * time.Millisecond)
}
}

// connectFunc is invoked by "connect" for each provided address.
// It receives a session and a target address. A non-nil session indicates
// an existing session for the target.
Expand Down
Loading
Loading