Skip to content

Commit

Permalink
Don't list WAL/DB devices as available
Browse files Browse the repository at this point in the history
Signed-off-by: Peter Sabaini <[email protected]>
  • Loading branch information
sabaini committed Feb 13, 2024
1 parent 57a1571 commit 03761d8
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 11 deletions.
60 changes: 60 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,66 @@ jobs:
~/actionutils.sh wait_for_osds 3
sudo microceph.ceph -s
wal-db-tests:
name: Test WAL/DB device usage
runs-on: ubuntu-22.04
needs: build-microceph
steps:
- name: Download snap
uses: actions/download-artifact@v3
with:
name: snaps
path: /home/runner

- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0

- name: Copy utils
run: cp tests/scripts/actionutils.sh $HOME

- name: Clear FORWARD firewall rules
run: ~/actionutils.sh cleaript

- name: Free disk
run: ~/actionutils.sh free_runner_disk

- name: Install and setup
run: ~/actionutils.sh install_microceph

- name: Add loopback file OSDs
run: |
set -uex
sudo microceph disk add loop,1G,3
~/actionutils.sh wait_for_osds 3
sudo microceph.ceph -s
- name: Add WAL/DB enabled OSD
run: |
set -uex
~/actionutils.sh create_loop_devices
sudo microceph disk list
sudo microceph disk add /dev/sdia --wal-device /dev/sdib --db-device /dev/sdic
~/actionutils.sh wait_for_osds 4
sudo microceph.ceph -s
- name: Check that disk list doesn't contain WAL/DB blockdevs
run: |
set -uex
sudo microceph disk list
for d in sdb sdc ; do
if sudo microceph disk list | grep -q /dev/$d ; then
echo "Error: found WAL/DB device: /dev/$d"
exit 1
fi
done
- name: Enable RGW
run: ~/actionutils.sh enable_rgw

- name: Exercise RGW
run: ~/actionutils.sh testrgw

upgrade-quincy-tests:
name: Test quincy upgrades
Expand Down
30 changes: 29 additions & 1 deletion microceph/cmd/microceph/disk_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/canonical/lxd/shared/api"
"github.com/canonical/microceph/microceph/constants"
"os"
"sort"
Expand All @@ -17,6 +18,7 @@ import (

"github.com/canonical/microceph/microceph/api/types"
"github.com/canonical/microceph/microceph/client"
"github.com/canonical/microceph/microceph/common"
)

type cmdDiskList struct {
Expand Down Expand Up @@ -150,6 +152,17 @@ func getUnpartitionedDisks(cli *microCli.Client) ([]Disk, error) {
return nil, fmt.Errorf("internal error: unable to fetch available disks: %w", err)
}

data, err := filterLocalDisks(resources, disks)
if err != nil {
return nil, err
}

return data, nil
}

// filterLocalDisks filters out the disks that are in use or otherwise not suitable for OSDs.
func filterLocalDisks(resources *api.ResourcesStorage, disks types.Disks) ([]Disk, error) {
var err error
// Get local hostname.
hostname, err := os.Hostname()
if err != nil {
Expand Down Expand Up @@ -192,13 +205,28 @@ func getUnpartitionedDisks(cli *microCli.Client) ([]Disk, error) {
continue
}

// check if disk is mounted or already employed as a journal or db
mounted, err := common.IsMounted(devicePath)
if err != nil {
return nil, fmt.Errorf("internal error: unable to check if disk is mounted: %w", err)
}
if mounted {
continue
}
isCephDev, err := common.IsCephDevice(devicePath)
if err != nil {
return nil, fmt.Errorf("internal error checking if disk is ceph device: %w", err)
}
if isCephDev {
continue
}

data = append(data, Disk{
Model: disk.Model,
Size: units.GetByteSizeStringIEC(int64(disk.Size), 2),
Type: disk.Type,
Path: devicePath,
})
}

return data, nil
}
84 changes: 84 additions & 0 deletions microceph/common/storage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package common

import (
"bufio"
"github.com/canonical/lxd/shared/logger"
"github.com/canonical/microceph/microceph/constants"
"os"
"path/filepath"
"strings"
)

// IsMounted checks if a device is mounted.
func IsMounted(device string) (bool, error) {
// Resolve any symlink and get the absolute path of the device.
// Note /proc/mounts contains the absolute path of the device as well.
resolvedPath, err := filepath.EvalSymlinks(device)
if err != nil {
// Handle errors other than not existing differently as EvalSymlinks takes care of symlink resolution
return false, err
}
file, err := os.Open(filepath.Join(constants.GetPathConst().ProcPath, "mounts"))
if err != nil {
return false, err
}
defer file.Close()

scanner := bufio.NewScanner(file)
for scanner.Scan() {
// Each line in /proc/mounts is of the format:
// device mountpoint fstype options dump pass
// --> split the line into parts and check if the first part matches
parts := strings.Fields(scanner.Text())
if len(parts) > 0 && parts[0] == resolvedPath {
return true, nil
}
}
if err := scanner.Err(); err != nil {
return false, err
}
return false, nil
}

// IsCephDevice checks if a given device is used as either a WAL or DB block device in any Ceph OSD.
func IsCephDevice(device string) (bool, error) {
// Resolve the given device path first to handle any symlinks
resolved, err := filepath.EvalSymlinks(device)
if err != nil {
logger.Errorf("failed to resolve device path: %v", err)
return false, err
}
// Check all ceph data dirs
baseDir := filepath.Join(constants.GetPathConst().DataPath, "osd")
osdDirs, err := os.ReadDir(baseDir)
if err != nil {
// Likely no OSDs exist yet
logger.Debugf("couldn't read osd data dir %s: %v", baseDir, err)
return false, nil
}
// Do we have a block{,.wal,.db} symlink pointing to the given device? if yes
// it's already being used as a ceph device
for _, osdDir := range osdDirs {
if osdDir.IsDir() {
if !strings.HasPrefix(osdDir.Name(), "ceph-") {
continue
}
for _, symlinkName := range []string{"block", "block.wal", "block.db"} {
symlinkPath := filepath.Join(baseDir, osdDir.Name(), symlinkName)
resolvedPath, err := filepath.EvalSymlinks(symlinkPath)
if err == nil {
if resolvedPath == resolved {
logger.Debugf("device %s is used as %s for OSD %s", device, symlinkName, osdDir.Name())
return true, nil
}
} else if !os.IsNotExist(err) {
logger.Errorf("failed to resolve symlink %s: %v", symlinkPath, err)
return false, err
}
}
}
}
// Fall-through: no symlink found
logger.Debugf("device %s is not used as WAL or DB device for any OSD", device)
return false, nil
}
62 changes: 62 additions & 0 deletions microceph/common/storage_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package common

import (
"github.com/canonical/microceph/microceph/tests"
"github.com/stretchr/testify/suite"
"os"
"path/filepath"
"testing"
)

type StorageDeviceTestSuite struct {
tests.BaseSuite
devicePath string
}

func (s *StorageDeviceTestSuite) SetupTest() {
s.BaseSuite.SetupTest()
s.CopyCephConfigs()

osdDir := filepath.Join(s.Tmp, "SNAP_COMMON", "data", "osd", "ceph-0")
os.MkdirAll(osdDir, 0775)
// create a temp file to use as a device
s.devicePath = filepath.Join(s.Tmp, "device")
os.Create(s.devicePath)

// create a /proc/mounts like file
mountsFile := filepath.Join(s.Tmp, "proc", "mounts")
mountsContent := "/dev/root / ext4 rw,relatime,discard,errors=remount-ro 0 0\n"
mountsContent += "/dev/sdb /mnt ext2 rw,relatime 0 0\n"
_ = os.WriteFile(mountsFile, []byte(mountsContent), 0644)

}

func (s *StorageDeviceTestSuite) TestIsCephDeviceNotADevice() {
isCeph, err := IsCephDevice(s.devicePath)
s.NoError(err, "There should not be an error when checking a device that is not used by Ceph")
s.False(isCeph, "The device should not be identified as a Ceph device")
}

func (s *StorageDeviceTestSuite) TestIsCephDeviceHaveDevice() {
// create a symlink to the device file
os.Symlink(s.devicePath, filepath.Join(s.Tmp, "SNAP_COMMON", "data", "osd", "ceph-0", "block"))
isCeph, err := IsCephDevice(s.devicePath)
s.NoError(err, "There should not be an error when checking a device that is used by Ceph")
s.True(isCeph, "The device should be identified as a Ceph device")
}

func (s *StorageDeviceTestSuite) TestIsMounted() {
// we added a /proc/mounts like file containing an entry for /dev/sdb
mounted, err := IsMounted("/dev/sdb")
s.NoError(err, "There should not be an error when checking if a device is mounted")
s.True(mounted, "The device should be mounted")

mounted, err = IsMounted("/dev/sdc")
s.NoError(err, "There should not be an error when checking if a device is not mounted")
s.False(mounted, "The device should not be mounted")

}

func TestStorageDeviceSuite(t *testing.T) {
suite.Run(t, new(StorageDeviceTestSuite))
}
18 changes: 8 additions & 10 deletions tests/scripts/actionutils.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,22 @@ function install_microceph() {

}

function add_encrypted_osds() {
# Enable dm-crypt connection and restart microceph daemon
sudo snap connect microceph:dm-crypt
sudo snap restart microceph.daemon
# Add OSDs backed by loop devices on /mnt (ephemeral "large" disk attached to GitHub action runners)
i=0
function create_loop_devices() {
for l in a b c; do
loop_file="$(sudo mktemp -p /mnt XXXX.img)"
sudo truncate -s 4G "${loop_file}"
loop_dev="$(sudo losetup --show -f "${loop_file}")"

# XXX: the block-devices plug doesn't allow accessing /dev/loopX
# devices so we make those same devices available under alternate
# names (/dev/sdiY) that are not used inside GitHub Action runners
minor="${loop_dev##/dev/loop}"
# create well-known names
sudo mknod -m 0660 "/dev/sdi${l}" b 7 "${minor}"
done
}

function add_encrypted_osds() {
# Enable dm-crypt connection and restart microceph daemon
sudo snap connect microceph:dm-crypt
sudo snap restart microceph.daemon
create_loop_devices
sudo microceph disk add /dev/sdia /dev/sdib /dev/sdic --wipe --encrypt

# Wait for OSDs to become up
Expand Down

0 comments on commit 03761d8

Please sign in to comment.