Skip to content

Commit

Permalink
Merge pull request #253 from stgraber/import
Browse files Browse the repository at this point in the history
Import LXD changes
  • Loading branch information
tych0 authored Nov 29, 2023
2 parents bff7d01 + d416550 commit abb77db
Show file tree
Hide file tree
Showing 20 changed files with 422 additions and 178 deletions.
3 changes: 3 additions & 0 deletions doc/api-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2284,3 +2284,6 @@ Calling `POST /1.0/storage-pools/<pool>/custom/<volume>?target=<target>` will mo

## `disk_io_bus`
This introduces a new `io.bus` property to disk devices which can be used to override the bus the disk is attached to.

## `storage_cephfs_create_missing`
This introduces the configuration keys `cephfs.create_missing`, `cephfs.osd_pg_num`, `cephfs.meta_pool` and `cephfs.osd_pool` to be used when adding a `cephfs` storage pool to instruct Incus to create the necessary entities for the storage pool, if they do not exist.
2 changes: 1 addition & 1 deletion doc/howto/instances_create.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ The second step is to import an ISO image that can later be attached to the VM a

Lastly, you need to attach the custom ISO volume to the VM using the following command:

incus config device add iso-vm iso-volume disk pool=default source=iso-volume boot.priority=10
incus config device add iso-vm iso-volume disk pool=<pool> source=iso-volume boot.priority=10

The `boot.priority` configuration key ensures that the VM will boot from the ISO first.
Start the VM and connect to the console as there might be a menu you need to interact with:
Expand Down
4 changes: 4 additions & 0 deletions doc/reference/storage_cephfs.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ Key | Type | Default
`cephfs.fscache` | bool | `false` | Enable use of kernel `fscache` and `cachefilesd`
`cephfs.path` | string | `/` | The base path for the CephFS mount
`cephfs.user.name` | string | `admin` | The Ceph user to use
`cephfs.create_missing` | bool | `false` | Create the file-system and missing data and metadata OSD pools
`cephfs.osd_pg_num` | string | - | OSD pool `pg_num` to use when creating missing OSD pools
`cephfs.meta_pool` | string | - | Metadata OSD pool name to create for the file-system
`cephfs.data_pool` | string | - | Data OSD pool name to create for the file-system
`source` | string | - | Existing CephFS file system or file system path to use
`volatile.pool.pristine` | string | `true` | Whether the CephFS file system was empty on creation time

Expand Down
24 changes: 13 additions & 11 deletions internal/server/instance/drivers/driver_qemu.go
Original file line number Diff line number Diff line change
Expand Up @@ -1044,7 +1044,7 @@ func (d *qemu) validateStartup(stateful bool, statusCode api.StatusCode) error {
return err
}

stateDiskSizeStr := deviceConfig.DefaultVMBlockFilesystemSize
stateDiskSizeStr := d.storagePool.Driver().Info().DefaultVMBlockFilesystemSize
if rootDiskDevice["size.state"] != "" {
stateDiskSizeStr = rootDiskDevice["size.state"]
}
Expand Down Expand Up @@ -3565,19 +3565,21 @@ func (d *qemu) addRootDriveConfig(qemuDev map[string]string, mountInfo *storageP
if d.storagePool.Driver().Info().Remote {
vol := d.storagePool.GetVolume(storageDrivers.VolumeTypeVM, storageDrivers.ContentTypeBlock, project.Instance(d.project.Name, d.name), nil)

config := d.storagePool.ToAPI().Config
if util.ValueInSlice(d.storagePool.Driver().Info().Name, []string{"ceph", "cephfs"}) {
config := d.storagePool.ToAPI().Config

userName := config["ceph.user.name"]
if userName == "" {
userName = storageDrivers.CephDefaultUser
}
userName := config["ceph.user.name"]
if userName == "" {
userName = storageDrivers.CephDefaultUser
}

clusterName := config["ceph.cluster_name"]
if clusterName == "" {
clusterName = storageDrivers.CephDefaultUser
}
clusterName := config["ceph.cluster_name"]
if clusterName == "" {
clusterName = storageDrivers.CephDefaultUser
}

driveConf.DevPath = device.DiskGetRBDFormat(clusterName, userName, config["ceph.osd.pool_name"], vol.Name())
driveConf.DevPath = device.DiskGetRBDFormat(clusterName, userName, config["ceph.osd.pool_name"], vol.Name())
}
}

return d.addDriveConfig(qemuDev, bootIndexes, driveConf)
Expand Down
34 changes: 27 additions & 7 deletions internal/server/instance/instance_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/lxc/incus/internal/server/seccomp"
"github.com/lxc/incus/internal/server/state"
"github.com/lxc/incus/internal/server/sys"
localUtil "github.com/lxc/incus/internal/server/util"
internalUtil "github.com/lxc/incus/internal/util"
"github.com/lxc/incus/internal/version"
"github.com/lxc/incus/shared/api"
Expand Down Expand Up @@ -1031,9 +1032,18 @@ func NextSnapshotName(s *state.State, inst Instance, defaultPattern string) (str
return pattern, nil
}

// temporaryName concatenates the move prefix and instUUID for a temporary instance.
func temporaryName(instUUID string) string {
return fmt.Sprintf("move-of-%s", instUUID)
// temporaryName returns the temporary instance name using a stable random generator.
// The returned string is a valid DNS name.
func temporaryName(instUUID string) (string, error) {
r, err := localUtil.GetStableRandomGenerator(instUUID)
if err != nil {
return "", err
}

// The longest temporary name is move-of-18446744073709551615 which has a length
// of 30 characters since 18446744073709551615 is the biggest value for an uint64.
// The prefix is attached to have a valid DNS name that doesn't start with numbers.
return fmt.Sprintf("move-of-%d", r.Uint64()), nil
}

// MoveTemporaryName returns a name derived from the instance's volatile.uuid, to use when moving an instance
Expand All @@ -1046,11 +1056,11 @@ func MoveTemporaryName(inst Instance) (string, error) {
instUUID = uuid.New().String()
err := inst.VolatileSet(map[string]string{"volatile.uuid": instUUID})
if err != nil {
return "", fmt.Errorf("Failed generating instance UUID: %w", err)
return "", fmt.Errorf("Failed setting volatile.uuid to %s: %w", instUUID, err)
}
}

return temporaryName(instUUID), nil
return temporaryName(instUUID)
}

// IsSameLogicalInstance returns true if the supplied Instance and db.Instance have the same project and name or
Expand All @@ -1065,12 +1075,22 @@ func IsSameLogicalInstance(inst Instance, dbInst *db.InstanceArgs) bool {
if dbInst.Config["volatile.uuid"] == inst.LocalConfig()["volatile.uuid"] {
// Accommodate moving instances between storage pools.
// Check temporary copy against source.
if dbInst.Name == temporaryName(inst.LocalConfig()["volatile.uuid"]) {
tempName, err := temporaryName(inst.LocalConfig()["volatile.uuid"])
if err != nil {
return false
}

if dbInst.Name == tempName {
return true
}

// Check source against temporary copy.
if inst.Name() == temporaryName(dbInst.Config["volatile.uuid"]) {
tempName, err = temporaryName(dbInst.Config["volatile.uuid"])
if err != nil {
return false
}

if inst.Name() == tempName {
return true
}

Expand Down
4 changes: 3 additions & 1 deletion internal/server/project/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -1251,7 +1251,7 @@ func expandInstancesConfigAndDevices(instances []api.Instance, profiles []api.Pr
}

// Sum of the effective values for the given limits across all project
// enties (instances and custom volumes).
// entities (instances and custom volumes).
func getTotalsAcrossProjectEntities(info *projectInfo, keys []string, skipUnset bool) (map[string]int64, error) {
totals := map[string]int64{}

Expand Down Expand Up @@ -1329,6 +1329,8 @@ func getInstanceLimits(inst api.Instance, keys []string, skipUnset bool) (map[st
if inst.Type == instancetype.VM.String() {
sizeStateValue, ok := device["size.state"]
if !ok {
// TODO: In case the VMs storage drivers config drive size isn't the default,
// the limits accounting will be incorrect.
sizeStateValue = deviceconfig.DefaultVMBlockFilesystemSize
}

Expand Down
5 changes: 2 additions & 3 deletions internal/server/storage/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import (
"github.com/lxc/incus/internal/server/cluster/request"
"github.com/lxc/incus/internal/server/db"
"github.com/lxc/incus/internal/server/db/cluster"
deviceConfig "github.com/lxc/incus/internal/server/device/config"
"github.com/lxc/incus/internal/server/instance"
"github.com/lxc/incus/internal/server/instance/instancetype"
"github.com/lxc/incus/internal/server/lifecycle"
Expand Down Expand Up @@ -918,7 +917,7 @@ func (b *backend) CreateInstanceFromBackup(srcBackup backup.Info, srcData io.Rea
// filesystem volume as well, allowing a former quota to be removed from both
// volumes.
if vmStateSize == "" && size != "" {
vmStateSize = deviceConfig.DefaultVMBlockFilesystemSize
vmStateSize = b.driver.Info().DefaultVMBlockFilesystemSize
}

l.Debug("Applying filesystem volume quota from root disk config", logger.Ctx{"size.state": vmStateSize})
Expand Down Expand Up @@ -2636,7 +2635,7 @@ func (b *backend) SetInstanceQuota(inst instance.Instance, size string, vmStateS
// this will also pass empty quota for the config filesystem volume as well, allowing a former
// quota to be removed from both volumes.
if vmStateSize == "" && size != "" {
vmStateSize = deviceConfig.DefaultVMBlockFilesystemSize
vmStateSize = b.driver.Info().DefaultVMBlockFilesystemSize
}

fsVol := vol.NewVMBlockFilesystemVolume()
Expand Down
30 changes: 16 additions & 14 deletions internal/server/storage/drivers/driver_btrfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/lxc/incus/internal/linux"
"github.com/lxc/incus/internal/migration"
"github.com/lxc/incus/internal/revert"
deviceConfig "github.com/lxc/incus/internal/server/device/config"
localMigration "github.com/lxc/incus/internal/server/migration"
"github.com/lxc/incus/internal/server/operations"
internalUtil "github.com/lxc/incus/internal/util"
Expand Down Expand Up @@ -91,20 +92,21 @@ func (d *btrfs) load() error {
// Info returns info about the driver and its environment.
func (d *btrfs) Info() Info {
return Info{
Name: "btrfs",
Version: btrfsVersion,
OptimizedImages: true,
OptimizedBackups: true,
OptimizedBackupHeader: true,
PreservesInodes: !d.state.OS.RunningInUserNS,
Remote: d.isRemote(),
VolumeTypes: []VolumeType{VolumeTypeBucket, VolumeTypeCustom, VolumeTypeImage, VolumeTypeContainer, VolumeTypeVM},
BlockBacking: false,
RunningCopyFreeze: false,
DirectIO: true,
IOUring: true,
MountedRoot: true,
Buckets: true,
Name: "btrfs",
Version: btrfsVersion,
DefaultVMBlockFilesystemSize: deviceConfig.DefaultVMBlockFilesystemSize,
OptimizedImages: true,
OptimizedBackups: true,
OptimizedBackupHeader: true,
PreservesInodes: !d.state.OS.RunningInUserNS,
Remote: d.isRemote(),
VolumeTypes: []VolumeType{VolumeTypeBucket, VolumeTypeCustom, VolumeTypeImage, VolumeTypeContainer, VolumeTypeVM},
BlockBacking: false,
RunningCopyFreeze: false,
DirectIO: true,
IOUring: true,
MountedRoot: true,
Buckets: true,
}
}

Expand Down
72 changes: 52 additions & 20 deletions internal/server/storage/drivers/driver_btrfs_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"context"
"fmt"
"io"
"io/fs"
"os"
"os/exec"
"path/filepath"
Expand Down Expand Up @@ -107,39 +108,70 @@ func (d *btrfs) hasSubvolumes(path string) (bool, error) {
}

func (d *btrfs) getSubvolumes(path string) ([]string, error) {
// Make sure the path has a trailing slash.
if !strings.HasSuffix(path, "/") {
path = path + "/"
}

poolMountPath := GetPoolMountPath(d.name)
if !strings.HasPrefix(path, poolMountPath+"/") {
return nil, fmt.Errorf("%q is outside pool mount path %q", path, poolMountPath)
}

path = strings.TrimPrefix(path, poolMountPath+"/")
var result []string

// Make sure the path has a trailing slash.
if !strings.HasSuffix(path, "/") {
path = path + "/"
}
if d.state.OS.RunningInUserNS {
// If using BTRFS in a nested container we cannot use "btrfs subvolume list" due to a permission error.
// So instead walk the directory tree testing each directory to see if it is subvolume.
err := filepath.Walk(path, func(fpath string, entry fs.FileInfo, err error) error {
if err != nil {
return err
}

var stdout bytes.Buffer
err := subprocess.RunCommandWithFds(d.state.ShutdownCtx, nil, &stdout, "btrfs", "subvolume", "list", poolMountPath)
if err != nil {
return nil, err
}
// Ignore the base path.
if strings.TrimRight(fpath, "/") == strings.TrimRight(path, "/") {
return nil
}

result := []string{}
// Subvolumes can only be directories.
if !entry.IsDir() {
return nil
}

scanner := bufio.NewScanner(&stdout)
for scanner.Scan() {
fields := strings.Fields(scanner.Text())
// Check if directory is a subvolume.
if d.isSubvolume(fpath) {
result = append(result, strings.TrimPrefix(fpath, path))
}

if len(fields) != 9 {
continue
return nil
})
if err != nil {
return nil, err
}

if !strings.HasPrefix(fields[8], path) {
continue
} else {
// If not running inside a nested container we can use "btrfs subvolume list" to get subvolumes which is more
// performant than walking the directory tree.
var stdout bytes.Buffer
err := subprocess.RunCommandWithFds(d.state.ShutdownCtx, nil, &stdout, "btrfs", "subvolume", "list", poolMountPath)
if err != nil {
return nil, err
}

result = append(result, strings.TrimPrefix(fields[8], path))
path = strings.TrimPrefix(path, poolMountPath+"/")
scanner := bufio.NewScanner(&stdout)
for scanner.Scan() {
fields := strings.Fields(scanner.Text())

if len(fields) != 9 {
continue
}

if !strings.HasPrefix(fields[8], path) {
continue
}

result = append(result, strings.TrimPrefix(fields[8], path))
}
}

return result, nil
Expand Down
24 changes: 13 additions & 11 deletions internal/server/storage/drivers/driver_ceph.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/lxc/incus/internal/migration"
"github.com/lxc/incus/internal/revert"
deviceConfig "github.com/lxc/incus/internal/server/device/config"
localMigration "github.com/lxc/incus/internal/server/migration"
"github.com/lxc/incus/internal/server/operations"
"github.com/lxc/incus/shared/api"
Expand Down Expand Up @@ -80,17 +81,18 @@ func (d *ceph) isRemote() bool {
// Info returns info about the driver and its environment.
func (d *ceph) Info() Info {
return Info{
Name: "ceph",
Version: cephVersion,
OptimizedImages: true,
PreservesInodes: false,
Remote: d.isRemote(),
VolumeTypes: []VolumeType{VolumeTypeCustom, VolumeTypeImage, VolumeTypeContainer, VolumeTypeVM},
BlockBacking: true,
RunningCopyFreeze: true,
DirectIO: true,
IOUring: true,
MountedRoot: false,
Name: "ceph",
Version: cephVersion,
DefaultVMBlockFilesystemSize: deviceConfig.DefaultVMBlockFilesystemSize,
OptimizedImages: true,
PreservesInodes: false,
Remote: d.isRemote(),
VolumeTypes: []VolumeType{VolumeTypeCustom, VolumeTypeImage, VolumeTypeContainer, VolumeTypeVM},
BlockBacking: true,
RunningCopyFreeze: true,
DirectIO: true,
IOUring: true,
MountedRoot: false,
}
}

Expand Down
Loading

0 comments on commit abb77db

Please sign in to comment.