Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 15 additions & 0 deletions cmd/incusd/api_1.0.go
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,13 @@ func doApi10Update(d *Daemon, r *http.Request, req api.ServerPut, patch bool) re
}
}

if nodeValues["storage.logs_volume"] != "" && nodeValues["storage.logs_volume"] != newNodeConfig.StorageLogsVolume() {
err := daemonStorageValidate(s, nodeValues["storage.logs_volume"])
if err != nil {
return fmt.Errorf("Failed validation of %q: %w", "storage.logs_volume", err)
}
}

if patch {
nodeChanged, err = newNodeConfig.Patch(nodeValues)
} else {
Expand Down Expand Up @@ -950,6 +957,14 @@ func doApi10UpdateTriggers(d *Daemon, nodeChanged, clusterChanged map[string]str
}
}

value, ok = nodeChanged["storage.logs_volume"]
if ok {
err := daemonStorageMove(s, "logs", value)
if err != nil {
return err
}
}

// Apply larger changes.
if acmeChanged {
err := autoRenewCertificate(s.ShutdownCtx, d, true)
Expand Down
90 changes: 79 additions & 11 deletions cmd/incusd/daemon_storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
func daemonStorageVolumesUnmount(s *state.State) error {
var storageBackups string
var storageImages string
var storageLogs string

err := s.DB.Node.Transaction(context.Background(), func(ctx context.Context, tx *db.NodeTx) error {
nodeConfig, err := node.ConfigLoad(ctx, tx)
Expand All @@ -31,6 +32,7 @@ func daemonStorageVolumesUnmount(s *state.State) error {

storageBackups = nodeConfig.StorageBackupsVolume()
storageImages = nodeConfig.StorageImagesVolume()
storageLogs = nodeConfig.StorageLogsVolume()

return nil
})
Expand Down Expand Up @@ -73,12 +75,20 @@ func daemonStorageVolumesUnmount(s *state.State) error {
}
}

if storageLogs != "" {
err := unmount("logs", storageLogs)
if err != nil {
return fmt.Errorf("Failed to unmount logs storage: %w", err)
}
}

return nil
}

func daemonStorageMount(s *state.State) error {
var storageBackups string
var storageImages string
var storageLogs string
err := s.DB.Node.Transaction(context.Background(), func(ctx context.Context, tx *db.NodeTx) error {
nodeConfig, err := node.ConfigLoad(ctx, tx)
if err != nil {
Expand All @@ -87,6 +97,7 @@ func daemonStorageMount(s *state.State) error {

storageBackups = nodeConfig.StorageBackupsVolume()
storageImages = nodeConfig.StorageImagesVolume()
storageLogs = nodeConfig.StorageLogsVolume()

return nil
})
Expand Down Expand Up @@ -138,6 +149,13 @@ func daemonStorageMount(s *state.State) error {
}
}

if storageLogs != "" {
err := mount("logs", storageLogs)
if err != nil {
return fmt.Errorf("Failed to mount logs storage: %w", err)
}
}

return nil
}

Expand Down Expand Up @@ -241,6 +259,8 @@ func daemonStorageValidate(s *state.State, target string) error {
}

func daemonStorageMove(s *state.State, storageType string, target string) error {
isLogs := storageType == "logs"
sysLogDir := internalUtil.LogPath()
destPath := internalUtil.VarPath(storageType)

// Track down the current storage.
Expand Down Expand Up @@ -279,6 +299,33 @@ func daemonStorageMove(s *state.State, storageType string, target string) error
return nil
}

moveInstanceDirs := func(source string, target string) error {
entries, err := os.ReadDir(source)
if err != nil {
return err
}

for _, entry := range entries {
if !entry.IsDir() {
continue
}

src := filepath.Join(source, entry.Name())
dst := filepath.Join(target, entry.Name())
_, err := rsync.LocalCopy(src, dst, "", false)
if err != nil {
return err
}

err = os.RemoveAll(src)
if err != nil {
return err
}
}

return nil
}

// Deal with unsetting.
if target == "" {
// Things already look correct.
Expand All @@ -292,16 +339,29 @@ func daemonStorageMove(s *state.State, storageType string, target string) error
return fmt.Errorf("Failed to delete storage symlink at %q: %w", destPath, err)
}

// Re-create as a directory.
err = os.MkdirAll(destPath, 0o700)
if err != nil {
return fmt.Errorf("Failed to create directory %q: %w", destPath, err)
}
if isLogs {
// Ensure system log dir exists and move instance dirs back there.
err = os.MkdirAll(sysLogDir, 0o700)
if err != nil {
return fmt.Errorf("Failed to create system log directory %q: %w", sysLogDir, err)
}

// Move the data across.
err = moveContent(sourcePath, destPath)
if err != nil {
return fmt.Errorf("Failed to move data over to directory %q: %w", destPath, err)
err = moveInstanceDirs(sourcePath, sysLogDir)
if err != nil {
return fmt.Errorf("Failed to move instance logs back to %q: %w", sysLogDir, err)
}
} else {
// Re-create as a directory.
err = os.MkdirAll(destPath, 0o700)
if err != nil {
return fmt.Errorf("Failed to create directory %q: %w", destPath, err)
}

// Move the data across.
err = moveContent(sourcePath, destPath)
if err != nil {
return fmt.Errorf("Failed to move data over to directory %q: %w", destPath, err)
}
}

pool, err := storagePools.LoadByName(s, sourcePool)
Expand Down Expand Up @@ -366,7 +426,11 @@ func daemonStorageMove(s *state.State, storageType string, target string) error
}

// Move the data across.
err = moveContent(sourcePath, destPath)
if isLogs {
err = moveInstanceDirs(sourcePath, destPath)
} else {
err = moveContent(sourcePath, destPath)
}
if err != nil {
return fmt.Errorf("Failed to move data over to directory %q: %w", destPath, err)
}
Expand Down Expand Up @@ -401,7 +465,11 @@ func daemonStorageMove(s *state.State, storageType string, target string) error
}

// Move the data across.
err = moveContent(sourcePath, destPath)
if isLogs {
err = moveInstanceDirs(sysLogDir, destPath)
} else {
err = moveContent(sourcePath, destPath)
}
if err != nil {
return fmt.Errorf("Failed to move data over to directory %q: %w", destPath, err)
}
Expand Down
8 changes: 8 additions & 0 deletions internal/server/metadata/configuration.json
Original file line number Diff line number Diff line change
Expand Up @@ -5727,6 +5727,14 @@
"type": "string"
}
},
{
"storage.logs_volume": {
"longdesc": "Specify the volume using the syntax `POOL/VOLUME`.",
"scope": "local",
"shortdesc": "Volume to use to store instance log directories",
"type": "string"
}
},
{
"storage.linstor.ca_cert": {
"longdesc": "",
Expand Down
15 changes: 14 additions & 1 deletion internal/server/node/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ func (c *Config) StorageImagesVolume() string {
return c.m.GetString("storage.images_volume")
}

// StorageLogsVolume returns the name of the pool/volume to use for storing instance log directories.
func (c *Config) StorageLogsVolume() string {
return c.m.GetString("storage.logs_volume")
}

// LinstorSatelliteName returns the LINSTOR satellite name override.
func (c *Config) LinstorSatelliteName() string {
return c.m.GetString("storage.linstor.satellite.name")
Expand Down Expand Up @@ -261,7 +266,7 @@ var ConfigSchema = config.Schema{
// shortdesc: OVS socket path
"network.ovs.connection": {Default: "unix:/run/openvswitch/db.sock"},

// Storage volumes to store backups/images on
// Storage volumes to store backups/images/logs on

// gendoc:generate(entity=server, group=miscellaneous, key=storage.backups_volume)
// Specify the volume using the syntax `POOL/VOLUME`.
Expand All @@ -279,6 +284,14 @@ var ConfigSchema = config.Schema{
// shortdesc: Volume to use to store the image tarballs
"storage.images_volume": {},

// gendoc:generate(entity=server, group=miscellaneous, key=storage.logs_volume)
// Specify the volume using the syntax `POOL/VOLUME`.
// ---
// type: string
// scope: local
// shortdesc: Volume to use to store instance log directories
"storage.logs_volume": {},

// LINSTOR

// gendoc:generate(entity=server, group=miscellaneous, key=storage.linstor.satellite.name)
Expand Down
Loading