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

Add linstor storage driver #1621

Merged
merged 43 commits into from
Mar 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
d40571b
api: storage_driver_linstor
bensmrs Feb 6, 2025
f62f705
doc: Linstor driver documentation
luissimas Mar 13, 2025
f77fa5c
incusd/cluster/config: Add cluster-level LINSTOR config options
luissimas Jan 29, 2025
f9d3a55
incusd/node/config: Add node-level LINSTOR config options
bensmrs Feb 6, 2025
fb6be04
incusd/migration: Add linstor to migration protobuf
luissimas Mar 25, 2025
8f90a2e
incusd/storage/linstor: Linstor client setup
luissimas Jan 29, 2025
126d0ec
incusd/storage/linstor: Wrap golinstor logger
bensmrs Mar 15, 2025
c043d29
incusd/state: Add Linstor client to server state
luissimas Jan 29, 2025
485283c
incusd: Trigger Linstor client setup on server config changes
luissimas Jan 29, 2025
9cc1040
incusd/storage/linstor: Storage pool creation and deletion
luissimas Jan 29, 2025
09548bd
incusd/storage/linstor: Volume creation and deletion
winiciusallan Jan 28, 2025
5273af8
incusd/storage: Implement the Filler function for Linstor volumes
winiciusallan Jan 30, 2025
8f93d6f
incusd/storage/linstor: Add mount and unmount support for volumes
luissimas Jan 30, 2025
a86d6c4
incusd/storage/linstor: Add trivial renaming support for volumes
bensmrs Feb 7, 2025
0471e14
incusd/storage/linstor: Remove filesystem volume and mount path
winiciusallan Jan 30, 2025
7da1141
incusd/storage/linstor: Add common volume config validation
luissimas Jan 31, 2025
53af2a9
incusd/storage/linstor: Copy volumes on the same pool
luissimas Feb 7, 2025
b8b71fa
incusd/storage/linstor: Add basic snapshot support
luissimas Feb 7, 2025
6d7beb0
incusd/storage/linstor: Add support for mounting and unmounting snaps…
luissimas Feb 10, 2025
93ae977
incusd/storage/linstor: Add support for resizing volumes
luissimas Feb 11, 2025
7fee8e4
incusd/storage/linstor: Add basic support for same-pool migration
luissimas Feb 11, 2025
b34e65b
incusd/storage/linstor: Add support for optimized images
luissimas Feb 13, 2025
2639813
incusd/storage/linstor: List snapshots to support non optimized copyi…
luissimas Feb 17, 2025
79cad37
incusd/storage/linstor: Implement GetResources
bensmrs Feb 17, 2025
638b042
incusd/storage/linstor: Add support for updating storage pool config
luissimas Feb 18, 2025
1c85bc8
incusd/storage/linstor: Add support for generic refreshing of volumes
luissimas Feb 18, 2025
803afa2
incusd/storage/linstor: Add support for fetching volume disk usage
luissimas Feb 18, 2025
e2b000e
incusd/storage/linstor: Add support for non optimized backups
luissimas Feb 18, 2025
bee4434
incusd/storage/linstor: Encode snapshot names in metadata
bensmrs Feb 18, 2025
b3926a0
incusd/storage/linstor: Add support for listing volumes
luissimas Feb 19, 2025
12f8874
incusd/storage/linstor: Use aux property to map incus names to linsto…
luissimas Feb 20, 2025
3a3bf61
incusd/storage/linstor: Ensure volumes are available locally when ret…
luissimas Feb 21, 2025
4cefc5d
incusd/storage/linstor: Fallback to generic migration implementation
luissimas Feb 27, 2025
9aef3c8
incusd/storage/linstor: Freeze and sync filesystem when snapshotting …
luissimas Mar 12, 2025
57371f6
incusd/storage/linstor: Cleanup temporary resource definitions from s…
luissimas Mar 12, 2025
05865f0
incusd/storage/linstor: Delete diskless resources when unmounting vol…
luissimas Mar 13, 2025
714cfd8
incusd/storage/linstor: Add drbd resource config options
luissimas Mar 14, 2025
7e061ea
tests: Add standalone LINSTOR tests
bensmrs Feb 11, 2025
5e6edb4
tests: Add clustered LINSTOR tests
bensmrs Feb 12, 2025
f515ade
internal/migration: Update protobuf
luissimas Feb 11, 2025
038d9c4
doc: Update configs
luissimas Jan 31, 2025
1c0fdc0
gomod: Tidy dependencies and add LINSTOR
bensmrs Mar 13, 2025
809c559
github: Add LINSTOR CI tests
bensmrs Feb 12, 2025
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
46 changes: 45 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ jobs:
- lvm
- zfs
- ceph
- linstor
- random
os:
- ubuntu-22.04
Expand Down Expand Up @@ -391,6 +392,48 @@ jobs:
sudo microceph.ceph status
sudo rm -f /snap/bin/rbd

- name: Setup LINSTOR
if: ${{ matrix.backend == 'linstor' }}
run: |
set -x

# As with Ceph, we hope for a spare disk.
if [ "$(stat -c '%d' /)" = "$(stat -c '%d' /mnt)" ]; then
echo "FAIL: rootfs and ephemeral part on the same disk, aborting"
exit 1
fi

sudo add-apt-repository ppa:linbit/linbit-drbd9-stack -y

# Install everything required to compile DRBD and run LINSTOR tools.
sudo apt-get install --no-install-recommends -y \
drbd-dkms \
linstor-client \
linstor-controller \
linstor-satellite \
linux-headers-generic \
python3-setuptools

# Enable DRBD.
sudo modprobe -r drbd
sudo modprobe drbd

# Get the runner IP.
runner_ip="$(hostname -I | cut -d' ' -f1)"

# Create a single local node.
sudo linstor node create local "${runner_ip}" --node-type combined

# Repurpose the ephemeral disk for LINSTOR physical storage.
sudo swapoff /mnt/swapfile
ephemeral_disk="$(findmnt --noheadings --output SOURCE --target /mnt | sed 's/[0-9]\+$//')"
sudo umount /mnt
sudo wipefs -a "${ephemeral_disk}"
sudo linstor physical-storage create-device-pool --storage-pool incus --pool-name linstor-incus lvmthin local "${ephemeral_disk}"

# Update the runner env.
echo "INCUS_LINSTOR_CLUSTER=${runner_ip}" >> "$GITHUB_ENV"

- name: "Ensure offline mode (block image server)"
run: |
sudo nft add table inet filter
Expand All @@ -404,6 +447,7 @@ jobs:
INCUS_CEPH_CLUSTER: "ceph"
INCUS_CEPH_CEPHFS: "cephfs"
INCUS_CEPH_CEPHOBJECT_RADOSGW: "http://127.0.0.1"
INCUS_LINSTOR_LOCAL_SATELLITE: "local"
INCUS_CONCURRENT: "1"
INCUS_VERBOSE: "1"
INCUS_OFFLINE: "1"
Expand All @@ -413,7 +457,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_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_LINSTOR_LOCAL_SATELLITE,INCUS_LINSTOR_CLUSTER,INCUS_OFFLINE,INCUS_SKIP_TESTS,INCUS_REQUIRED_TESTS, INCUS_BACKEND=${{ matrix.backend }} ./main.sh ${{ matrix.suite }}

client:
name: Client
Expand Down
11 changes: 11 additions & 0 deletions cmd/incusd/api_1.0.go
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,7 @@ func doApi10UpdateTriggers(d *Daemon, nodeChanged, clusterChanged map[string]str
oidcChanged := false
openFGAChanged := false
ovnChanged := false
linstorChanged := false
ovsChanged := false
syslogChanged := false

Expand Down Expand Up @@ -835,6 +836,9 @@ func doApi10UpdateTriggers(d *Daemon, nodeChanged, clusterChanged map[string]str

case "openfga.api.url", "openfga.api.token", "openfga.store.id":
openFGAChanged = true

case "storage.linstor.controller_connection", "storage.linstor.ca_cert", "storage.linstor.client_cert", "storage.linstor.client_key":
linstorChanged = true
}
}

Expand Down Expand Up @@ -1002,6 +1006,13 @@ func doApi10UpdateTriggers(d *Daemon, nodeChanged, clusterChanged map[string]str
}
}

if linstorChanged {
err := d.setupLinstor()
if err != nil {
return err
}
}

// Compile and load the instance placement scriptlet.
value, ok = clusterChanged["instances.placement.scriptlet"]
if ok {
Expand Down
43 changes: 43 additions & 0 deletions cmd/incusd/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import (
"github.com/lxc/incus/v6/internal/server/state"
storagePools "github.com/lxc/incus/v6/internal/server/storage"
storageDrivers "github.com/lxc/incus/v6/internal/server/storage/drivers"
"github.com/lxc/incus/v6/internal/server/storage/linstor"
"github.com/lxc/incus/v6/internal/server/storage/s3/miniod"
"github.com/lxc/incus/v6/internal/server/sys"
"github.com/lxc/incus/v6/internal/server/syslog"
Expand Down Expand Up @@ -172,6 +173,10 @@ type Daemon struct {

// API info.
apiExtensions int

// Linstor client.
linstor *linstor.Client
linstorMu sync.Mutex
}

// DaemonConfig holds configuration values for Daemon.
Expand Down Expand Up @@ -604,6 +609,7 @@ func (d *Daemon) State() *state.State {
OS: d.os,
OVN: d.getOVN,
OVS: d.getOVS,
Linstor: d.getLinstor,
Proxy: d.proxy,
ServerCert: d.serverCert,
ServerClustered: d.serverClustered,
Expand Down Expand Up @@ -2732,3 +2738,40 @@ func (d *Daemon) getOVS() (*ovs.VSwitch, error) {

return d.ovs, nil
}

func (d *Daemon) setupLinstor() error {
d.linstorMu.Lock()
defer d.linstorMu.Unlock()

// Clear any existing client.
d.linstor = nil

// Get the Linstor controller connection string.
controllerConnection := d.globalConfig.LinstorControllerConnection()

// Get the SSL certificates if needed.
sslCACert, sslClientCert, sslClientKey := d.globalConfig.LinstorSSL()

// Get Linstor client.
client, err := linstor.NewClient(controllerConnection, sslCACert, sslClientCert, sslClientKey)
if err != nil {
return fmt.Errorf("Failed to connect to Linstor: %w", err)
}

// Set the client.
d.linstor = client

return nil
}

func (d *Daemon) getLinstor() (*linstor.Client, error) {
// Setup the client if it does not exist.
if d.linstor == nil {
err := d.setupLinstor()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m seeing a small race condition leading to a double execution of setupLinstor:

  • getLinstor gets called until after the d.linstor == nil condition is checked
  • setupLinstor gets called before err := d.setupLinstor() gets called
  • setupLinstor gets called again by err := d.setupLinstor().

I’d put a synchronization here…

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

return d.linstor, nil
}
11 changes: 11 additions & 0 deletions doc/.wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,15 @@ DHCPv
Diátaxis
Diffie
Distrobuilder
diskful
Diskless
diskless
DNS
dnsmasq
DNSSEC
DoS
DRM
DRBD
EB
Ebit
eBPF
Expand Down Expand Up @@ -104,8 +108,10 @@ GPUs
Grafana
HAProxy
hardcoded
HDDs
Hellman
Homebrew
hostname
hotplug
hotplugged
hotplugging
Expand Down Expand Up @@ -151,6 +157,9 @@ LXCFS
LXC's
LXD
LXD's
LINBIT
LINSTOR
LINSTOR's
macOS
macvlan
Makefile
Expand Down Expand Up @@ -178,6 +187,7 @@ NIC
NICs
NixOS
NUMA
NVMe
NVRAM
OCI
OData
Expand Down Expand Up @@ -212,6 +222,7 @@ PKI
PNG
Pongo
POSIX
PPA
pre
preseed
proxied
Expand Down
4 changes: 4 additions & 0 deletions doc/api-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2760,3 +2760,7 @@ This introduces a new `io.bus` property for compatible network devices allowing
## `disk_io_bus_usb`

Adds a new `usb` value for `io.bus` on `disk` devices.

## `storage_driver_linstor`

This adds a LINSTOR storage driver.
35 changes: 35 additions & 0 deletions doc/config_options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2603,6 +2603,41 @@ Specify the volume using the syntax `POOL/VOLUME`.
Specify the volume using the syntax `POOL/VOLUME`.
```

```{config:option} storage.linstor.ca_cert server-miscellaneous
:scope: "global"
:shortdesc: "LINSTOR SSL certificate authority"
:type: "string"

```

```{config:option} storage.linstor.client_cert server-miscellaneous
:scope: "global"
:shortdesc: "LINSTOR SSL client certificate"
:type: "string"

```

```{config:option} storage.linstor.client_key server-miscellaneous
:scope: "global"
:shortdesc: "LINSTOR SSL client key"
:type: "string"

```

```{config:option} storage.linstor.controller_connection server-miscellaneous
:scope: "global"
:shortdesc: "LINSTOR controller connection string"
:type: "string"

```

```{config:option} storage.linstor.satellite.name server-miscellaneous
:scope: "global"
:shortdesc: "LINSTOR satellite node name override"
:type: "string"
Set this option to the name of the local LINSTOR satellite node, should it be different from the Incus server name.
```

<!-- config group server-miscellaneous end -->
<!-- config group server-oidc start -->
```{config:option} oidc.audience server-oidc
Expand Down
16 changes: 9 additions & 7 deletions doc/explanation/storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ The following storage drivers are supported:
- [Ceph RBD - `ceph`](storage-ceph)
- [CephFS - `cephfs`](storage-cephfs)
- [Ceph Object - `cephobject`](storage-cephobject)
- [LINSTOR - `linstor`](storage-linstor)

See the following how-to guides for additional information:

Expand All @@ -36,12 +37,12 @@ See the following how-to guides for additional information:
Where the Incus data is stored depends on the configuration and the selected storage driver.
Depending on the storage driver that is used, Incus can either share the file system with its host or keep its data separate.

Storage location | Directory | Btrfs | LVM (all) | ZFS | Ceph (all) |
:--- | :-: | :-: | :-: | :-: | :-: |
Shared with the host | &#x2713; | &#x2713; | - | &#x2713; | - |
Dedicated disk/partition | - | &#x2713; | &#x2713; | &#x2713; | - |
Loop disk | - | &#x2713; | &#x2713; | &#x2713; | - |
Remote storage | - | - | &#x2713; | - | &#x2713; |
Storage location | Directory | Btrfs | LVM (all) | ZFS | Ceph (all) | LINSTOR |
:--- | :-: | :-: | :-: | :-: | :-: | :-: |
Shared with the host | &#x2713; | &#x2713; | - | &#x2713; | - | - |
Dedicated disk/partition | - | &#x2713; | &#x2713; | &#x2713; | - | &#x2713; |
Loop disk | - | &#x2713; | &#x2713; | &#x2713; | - | &#x2713; |
Remote storage | - | - | &#x2713; | - | &#x2713; | &#x2713; |

#### Shared with the host

Expand All @@ -54,7 +55,7 @@ This option is supported for the `dir` driver, the `btrfs` driver (if the host i

Having Incus use an empty partition on your main disk or a full dedicated disk keeps its storage completely independent from the host.

This option is supported for the `btrfs` driver, the `lvm` driver and the `zfs` driver.
This option is supported for the `btrfs` driver, the `lvm` driver, the `zfs` driver and the `linstor` driver.

#### Loop disk

Expand All @@ -72,6 +73,7 @@ You can increase their size though; see {ref}`storage-resize-pool`.

The `ceph`, `cephfs` and `cephobject` drivers store the data in a completely independent Ceph storage cluster that must be set up separately.
The `lvmcluster` driver relies on a shared block device being available to all cluster members and on a pre-existing `lvmlockd` setup.
The `linstor` driver stores the data in a LINSTOR storage cluster that must be setup separately.

(storage-default-pool)=
### Default storage pool
Expand Down
Loading
Loading