2018-03-05 12:59:47 +01:00
|
|
|
/*
|
2019-04-03 10:46:15 +02:00
|
|
|
Copyright 2018 The Ceph-CSI Authors.
|
2018-03-05 12:59:47 +01:00
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2021-09-16 19:17:57 +05:30
|
|
|
package core
|
2018-03-05 12:59:47 +01:00
|
|
|
|
|
|
|
import (
|
2019-08-22 13:19:06 -04:00
|
|
|
"context"
|
2020-11-30 15:35:39 +05:30
|
|
|
"errors"
|
2020-10-10 18:17:08 +02:00
|
|
|
"fmt"
|
2019-07-10 10:50:04 +00:00
|
|
|
"path"
|
2019-06-08 05:06:03 +00:00
|
|
|
"strings"
|
2023-10-09 11:38:04 +05:30
|
|
|
"sync"
|
2019-01-28 19:29:16 +05:30
|
|
|
|
2021-08-25 12:16:03 +05:30
|
|
|
cerrors "github.com/ceph/ceph-csi/internal/cephfs/errors"
|
2021-09-16 19:17:57 +05:30
|
|
|
fsutil "github.com/ceph/ceph-csi/internal/cephfs/util"
|
2020-04-17 11:23:49 +02:00
|
|
|
"github.com/ceph/ceph-csi/internal/util"
|
2021-08-24 17:03:25 +02:00
|
|
|
"github.com/ceph/ceph-csi/internal/util/log"
|
2020-08-05 16:31:22 +02:00
|
|
|
|
|
|
|
fsAdmin "github.com/ceph/go-ceph/cephfs/admin"
|
2020-11-30 15:35:39 +05:30
|
|
|
"github.com/ceph/go-ceph/rados"
|
2018-03-09 17:05:19 +01:00
|
|
|
)
|
|
|
|
|
2023-10-09 11:38:04 +05:30
|
|
|
var (
|
|
|
|
// clusterAdditionalInfo contains information regarding if resize is
|
|
|
|
// supported in the particular cluster and subvolumegroup is
|
|
|
|
// created or not.
|
|
|
|
// Subvolumegroup creation and volume resize decisions are
|
|
|
|
// taken through this additional cluster information.
|
|
|
|
clusterAdditionalInfo = make(map[string]*localClusterState)
|
|
|
|
// clusterAdditionalInfoMutex is used to protect against
|
|
|
|
// concurrent writes.
|
|
|
|
clusterAdditionalInfoMutex = sync.Mutex{}
|
|
|
|
)
|
2018-03-22 14:01:10 +01:00
|
|
|
|
2020-10-10 18:17:08 +02:00
|
|
|
// Subvolume holds subvolume information. This includes only the needed members
|
|
|
|
// from fsAdmin.SubVolumeInfo.
|
2020-08-04 09:51:31 +05:30
|
|
|
type Subvolume struct {
|
2020-10-10 18:17:08 +02:00
|
|
|
BytesQuota int64
|
|
|
|
Path string
|
|
|
|
Features []string
|
2020-08-04 09:51:31 +05:30
|
|
|
}
|
|
|
|
|
2022-02-15 17:41:09 +05:30
|
|
|
// SubVolumeClient is the interface that holds the signature of subvolume methods
|
|
|
|
// that interacts with CephFS subvolume API's.
|
2023-06-02 14:36:39 +02:00
|
|
|
//
|
|
|
|
//nolint:interfacebloat // SubVolumeClient has more than 10 methods, that is ok.
|
2022-02-15 17:41:09 +05:30
|
|
|
type SubVolumeClient interface {
|
|
|
|
// GetVolumeRootPathCeph returns the root path of the subvolume.
|
|
|
|
GetVolumeRootPathCeph(ctx context.Context) (string, error)
|
|
|
|
// CreateVolume creates a subvolume.
|
|
|
|
CreateVolume(ctx context.Context) error
|
|
|
|
// GetSubVolumeInfo returns the subvolume information.
|
|
|
|
GetSubVolumeInfo(ctx context.Context) (*Subvolume, error)
|
|
|
|
// ExpandVolume expands the volume if the requested size is greater than
|
|
|
|
// the subvolume size.
|
|
|
|
ExpandVolume(ctx context.Context, bytesQuota int64) error
|
|
|
|
// ResizeVolume resizes the volume.
|
|
|
|
ResizeVolume(ctx context.Context, bytesQuota int64) error
|
|
|
|
// PurgSubVolume removes the subvolume.
|
|
|
|
PurgeVolume(ctx context.Context, force bool) error
|
|
|
|
|
|
|
|
// CreateCloneFromSubVolume creates a clone from the subvolume.
|
|
|
|
CreateCloneFromSubvolume(ctx context.Context, parentvolOpt *SubVolume) error
|
|
|
|
// GetCloneState returns the clone state of the subvolume.
|
2024-10-28 11:39:13 +05:30
|
|
|
GetCloneState(ctx context.Context) (*cephFSCloneState, error)
|
2022-02-15 17:41:09 +05:30
|
|
|
// CreateCloneFromSnapshot creates a clone from the subvolume snapshot.
|
|
|
|
CreateCloneFromSnapshot(ctx context.Context, snap Snapshot) error
|
|
|
|
// CleanupSnapshotFromSubvolume removes the snapshot from the subvolume.
|
|
|
|
CleanupSnapshotFromSubvolume(ctx context.Context, parentVol *SubVolume) error
|
2022-05-24 19:31:35 +05:30
|
|
|
|
|
|
|
// SetAllMetadata set all the metadata from arg parameters on Ssubvolume.
|
|
|
|
SetAllMetadata(parameters map[string]string) error
|
|
|
|
// UnsetAllMetadata unset all the metadata from arg keys on subvolume.
|
|
|
|
UnsetAllMetadata(keys []string) error
|
2022-02-15 17:41:09 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
// subVolumeClient implements SubVolumeClient interface.
|
|
|
|
type subVolumeClient struct {
|
2022-07-28 17:35:33 +05:30
|
|
|
*SubVolume // Embedded SubVolume struct.
|
|
|
|
clusterID string // Cluster ID to check subvolumegroup and resize functionality.
|
|
|
|
clusterName string // Cluster name
|
|
|
|
enableMetadata bool // Set metadata on volume
|
|
|
|
conn *util.ClusterConnection // Cluster connection.
|
2022-02-15 17:41:09 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
// SubVolume holds the information about the subvolume.
|
|
|
|
type SubVolume struct {
|
|
|
|
VolID string // subvolume id.
|
|
|
|
FsName string // filesystem name.
|
|
|
|
SubvolumeGroup string // subvolume group name where subvolume will be created.
|
2024-10-03 16:04:22 +05:30
|
|
|
RadosNamespace string // rados namespace where omap data will be stored.
|
2022-02-15 17:41:09 +05:30
|
|
|
Pool string // pool name where subvolume will be created.
|
|
|
|
Features []string // subvolume features.
|
|
|
|
Size int64 // subvolume size.
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewSubVolume returns a new subvolume client.
|
2022-07-28 17:35:33 +05:30
|
|
|
func NewSubVolume(
|
|
|
|
conn *util.ClusterConnection,
|
|
|
|
vol *SubVolume,
|
|
|
|
clusterID,
|
|
|
|
clusterName string,
|
|
|
|
setMetadata bool,
|
|
|
|
) SubVolumeClient {
|
2022-02-15 17:41:09 +05:30
|
|
|
return &subVolumeClient{
|
2022-07-28 17:35:33 +05:30
|
|
|
SubVolume: vol,
|
|
|
|
clusterID: clusterID,
|
|
|
|
clusterName: clusterName,
|
|
|
|
enableMetadata: setMetadata,
|
|
|
|
conn: conn,
|
2022-02-15 17:41:09 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetVolumeRootPathCephDeprecated returns the root path of the subvolume.
|
2021-09-16 19:17:57 +05:30
|
|
|
func GetVolumeRootPathCephDeprecated(volID fsutil.VolumeID) string {
|
2019-07-10 10:50:04 +00:00
|
|
|
return path.Join("/", "csi-volumes", string(volID))
|
|
|
|
}
|
|
|
|
|
2022-02-15 17:41:09 +05:30
|
|
|
// GetVolumeRootPathCeph returns the root path of the subvolume.
|
|
|
|
func (s *subVolumeClient) GetVolumeRootPathCeph(ctx context.Context) (string, error) {
|
|
|
|
fsa, err := s.conn.GetFSAdmin()
|
2019-06-08 05:06:03 +00:00
|
|
|
if err != nil {
|
2021-08-24 17:03:25 +02:00
|
|
|
log.ErrorLog(ctx, "could not get FSAdmin err %s", err)
|
2021-07-22 11:15:17 +05:30
|
|
|
|
2020-11-26 13:55:48 +05:30
|
|
|
return "", err
|
|
|
|
}
|
2022-02-15 17:41:09 +05:30
|
|
|
svPath, err := fsa.SubVolumePath(s.FsName, s.SubvolumeGroup, s.VolID)
|
2020-11-26 13:55:48 +05:30
|
|
|
if err != nil {
|
2022-02-15 17:41:09 +05:30
|
|
|
log.ErrorLog(ctx, "failed to get the rootpath for the vol %s: %s", s.VolID, err)
|
2020-11-30 15:35:39 +05:30
|
|
|
if errors.Is(err, rados.ErrNotFound) {
|
2024-05-07 15:51:05 -07:00
|
|
|
return "", fmt.Errorf("Failed as %w (internal %w)", cerrors.ErrVolumeNotFound, err)
|
2020-01-22 21:48:46 -05:00
|
|
|
}
|
2021-07-22 11:15:17 +05:30
|
|
|
|
2019-06-08 05:06:03 +00:00
|
|
|
return "", err
|
|
|
|
}
|
2021-07-22 11:15:17 +05:30
|
|
|
|
2020-11-26 13:55:48 +05:30
|
|
|
return svPath, nil
|
2018-04-13 14:49:49 +02:00
|
|
|
}
|
|
|
|
|
2022-02-15 17:41:09 +05:30
|
|
|
// GetSubVolumeInfo returns the subvolume information.
|
|
|
|
func (s *subVolumeClient) GetSubVolumeInfo(ctx context.Context) (*Subvolume, error) {
|
|
|
|
fsa, err := s.conn.GetFSAdmin()
|
2020-10-10 18:17:08 +02:00
|
|
|
if err != nil {
|
2022-02-15 17:41:09 +05:30
|
|
|
log.ErrorLog(ctx, "could not get FSAdmin, can not fetch metadata pool for %s:", s.FsName, err)
|
2021-07-22 11:15:17 +05:30
|
|
|
|
2020-10-10 18:17:08 +02:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-02-15 17:41:09 +05:30
|
|
|
info, err := fsa.SubVolumeInfo(s.FsName, s.SubvolumeGroup, s.VolID)
|
2020-08-04 09:51:31 +05:30
|
|
|
if err != nil {
|
2022-02-15 17:41:09 +05:30
|
|
|
log.ErrorLog(ctx, "failed to get subvolume info for the vol %s: %s", s.VolID, err)
|
2020-11-30 15:40:50 +05:30
|
|
|
if errors.Is(err, rados.ErrNotFound) {
|
2021-08-25 12:16:03 +05:30
|
|
|
return nil, cerrors.ErrVolumeNotFound
|
2020-08-04 09:51:31 +05:30
|
|
|
}
|
2020-11-30 15:50:07 +05:30
|
|
|
// In case the error is invalid command return error to the caller.
|
|
|
|
var invalid fsAdmin.NotImplementedError
|
|
|
|
if errors.As(err, &invalid) {
|
2021-08-25 12:16:03 +05:30
|
|
|
return nil, cerrors.ErrInvalidCommand
|
2020-08-04 09:51:31 +05:30
|
|
|
}
|
|
|
|
|
2020-10-10 18:17:08 +02:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-11-05 15:29:30 +01:00
|
|
|
subvol := Subvolume{
|
|
|
|
// only set BytesQuota when it is of type ByteCount
|
|
|
|
Path: info.Path,
|
|
|
|
Features: make([]string, len(info.Features)),
|
|
|
|
}
|
2020-10-10 18:17:08 +02:00
|
|
|
bc, ok := info.BytesQuota.(fsAdmin.ByteCount)
|
|
|
|
if !ok {
|
2021-02-01 13:14:56 +05:30
|
|
|
// If info.BytesQuota == Infinite (in case it is not set)
|
|
|
|
// or nil (in case the subvolume is in snapshot-retained state),
|
|
|
|
// just continue without returning quota information.
|
2021-08-17 16:32:46 +05:30
|
|
|
if !(info.BytesQuota == fsAdmin.Infinite || info.State == fsAdmin.StateSnapRetained) {
|
2022-02-15 17:41:09 +05:30
|
|
|
return nil, fmt.Errorf("subvolume %s has unsupported quota: %v", s.VolID, info.BytesQuota)
|
2020-11-05 15:29:30 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
subvol.BytesQuota = int64(bc)
|
2020-10-10 18:17:08 +02:00
|
|
|
}
|
|
|
|
for i, feature := range info.Features {
|
|
|
|
subvol.Features[i] = string(feature)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &subvol, nil
|
2020-08-04 09:51:31 +05:30
|
|
|
}
|
|
|
|
|
2020-12-22 13:10:06 +05:30
|
|
|
type operationState int64
|
|
|
|
|
|
|
|
const (
|
2023-11-17 19:20:13 +05:30
|
|
|
supported operationState = iota
|
2020-12-22 13:10:06 +05:30
|
|
|
unsupported
|
|
|
|
)
|
|
|
|
|
2020-06-25 19:18:48 +05:30
|
|
|
type localClusterState struct {
|
2023-11-17 19:20:13 +05:30
|
|
|
// set the enum value i.e., supported or
|
2020-12-22 13:10:06 +05:30
|
|
|
// unsupported as per the state of the cluster.
|
2022-07-19 16:02:58 +05:30
|
|
|
subVolMetadataState operationState
|
|
|
|
subVolSnapshotMetadataState operationState
|
2020-06-25 19:18:48 +05:30
|
|
|
}
|
|
|
|
|
2022-09-07 11:46:22 +05:30
|
|
|
func newLocalClusterState(clusterID string) {
|
2022-02-15 17:41:09 +05:30
|
|
|
// verify if corresponding clusterID key is present in the map,
|
2020-06-25 19:18:48 +05:30
|
|
|
// and if not, initialize with default values(false).
|
2023-10-09 11:38:04 +05:30
|
|
|
clusterAdditionalInfoMutex.Lock()
|
|
|
|
defer clusterAdditionalInfoMutex.Unlock()
|
2022-09-07 11:46:22 +05:30
|
|
|
if _, keyPresent := clusterAdditionalInfo[clusterID]; !keyPresent {
|
|
|
|
clusterAdditionalInfo[clusterID] = &localClusterState{}
|
2020-06-25 19:18:48 +05:30
|
|
|
}
|
2022-09-07 11:46:22 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
// CreateVolume creates a subvolume.
|
|
|
|
func (s *subVolumeClient) CreateVolume(ctx context.Context) error {
|
|
|
|
newLocalClusterState(s.clusterID)
|
2020-06-25 19:18:48 +05:30
|
|
|
|
2022-02-15 17:41:09 +05:30
|
|
|
ca, err := s.conn.GetFSAdmin()
|
2020-08-05 16:31:22 +02:00
|
|
|
if err != nil {
|
2022-02-15 17:41:09 +05:30
|
|
|
log.ErrorLog(ctx, "could not get FSAdmin, can not create subvolume %s: %s", s.VolID, err)
|
2021-07-22 11:15:17 +05:30
|
|
|
|
2020-08-05 16:31:22 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
opts := fsAdmin.SubVolumeOptions{
|
2022-02-15 17:41:09 +05:30
|
|
|
Size: fsAdmin.ByteCount(s.Size),
|
2019-08-05 11:40:48 -04:00
|
|
|
}
|
2022-02-15 17:41:09 +05:30
|
|
|
if s.Pool != "" {
|
|
|
|
opts.PoolLayout = s.Pool
|
2019-08-05 11:40:48 -04:00
|
|
|
}
|
|
|
|
|
2020-08-05 16:31:22 +02:00
|
|
|
// FIXME: check if the right credentials are used ("-n", cephEntityClientPrefix + cr.ID)
|
2022-02-15 17:41:09 +05:30
|
|
|
err = ca.CreateSubVolume(s.FsName, s.SubvolumeGroup, s.VolID, &opts)
|
2019-06-08 05:06:03 +00:00
|
|
|
if err != nil {
|
2022-02-15 17:41:09 +05:30
|
|
|
log.ErrorLog(ctx, "failed to create subvolume %s in fs %s: %s", s.VolID, s.FsName, err)
|
2021-07-22 11:15:17 +05:30
|
|
|
|
2019-02-26 11:06:16 +01:00
|
|
|
return err
|
|
|
|
}
|
2018-04-13 14:49:49 +02:00
|
|
|
|
2019-02-26 11:06:16 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-01-10 10:26:28 +05:30
|
|
|
// ExpandVolume will expand the volume if the requested size is greater than
|
|
|
|
// the subvolume size.
|
2022-02-15 17:41:09 +05:30
|
|
|
func (s *subVolumeClient) ExpandVolume(ctx context.Context, bytesQuota int64) error {
|
2022-01-10 10:26:28 +05:30
|
|
|
// get the subvolume size for comparison with the requested size.
|
2022-02-15 17:41:09 +05:30
|
|
|
info, err := s.GetSubVolumeInfo(ctx)
|
2022-01-10 10:26:28 +05:30
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// resize if the requested size is greater than the current size.
|
2022-02-15 17:41:09 +05:30
|
|
|
if s.Size > info.BytesQuota {
|
|
|
|
log.DebugLog(ctx, "clone %s size %d is greater than requested size %d", s.VolID, info.BytesQuota, bytesQuota)
|
|
|
|
err = s.ResizeVolume(ctx, bytesQuota)
|
2022-01-10 10:26:28 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-11-17 19:20:13 +05:30
|
|
|
// ResizeVolume will use the ceph fs subvolume resize command to resize the
|
|
|
|
// subvolume.
|
2022-02-15 17:41:09 +05:30
|
|
|
func (s *subVolumeClient) ResizeVolume(ctx context.Context, bytesQuota int64) error {
|
2023-11-17 19:20:13 +05:30
|
|
|
fsa, err := s.conn.GetFSAdmin()
|
|
|
|
if err != nil {
|
|
|
|
log.ErrorLog(ctx, "could not get FSAdmin, can not resize volume %s:", s.FsName, err)
|
2021-07-22 11:15:17 +05:30
|
|
|
|
2023-11-17 19:20:13 +05:30
|
|
|
return err
|
|
|
|
}
|
|
|
|
_, err = fsa.ResizeSubVolume(s.FsName, s.SubvolumeGroup, s.VolID, fsAdmin.ByteCount(bytesQuota), true)
|
|
|
|
if err != nil {
|
|
|
|
log.ErrorLog(ctx, "failed to resize subvolume %s in fs %s: %s", s.VolID, s.FsName, err)
|
2020-05-06 19:17:46 +05:30
|
|
|
}
|
2021-07-22 11:15:17 +05:30
|
|
|
|
2023-11-17 19:20:13 +05:30
|
|
|
return err
|
2020-05-06 19:17:46 +05:30
|
|
|
}
|
|
|
|
|
2022-02-15 17:41:09 +05:30
|
|
|
// PurgSubVolume removes the subvolume.
|
|
|
|
func (s *subVolumeClient) PurgeVolume(ctx context.Context, force bool) error {
|
|
|
|
fsa, err := s.conn.GetFSAdmin()
|
2020-12-09 11:59:10 +05:30
|
|
|
if err != nil {
|
2021-08-24 17:03:25 +02:00
|
|
|
log.ErrorLog(ctx, "could not get FSAdmin %s:", err)
|
2021-07-22 11:15:17 +05:30
|
|
|
|
2020-12-09 11:59:10 +05:30
|
|
|
return err
|
2020-08-04 09:53:26 +05:30
|
|
|
}
|
2020-12-09 11:59:10 +05:30
|
|
|
|
|
|
|
opt := fsAdmin.SubVolRmFlags{}
|
|
|
|
opt.Force = force
|
|
|
|
|
2022-02-15 17:41:09 +05:30
|
|
|
if checkSubvolumeHasFeature("snapshot-retention", s.Features) {
|
2020-12-09 11:59:10 +05:30
|
|
|
opt.RetainSnapshots = true
|
2020-09-10 17:34:51 +05:30
|
|
|
}
|
2020-08-04 09:53:26 +05:30
|
|
|
|
2022-02-15 17:41:09 +05:30
|
|
|
err = fsa.RemoveSubVolumeWithFlags(s.FsName, s.SubvolumeGroup, s.VolID, opt)
|
2018-08-14 11:19:41 +02:00
|
|
|
if err != nil {
|
2022-02-15 17:41:09 +05:30
|
|
|
log.ErrorLog(ctx, "failed to purge subvolume %s in fs %s: %s", s.VolID, s.FsName, err)
|
2021-08-25 12:16:03 +05:30
|
|
|
if strings.Contains(err.Error(), cerrors.VolumeNotEmpty) {
|
2024-05-07 15:51:05 -07:00
|
|
|
return fmt.Errorf("Failed as %w (internal %w)", cerrors.ErrVolumeHasSnapshots, err)
|
2020-09-21 11:08:14 +05:30
|
|
|
}
|
2020-12-09 11:59:10 +05:30
|
|
|
if errors.Is(err, rados.ErrNotFound) {
|
2024-05-07 15:51:05 -07:00
|
|
|
return fmt.Errorf("Failed as %w (internal %w)", cerrors.ErrVolumeNotFound, err)
|
2020-01-22 21:48:46 -05:00
|
|
|
}
|
2021-07-22 11:15:17 +05:30
|
|
|
|
2019-06-08 05:06:03 +00:00
|
|
|
return err
|
2018-04-13 14:49:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2018-03-09 17:05:19 +01:00
|
|
|
}
|
2020-09-10 17:35:53 +05:30
|
|
|
|
|
|
|
// checkSubvolumeHasFeature verifies if the referred subvolume has
|
|
|
|
// the required feature.
|
|
|
|
func checkSubvolumeHasFeature(feature string, subVolFeatures []string) bool {
|
|
|
|
// The subvolume "features" are based on the internal version of the subvolume.
|
|
|
|
// Verify if subvolume supports the required feature.
|
|
|
|
for _, subvolFeature := range subVolFeatures {
|
|
|
|
if subvolFeature == feature {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
2021-07-22 11:15:17 +05:30
|
|
|
|
2020-09-10 17:35:53 +05:30
|
|
|
return false
|
|
|
|
}
|