2019-05-14 19:15:01 +00:00
|
|
|
/*
|
|
|
|
Copyright 2019 The Ceph-CSI Authors.
|
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package rbd
|
|
|
|
|
|
|
|
import (
|
2019-08-24 09:14:15 +00:00
|
|
|
"context"
|
2019-05-14 19:15:01 +00:00
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/ceph/ceph-csi/pkg/util"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"k8s.io/klog"
|
|
|
|
)
|
|
|
|
|
|
|
|
func validateNonEmptyField(field, fieldName, structName string) error {
|
|
|
|
if field == "" {
|
|
|
|
return fmt.Errorf("value '%s' in '%s' structure cannot be empty", fieldName, structName)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateRbdSnap(rbdSnap *rbdSnapshot) error {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
if err = validateNonEmptyField(rbdSnap.RequestName, "RequestName", "rbdSnapshot"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = validateNonEmptyField(rbdSnap.Monitors, "Monitors", "rbdSnapshot"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = validateNonEmptyField(rbdSnap.Pool, "Pool", "rbdSnapshot"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = validateNonEmptyField(rbdSnap.RbdImageName, "RbdImageName", "rbdSnapshot"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = validateNonEmptyField(rbdSnap.ClusterID, "ClusterID", "rbdSnapshot"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateRbdVol(rbdVol *rbdVolume) error {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
if err = validateNonEmptyField(rbdVol.RequestName, "RequestName", "rbdVolume"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = validateNonEmptyField(rbdVol.Monitors, "Monitors", "rbdVolume"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = validateNonEmptyField(rbdVol.Pool, "Pool", "rbdVolume"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = validateNonEmptyField(rbdVol.ClusterID, "ClusterID", "rbdVolume"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if rbdVol.VolSize == 0 {
|
|
|
|
return errors.New("value 'VolSize' in 'rbdVolume' structure cannot be 0")
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
checkSnapExists, and its counterpart checkVolExists, function checks if the passed in rbdSnapshot
|
|
|
|
or rbdVolume exists on the backend.
|
|
|
|
|
|
|
|
**NOTE:** These functions manipulate the rados omaps that hold information regarding
|
|
|
|
volume names as requested by the CSI drivers. Hence, these need to be invoked only when the
|
|
|
|
respective CSI driver generated snapshot or volume name based locks are held, as otherwise racy
|
|
|
|
access to these omaps may end up leaving them in an inconsistent state.
|
|
|
|
|
|
|
|
These functions need enough information about cluster and pool (ie, Monitors, Pool, IDs filled in)
|
|
|
|
to operate. They further require that the RequestName element of the structure have a valid value
|
|
|
|
to operate on and determine if the said RequestName already exists on the backend.
|
|
|
|
|
|
|
|
These functions populate the snapshot or the image name, its attributes and the CSI snapshot/volume
|
|
|
|
ID for the same when successful.
|
|
|
|
|
|
|
|
These functions also cleanup omap reservations that are stale. I.e when omap entries exist and
|
|
|
|
backing images or snapshots are missing, or one of the omaps exist and the next is missing. This is
|
|
|
|
because, the order of omap creation and deletion are inverse of each other, and protected by the
|
|
|
|
request name lock, and hence any stale omaps are leftovers from incomplete transactions and are
|
|
|
|
hence safe to garbage collect.
|
|
|
|
*/
|
2019-08-22 16:57:23 +00:00
|
|
|
func checkSnapExists(ctx context.Context, rbdSnap *rbdSnapshot, cr *util.Credentials) (bool, error) {
|
2019-05-14 19:15:01 +00:00
|
|
|
err := validateRbdSnap(rbdSnap)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
2020-01-24 16:26:56 +00:00
|
|
|
snapData, err := snapJournal.CheckReservation(ctx, rbdSnap.Monitors, cr, rbdSnap.JournalPool,
|
2020-02-24 13:19:42 +00:00
|
|
|
rbdSnap.RequestName, rbdSnap.NamePrefix, rbdSnap.RbdImageName, "")
|
2019-05-14 19:15:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
2020-01-24 16:26:56 +00:00
|
|
|
if snapData == nil {
|
2019-05-14 19:15:01 +00:00
|
|
|
return false, nil
|
|
|
|
}
|
2020-01-24 16:26:56 +00:00
|
|
|
snapUUID := snapData.ImageUUID
|
|
|
|
rbdSnap.RbdSnapName = snapData.ImageAttributes.ImageName
|
2020-02-24 13:19:42 +00:00
|
|
|
|
2020-01-24 16:26:56 +00:00
|
|
|
// it should never happen that this disagrees, but check
|
|
|
|
if rbdSnap.Pool != snapData.ImagePool {
|
|
|
|
return false, fmt.Errorf("stored snapshot pool (%s) and expected snapshot pool (%s) mismatch",
|
|
|
|
snapData.ImagePool, rbdSnap.Pool)
|
2020-02-24 13:19:42 +00:00
|
|
|
}
|
2019-05-14 19:15:01 +00:00
|
|
|
|
|
|
|
// Fetch on-disk image attributes
|
2019-08-22 16:57:23 +00:00
|
|
|
err = updateSnapWithImageInfo(ctx, rbdSnap, cr)
|
2019-05-14 19:15:01 +00:00
|
|
|
if err != nil {
|
|
|
|
if _, ok := err.(ErrSnapNotFound); ok {
|
2020-01-24 16:26:56 +00:00
|
|
|
err = snapJournal.UndoReservation(ctx, rbdSnap.Monitors, cr, rbdSnap.JournalPool,
|
|
|
|
rbdSnap.Pool, rbdSnap.RbdSnapName, rbdSnap.RequestName)
|
2019-05-14 19:15:01 +00:00
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// found a snapshot already available, process and return its information
|
2020-01-24 16:26:56 +00:00
|
|
|
rbdSnap.SnapID, err = util.GenerateVolID(ctx, rbdSnap.Monitors, cr, snapData.ImagePoolID, rbdSnap.Pool,
|
2019-05-14 19:15:01 +00:00
|
|
|
rbdSnap.ClusterID, snapUUID, volIDVersion)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
2019-08-22 16:57:23 +00:00
|
|
|
klog.V(4).Infof(util.Log(ctx, "found existing snap (%s) with snap name (%s) for request (%s)"),
|
2019-05-14 19:15:01 +00:00
|
|
|
rbdSnap.SnapID, rbdSnap.RbdSnapName, rbdSnap.RequestName)
|
|
|
|
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Check comment on checkSnapExists, to understand how this function behaves
|
|
|
|
|
|
|
|
**NOTE:** These functions manipulate the rados omaps that hold information regarding
|
|
|
|
volume names as requested by the CSI drivers. Hence, these need to be invoked only when the
|
|
|
|
respective CSI snapshot or volume name based locks are held, as otherwise racy access to these
|
|
|
|
omaps may end up leaving the omaps in an inconsistent state.
|
|
|
|
*/
|
2019-08-22 16:57:23 +00:00
|
|
|
func checkVolExists(ctx context.Context, rbdVol *rbdVolume, cr *util.Credentials) (bool, error) {
|
2019-05-14 19:15:01 +00:00
|
|
|
err := validateRbdVol(rbdVol)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
2020-02-06 16:23:14 +00:00
|
|
|
kmsID := ""
|
2020-01-29 11:44:45 +00:00
|
|
|
if rbdVol.Encrypted {
|
2020-02-06 16:23:14 +00:00
|
|
|
kmsID = rbdVol.KMS.GetID()
|
2020-01-29 11:44:45 +00:00
|
|
|
}
|
2020-01-24 16:26:56 +00:00
|
|
|
|
|
|
|
imageData, err := volJournal.CheckReservation(ctx, rbdVol.Monitors, cr, rbdVol.JournalPool,
|
2020-02-24 13:19:42 +00:00
|
|
|
rbdVol.RequestName, rbdVol.NamePrefix, "", kmsID)
|
2019-05-14 19:15:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
2020-01-24 16:26:56 +00:00
|
|
|
if imageData == nil {
|
2019-05-14 19:15:01 +00:00
|
|
|
return false, nil
|
|
|
|
}
|
2020-02-24 13:19:42 +00:00
|
|
|
|
2020-01-24 16:26:56 +00:00
|
|
|
imageUUID := imageData.ImageUUID
|
|
|
|
rbdVol.RbdImageName = imageData.ImageAttributes.ImageName
|
|
|
|
|
|
|
|
// check if topology constraints match what is found
|
|
|
|
rbdVol.Topology, err = util.MatchTopologyForPool(rbdVol.TopologyPools,
|
|
|
|
rbdVol.TopologyRequirement, imageData.ImagePool)
|
2020-02-24 13:19:42 +00:00
|
|
|
if err != nil {
|
2020-01-24 16:26:56 +00:00
|
|
|
// TODO check if need any undo operation here, or ErrVolNameConflict
|
2020-02-24 13:19:42 +00:00
|
|
|
return false, err
|
|
|
|
}
|
2020-01-24 16:26:56 +00:00
|
|
|
// update Pool, if it was topology constrained
|
|
|
|
if rbdVol.Topology != nil {
|
|
|
|
rbdVol.Pool = imageData.ImagePool
|
|
|
|
}
|
2019-05-14 19:15:01 +00:00
|
|
|
|
|
|
|
// NOTE: Return volsize should be on-disk volsize, not request vol size, so
|
|
|
|
// save it for size checks before fetching image data
|
|
|
|
requestSize := rbdVol.VolSize
|
|
|
|
// Fetch on-disk image attributes and compare against request
|
2019-08-22 16:57:23 +00:00
|
|
|
err = updateVolWithImageInfo(ctx, rbdVol, cr)
|
2019-05-14 19:15:01 +00:00
|
|
|
if err != nil {
|
|
|
|
if _, ok := err.(ErrImageNotFound); ok {
|
2020-01-24 16:26:56 +00:00
|
|
|
err = volJournal.UndoReservation(ctx, rbdVol.Monitors, cr, rbdVol.JournalPool, rbdVol.Pool,
|
2019-05-14 19:15:01 +00:00
|
|
|
rbdVol.RbdImageName, rbdVol.RequestName)
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// size checks
|
|
|
|
if rbdVol.VolSize < requestSize {
|
|
|
|
err = fmt.Errorf("image with the same name (%s) but with different size already exists",
|
|
|
|
rbdVol.RbdImageName)
|
|
|
|
return false, ErrVolNameConflict{rbdVol.RbdImageName, err}
|
|
|
|
}
|
|
|
|
// TODO: We should also ensure image features and format is the same
|
|
|
|
|
|
|
|
// found a volume already available, process and return it!
|
2020-01-24 16:26:56 +00:00
|
|
|
rbdVol.VolID, err = util.GenerateVolID(ctx, rbdVol.Monitors, cr, imageData.ImagePoolID, rbdVol.Pool,
|
2019-05-14 19:15:01 +00:00
|
|
|
rbdVol.ClusterID, imageUUID, volIDVersion)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
2019-08-22 16:57:23 +00:00
|
|
|
klog.V(4).Infof(util.Log(ctx, "found existing volume (%s) with image name (%s) for request (%s)"),
|
2019-05-14 19:15:01 +00:00
|
|
|
rbdVol.VolID, rbdVol.RbdImageName, rbdVol.RequestName)
|
|
|
|
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// reserveSnap is a helper routine to request a rbdSnapshot name reservation and generate the
|
|
|
|
// volume ID for the generated name
|
2019-08-22 16:57:23 +00:00
|
|
|
func reserveSnap(ctx context.Context, rbdSnap *rbdSnapshot, cr *util.Credentials) error {
|
2020-02-24 13:19:42 +00:00
|
|
|
var (
|
|
|
|
snapUUID string
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
|
2020-01-24 16:26:56 +00:00
|
|
|
journalPoolID, imagePoolID, err := util.GetPoolIDs(ctx, rbdSnap.Monitors, rbdSnap.JournalPool, rbdSnap.Pool, cr)
|
2019-05-14 19:15:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-01-24 16:26:56 +00:00
|
|
|
snapUUID, rbdSnap.RbdSnapName, err = snapJournal.ReserveName(ctx, rbdSnap.Monitors, cr, rbdSnap.JournalPool, journalPoolID,
|
|
|
|
rbdSnap.Pool, imagePoolID, rbdSnap.RequestName, rbdSnap.NamePrefix, rbdSnap.RbdImageName, "")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
rbdSnap.SnapID, err = util.GenerateVolID(ctx, rbdSnap.Monitors, cr, imagePoolID, rbdSnap.Pool,
|
2019-05-14 19:15:01 +00:00
|
|
|
rbdSnap.ClusterID, snapUUID, volIDVersion)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-08-22 16:57:23 +00:00
|
|
|
klog.V(4).Infof(util.Log(ctx, "generated Volume ID (%s) and image name (%s) for request name (%s)"),
|
2020-02-24 13:19:42 +00:00
|
|
|
rbdSnap.SnapID, rbdSnap.RbdSnapName, rbdSnap.RequestName)
|
2019-05-14 19:15:01 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-01-24 16:26:56 +00:00
|
|
|
func updateTopologyConstraints(rbdVol *rbdVolume, rbdSnap *rbdSnapshot) error {
|
|
|
|
var err error
|
|
|
|
if rbdSnap != nil {
|
|
|
|
// check if topology constraints matches snapshot pool
|
|
|
|
rbdVol.Topology, err = util.MatchTopologyForPool(rbdVol.TopologyPools,
|
|
|
|
rbdVol.TopologyRequirement, rbdSnap.Pool)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// update Pool, if it was topology constrained
|
|
|
|
if rbdVol.Topology != nil {
|
|
|
|
rbdVol.Pool = rbdSnap.Pool
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// update request based on topology constrained parameters (if present)
|
|
|
|
poolName, topology, err := util.FindPoolAndTopology(rbdVol.TopologyPools, rbdVol.TopologyRequirement)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if poolName != "" {
|
|
|
|
rbdVol.Pool = poolName
|
|
|
|
rbdVol.Topology = topology
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-05-14 19:15:01 +00:00
|
|
|
// reserveVol is a helper routine to request a rbdVolume name reservation and generate the
|
|
|
|
// volume ID for the generated name
|
2020-01-24 16:26:56 +00:00
|
|
|
func reserveVol(ctx context.Context, rbdVol *rbdVolume, rbdSnap *rbdSnapshot, cr *util.Credentials) error {
|
2020-02-24 13:19:42 +00:00
|
|
|
var (
|
|
|
|
imageUUID string
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
|
2020-01-24 16:26:56 +00:00
|
|
|
err = updateTopologyConstraints(rbdVol, rbdSnap)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
journalPoolID, imagePoolID, err := util.GetPoolIDs(ctx, rbdVol.Monitors, rbdVol.JournalPool, rbdVol.Pool, cr)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-02-06 16:23:14 +00:00
|
|
|
kmsID := ""
|
2020-01-29 11:44:45 +00:00
|
|
|
if rbdVol.Encrypted {
|
2020-02-06 16:23:14 +00:00
|
|
|
kmsID = rbdVol.KMS.GetID()
|
2020-01-29 11:44:45 +00:00
|
|
|
}
|
2020-02-24 13:19:42 +00:00
|
|
|
|
2020-01-24 16:26:56 +00:00
|
|
|
imageUUID, rbdVol.RbdImageName, err = volJournal.ReserveName(ctx, rbdVol.Monitors, cr, rbdVol.JournalPool, journalPoolID,
|
|
|
|
rbdVol.Pool, imagePoolID, rbdVol.RequestName, rbdVol.NamePrefix, "", kmsID)
|
2019-05-14 19:15:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-01-24 16:26:56 +00:00
|
|
|
rbdVol.VolID, err = util.GenerateVolID(ctx, rbdVol.Monitors, cr, imagePoolID, rbdVol.Pool,
|
2019-05-14 19:15:01 +00:00
|
|
|
rbdVol.ClusterID, imageUUID, volIDVersion)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-08-22 16:57:23 +00:00
|
|
|
klog.V(4).Infof(util.Log(ctx, "generated Volume ID (%s) and image name (%s) for request name (%s)"),
|
2019-05-14 19:15:01 +00:00
|
|
|
rbdVol.VolID, rbdVol.RbdImageName, rbdVol.RequestName)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// undoSnapReservation is a helper routine to undo a name reservation for rbdSnapshot
|
2019-08-22 17:19:06 +00:00
|
|
|
func undoSnapReservation(ctx context.Context, rbdSnap *rbdSnapshot, cr *util.Credentials) error {
|
2020-01-24 16:26:56 +00:00
|
|
|
err := snapJournal.UndoReservation(ctx, rbdSnap.Monitors, cr, rbdSnap.JournalPool, rbdSnap.Pool,
|
2019-05-14 19:15:01 +00:00
|
|
|
rbdSnap.RbdSnapName, rbdSnap.RequestName)
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// undoVolReservation is a helper routine to undo a name reservation for rbdVolume
|
2019-08-22 17:19:06 +00:00
|
|
|
func undoVolReservation(ctx context.Context, rbdVol *rbdVolume, cr *util.Credentials) error {
|
2020-01-24 16:26:56 +00:00
|
|
|
err := volJournal.UndoReservation(ctx, rbdVol.Monitors, cr, rbdVol.JournalPool, rbdVol.Pool,
|
2019-05-14 19:15:01 +00:00
|
|
|
rbdVol.RbdImageName, rbdVol.RequestName)
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|