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

Import LXD changes #279

Merged
merged 27 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4a7548f
[lxd-import] doc/instances: change pool name to be consistent
ru-fu Nov 29, 2023
f4068a1
[lxd-import] lxd/instance_post: Retain root disk device if not explic…
MusicDin Nov 29, 2023
3dd255c
[lxd-import] test: Add tests for server-side instance move
MusicDin Nov 29, 2023
13e21a2
[lxd-import] lxd/instance/drivers/qemu_cmd: Return clean EOF error
MusicDin Nov 30, 2023
cb6264f
[lxd-import] github: have curl fail instead of feeding bogus data on …
simondeziel Dec 4, 2023
9f1e508
[lxd-import] api: Add API extension for improved server-side move
MusicDin Nov 22, 2023
b389b89
[lxd-import] .github/workflows: remove shiftfs
gabrielmougard Nov 8, 2023
bf86673
[lxd-import] lxd/metadata: remove shiftfs
gabrielmougard Nov 8, 2023
84378d0
[lxd-import] lxd/instance/drivers: Set correct RBD content type for q…
roosterfish Dec 5, 2023
0c22685
[lxd-import] lxd/db/instances: Fix instance names from project not re…
MusicDin Dec 5, 2023
20f4574
[lxd-import] lxd/cluster/config: Add missing description default values
roosterfish Dec 6, 2023
47f4181
[lxd-import] lxd/node: Add missing description default values
roosterfish Dec 6, 2023
5bd7dfc
[lxd-import] Update metadata
roosterfish Dec 6, 2023
59e396a
[lxd-import] doc: remove shiftfs
gabrielmougard Nov 8, 2023
d39415e
tests: Re-introduce storage shifting test
stgraber Dec 6, 2023
178c2eb
[lxd-import] shared/api/instance: Expand InstancePost structure
MusicDin Nov 28, 2023
a0f6c5e
[lxd-import] lxc/move: Respect all flags on server-side move
MusicDin Nov 28, 2023
00535c7
[lxd-import] lxd/instance_post: Respect provided config, device and p…
MusicDin Nov 28, 2023
e0e2793
[lxd-import] tests: Add server-side move tests
MusicDin Nov 28, 2023
c3585c4
[lxd-import] doc: Update API
MusicDin Nov 28, 2023
0bcc96b
[lxd-import] i18n: Update translations
MusicDin Nov 28, 2023
55b79fc
[lxd-import] lxc/move: Overwrite profiles only if explicitly provided…
MusicDin Dec 5, 2023
20a8d9e
[lxd-import] lxd/instance_post: Retain previous profiles on instance …
MusicDin Dec 5, 2023
3d7f2de
[lxd-import] tests: Improve tests for instance move
MusicDin Dec 5, 2023
b8f6c08
[lxd-import] lxd/cluster: Retry cluster join if cluster is busy
masnax Nov 28, 2023
d883bf8
doc/cloud-init: Fix spellcheck error
stgraber Dec 6, 2023
a5b6339
shared: remove shiftfs
gabrielmougard Nov 8, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ jobs:
system-tests:
env:
CGO_LDFLAGS_ALLOW: "(-Wl,-wrap,pthread_create)|(-Wl,-z,now)"
INCUS_SHIFTFS_DISABLE: "true"
INCUS_CEPH_CLUSTER: "ceph"
INCUS_CEPH_CEPHFS: "cephfs"
INCUS_CEPH_CEPHOBJECT_RADOSGW: "http://127.0.0.1"
Expand Down Expand Up @@ -241,11 +240,11 @@ jobs:
chmod +x "$(go env GOPATH)/bin/minio"

# Download latest release of openfga server.
curl -s https://api.github.com/repos/openfga/openfga/releases/latest | jq -r '.assets | .[] | .browser_download_url | select(. | test("_linux_amd64.tar.gz$"))' | xargs -I {} curl -L {} -o openfga.tar.gz
curl -sSfL https://api.github.com/repos/openfga/openfga/releases/latest | jq -r '.assets | .[] | .browser_download_url | select(. | test("_linux_amd64.tar.gz$"))' | xargs -I {} curl -sSfL {} -o openfga.tar.gz
tar -xzf openfga.tar.gz -C "$(go env GOPATH)/bin/"

# Download latest release of openfga cli.
curl -s https://api.github.com/repos/openfga/cli/releases/latest | jq -r '.assets | .[] | .browser_download_url | select(. | test("_linux_amd64.tar.gz$"))' | xargs -I {} curl -L {} -o fga.tar.gz
curl -sSfL https://api.github.com/repos/openfga/cli/releases/latest | jq -r '.assets | .[] | .browser_download_url | select(. | test("_linux_amd64.tar.gz$"))' | xargs -I {} curl -sSfL {} -o fga.tar.gz
tar -xzf fga.tar.gz -C "$(go env GOPATH)/bin/"

- name: Download go dependencies
Expand Down Expand Up @@ -319,7 +318,7 @@ jobs:
chmod +x ~
echo "root:1000000:1000000000" | sudo tee /etc/subuid /etc/subgid
cd test
sudo --preserve-env=PATH,GOPATH,GITHUB_ACTIONS,INCUS_VERBOSE,INCUS_BACKEND,INCUS_CEPH_CLUSTER,INCUS_CEPH_CEPHFS,INCUS_CEPH_CEPHOBJECT_RADOSGW,INCUS_OFFLINE,INCUS_SKIP_TESTS,INCUS_REQUIRED_TESTS,INCUS_SHIFTFS_DISABLE INCUS_BACKEND=${{ matrix.backend }} ./main.sh ${{ matrix.suite }}
sudo --preserve-env=PATH,GOPATH,GITHUB_ACTIONS,INCUS_VERBOSE,INCUS_BACKEND,INCUS_CEPH_CLUSTER,INCUS_CEPH_CEPHFS,INCUS_CEPH_CEPHOBJECT_RADOSGW,INCUS_OFFLINE,INCUS_SKIP_TESTS,INCUS_REQUIRED_TESTS, INCUS_BACKEND=${{ matrix.backend }} ./main.sh ${{ matrix.suite }}

client:
name: Client
Expand Down
62 changes: 60 additions & 2 deletions cmd/incus/move.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"fmt"
"strings"

"github.com/spf13/cobra"

Expand Down Expand Up @@ -178,6 +179,23 @@ func (c *cmdMove) Run(cmd *cobra.Command, args []string) error {
return err
}

if source.HasExtension("instance_move_config") {
if c.flagMode != moveDefaultMode {
return fmt.Errorf(i18n.G("The --mode flag can't be used with --storage or --target-project"))
}

// Evaluate provided profiles. If no profiles were provided and flag noProfiles is false,
// existing instance profiles will be retained. Otherwise, flags are respected.
var profiles *[]string
if len(c.flagProfile) > 0 {
profiles = &c.flagProfile
} else if c.flagNoProfiles {
profiles = &[]string{}
}

return moveInstance(conf, sourceResource, destResource, c.flagStorage, c.flagTargetProject, c.flagConfig, c.flagDevice, profiles, c.flagInstanceOnly, stateful)
}

if source.HasExtension("instance_pool_move") && source.HasExtension("instance_project_move") {
if len(c.flagConfig) != 0 || len(c.flagDevice) != 0 || len(c.flagProfile) != 0 || c.flagNoProfiles {
return fmt.Errorf("The move command does not support flags --config, --device, --profile, and --no-profiles. Please use copy instead")
Expand All @@ -187,7 +205,7 @@ func (c *cmdMove) Run(cmd *cobra.Command, args []string) error {
return fmt.Errorf(i18n.G("The --mode flag can't be used with --storage or --target-project"))
}

return moveInstance(conf, sourceResource, destResource, c.flagStorage, c.flagTargetProject, c.flagInstanceOnly, stateful)
return moveInstance(conf, sourceResource, destResource, c.flagStorage, c.flagTargetProject, []string{}, []string{}, nil, c.flagInstanceOnly, stateful)
}
}

Expand Down Expand Up @@ -298,7 +316,7 @@ func moveClusterInstance(conf *config.Config, sourceResource string, destResourc
}

// Move an instance between pools and projects using special POST /instances/<name> API.
func moveInstance(conf *config.Config, sourceResource string, destResource string, storage string, targetProject string, instanceOnly bool, stateful bool) error {
func moveInstance(conf *config.Config, sourceResource string, destResource string, storage string, targetProject string, config []string, devices []string, profiles *[]string, instanceOnly bool, stateful bool) error {
// Parse the source.
sourceRemote, sourceName, err := conf.ParseRemote(sourceResource)
if err != nil {
Expand Down Expand Up @@ -327,6 +345,39 @@ func moveInstance(conf *config.Config, sourceResource string, destResource strin
return fmt.Errorf(i18n.G("Failed to connect to cluster member: %w"), err)
}

// Copy of an instance into a new instance.
inst, _, err := source.GetInstance(sourceName)
if err != nil {
return err
}

// Overwrite the config values.
for _, entry := range config {
key, value, found := strings.Cut(entry, "=")
if !found {
return fmt.Errorf(i18n.G("Bad key=value pair: %q"), entry)
}

inst.Config[key] = value
}

// Parse device map and overwrite device settings.
deviceMap, err := parseDeviceOverrides(devices)
if err != nil {
return err
}

for k, m := range deviceMap {
if inst.Devices[k] == nil {
inst.Devices[k] = m
continue
}

for key, value := range m {
inst.Devices[k][key] = value
}
}

// Pass the new pool to the migration API.
req := api.InstancePost{
Name: destName,
Expand All @@ -335,6 +386,13 @@ func moveInstance(conf *config.Config, sourceResource string, destResource strin
Pool: storage,
Project: targetProject,
Live: stateful,
Config: inst.Config,
Devices: inst.Devices,
}

// Overwrite profiles.
if profiles != nil {
req.Profiles = *profiles
}

op, err := source.MigrateInstance(sourceName, req)
Expand Down
98 changes: 56 additions & 42 deletions cmd/incusd/instance_post.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ func instancePost(d *Daemon, r *http.Request) response.Response {

// Setup the instance move operation.
run := func(op *operations.Operation) error {
return instancePostMigration(s, inst, req.Name, req.Pool, req.Project, req.InstanceOnly, req.Live, req.AllowInconsistent, op)
return instancePostMigration(s, inst, req.Name, req.Pool, req.Project, req.Config, req.Devices, req.Profiles, req.InstanceOnly, req.Live, req.AllowInconsistent, op)
}

resources := map[string][]api.URL{}
Expand Down Expand Up @@ -443,7 +443,7 @@ func instancePost(d *Daemon, r *http.Request) response.Response {
}

// Move an instance.
func instancePostMigration(s *state.State, inst instance.Instance, newName string, newPool string, newProject string, instanceOnly bool, stateful bool, allowInconsistent bool, op *operations.Operation) error {
func instancePostMigration(s *state.State, inst instance.Instance, newName string, newPool string, newProject string, config map[string]string, devices map[string]map[string]string, profiles []string, instanceOnly bool, stateful bool, allowInconsistent bool, op *operations.Operation) error {
if inst.IsSnapshot() {
return fmt.Errorf("Instance snapshots cannot be moved between pools")
}
Expand Down Expand Up @@ -471,68 +471,82 @@ func instancePostMigration(s *state.State, inst instance.Instance, newName strin
localConfig[k] = v
}

// Set user defined configuration entries.
for k, v := range config {
localConfig[k] = v
}

// Get instance local devices and then set user defined devices.
localDevices := inst.LocalDevices().Clone()
for devName, dev := range devices {
localDevices[devName] = dev
}

// Check if root disk device is present in the instance config. If instance config has not
// root disk device configured, check if any of the profiles that will be applied in the
// target project contain a root disk device. Lastly, set current root disk device in the
// instance's config.
rootDevKey, rootDev, err := internalInstance.GetRootDiskDevice(inst.LocalDevices().CloneNative())
if err != nil && !errors.Is(err, internalInstance.ErrNoRootDisk) {
return err
} else if errors.Is(err, internalInstance.ErrNoRootDisk) {
instProfiles := make([]string, 0, len(inst.Profiles()))
// Apply previous profiles, if provided profiles are nil.
if profiles == nil {
profiles = make([]string, 0, len(inst.Profiles()))
for _, p := range inst.Profiles() {
instProfiles = append(instProfiles, p.Name)
profiles = append(profiles, p.Name)
}
}

// Find profiles that will be applied in target project and check if any of them contains
// root disk device.
err := s.DB.Cluster.Transaction(s.ShutdownCtx, func(ctx context.Context, tx *db.ClusterTx) error {
profiles, err := dbCluster.GetProfilesIfEnabled(ctx, tx.Tx(), newProject, instProfiles)
apiProfiles := []api.Profile{}
if len(profiles) > 0 {
err := s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error {
profiles, err := dbCluster.GetProfilesIfEnabled(ctx, tx.Tx(), newProject, profiles)
if err != nil {
return err
}

for _, p := range profiles {
// Get disk devices of the matching profile.
devDiskType := dbCluster.TypeDisk
devDiskfilter := dbCluster.DeviceFilter{
Type: &devDiskType,
}

disks, err := dbCluster.GetProfileDevices(ctx, tx.Tx(), p.ID, devDiskfilter)
apiProfiles = make([]api.Profile, 0, len(profiles))
for _, profile := range profiles {
apiProfile, err := profile.ToAPI(ctx, tx.Tx())
if err != nil {
return err
}

devices := make(map[string]map[string]string)
for _, d := range disks {
devices[d.Name] = d.Config
}

rootDevKey, rootDev, err = internalInstance.GetRootDiskDevice(devices)
if err != nil {
continue
}

break
apiProfiles = append(apiProfiles, *apiProfile)
}

return nil
})
if err != nil {
return err
}
}

// If root disk device was not found in target project profiles, apply current root disk device
// to the instance's config.
if rootDev == nil {
rootDevKey, rootDev, err = internalInstance.GetRootDiskDevice(inst.ExpandedDevices().CloneNative())
if err != nil {
return err
// Check if root disk device is present in the instance config. If instance config has no
// root disk device configured, check if the same root disk device will be applied with new
// profiles in the target project. If the new root disk device differs from the existing
// one, add the existing one as a local device to the instance (we don't want to move root
// disk device if not necessary, as this is an expensive operation).
rootDevKey, rootDev, err := internalInstance.GetRootDiskDevice(localDevices.CloneNative())
if err != nil && !errors.Is(err, internalInstance.ErrNoRootDisk) {
return err
} else if errors.Is(err, internalInstance.ErrNoRootDisk) {
// Find currently applied root disk device from expanded devices.
rootDevKey, rootDev, err = internalInstance.GetRootDiskDevice(inst.ExpandedDevices().CloneNative())
if err != nil {
return err
}

// Iterate over profiles that will be applied in the target project and find
// the new root disk device. Iterate in reverse order to respect profile
// precedence.
var profileRootDev map[string]string
for i := len(apiProfiles) - 1; i >= 0; i-- {
_, profileRootDev, err = internalInstance.GetRootDiskDevice(apiProfiles[i].Devices)
if err == nil {
break
}
}

// If current root disk device would be replaced according to the new profiles,
// add current root disk device to local instance devices (to retain it).
if profileRootDev == nil ||
profileRootDev["pool"] != rootDev["pool"] ||
profileRootDev["size"] != rootDev["size"] ||
profileRootDev["size.state"] != rootDev["size.state"] {
localDevices[rootDevKey] = rootDev
}
}
Expand All @@ -549,12 +563,12 @@ func instancePostMigration(s *state.State, inst instance.Instance, newName strin
BaseImage: localConfig["volatile.base_image"],
Config: localConfig,
Devices: localDevices,
Profiles: apiProfiles,
Project: newProject,
Type: inst.Type(),
Architecture: inst.Architecture(),
Description: inst.Description(),
Ephemeral: inst.IsEphemeral(),
Profiles: inst.Profiles(),
Stateful: inst.IsStateful(),
}

Expand Down
11 changes: 8 additions & 3 deletions doc/api-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -820,7 +820,7 @@ This effectively gives us:
* `volatile.idmap.next` => Next on-disk idmap

This is required to implement environments where the on-disk map isn't
changed but the kernel map is (e.g. `shiftfs`).
changed but the kernel map is (e.g. `idmapped mounts`).

## `event_location`

Expand Down Expand Up @@ -909,7 +909,7 @@ elevated permissions.

## `container_disk_shift`

Adds the `shift` property on `disk` devices which controls the use of the `shiftfs` overlay.
Adds the `shift` property on `disk` devices which controls the use of the `idmapped mounts` overlay.

## `storage_shifted`

Expand All @@ -919,7 +919,7 @@ Setting it to `true` will allow multiple isolated containers to attach the
same storage volume while keeping the file system writable from all of
them.

This makes use of `shiftfs` as an overlay file system.
This makes use of `idmapped mounts` as an overlay file system.

## `resources_infiniband`

Expand Down Expand Up @@ -2287,3 +2287,8 @@ This introduces a new `io.bus` property to disk devices which can be used to ove

## `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.

## `instance_move_config`

This API extension provides the ability to use flags `--profile`, `--no-profile`, `--device`, and `--config`
when moving an instance between projects and/or storage pools.
2 changes: 1 addition & 1 deletion doc/cloud-init.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ config:
```

```{tip}
See {doc}`cloud-init:howto/debug_user_data` for information on how to check whether the syntax is correct.
See {doc}`How to validate user data cloud configuration <cloud-init:howto/debug_user_data>` for information on how to check whether the syntax is correct.
```

## How to check the `cloud-init` status
Expand Down
5 changes: 4 additions & 1 deletion doc/config_options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,7 @@ Specify the mounts of a given file system that should be redirected to their FUS
:condition: "container"
:defaultdesc: "`false`"
:liveupdate: "yes"
:shortdesc: "Whether to mount `shiftfs` on top of file systems handled through mount syscall interception"
:shortdesc: "Whether to use idmapped mounts for syscall interception"
:type: "bool"

```
Expand Down Expand Up @@ -1409,6 +1409,7 @@ See {ref}`server-expose`.
```

```{config:option} core.https_allowed_credentials server-core
:defaultdesc: "`false`"
:scope: "global"
:shortdesc: "Whether to set `Access-Control-Allow-Credentials`"
:type: "bool"
Expand Down Expand Up @@ -1505,13 +1506,15 @@ See {ref}`howto-storage-buckets`.
```

```{config:option} core.syslog_socket server-core
:defaultdesc: "`false`"
:scope: "local"
:shortdesc: "Whether to enable the syslog unixgram socket listener"
:type: "bool"
Set this option to `true` to enable the syslog unixgram socket to receive log messages from external processes.
```

```{config:option} core.trust_ca_certificates server-core
:defaultdesc: "`false`"
:scope: "global"
:shortdesc: "Whether to automatically trust clients signed by the CA"
:type: "bool"
Expand Down
1 change: 0 additions & 1 deletion doc/environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,4 @@ Name | Description
`INCUS_LXC_TEMPLATE_CONFIG` | Path to the LXC template configuration directory
`INCUS_OVMF_PATH` | Path to an OVMF build including `OVMF_CODE.fd` and `OVMF_VARS.ms.fd`
`INCUS_SECURITY_APPARMOR` | If set to `false`, forces AppArmor off
`INCUS_SHIFTFS_DISABLE` | Disable `shiftfs` support (useful when testing traditional UID shifting)
`INCUS_UI` | Path to the web UI to serve through the web server
2 changes: 1 addition & 1 deletion doc/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ For unprivileged containers, you need to make sure that the user in the containe
Otherwise, all files will show up as the overflow UID/GID (`65536:65536`) and access to anything that's not world-readable will fail.
Use either of the following methods to grant the required permissions:

- Pass `shift=true` to the [`incus config device add`](incus_config_device_add.md) call. This depends on the kernel and file system supporting either idmapped mounts or shiftfs (see [`incus info`](incus_info.md)).
- Pass `shift=true` to the [`incus config device add`](incus_config_device_add.md) call. This depends on the kernel and file system supporting either idmapped mounts (see [`incus info`](incus_info.md)).
- Add a `raw.idmap` entry (see [Idmaps for user namespace](userns-idmap.md)).
- Place recursive POSIX ACLs on your home directory.

Expand Down
2 changes: 1 addition & 1 deletion doc/howto/instances_create.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,6 @@ Once you're done in the serial console, you need to disconnect from the console

You should now see the installer. After the installation is done, you need to detach the custom ISO volume:

incus storage volume detach default iso-volume iso-vm
incus storage volume detach <pool> iso-volume iso-vm

Now the VM can be rebooted, and it will boot from disk.
Loading
Loading