2018-01-09 18:59:50 +00:00
/ *
Copyright 2018 The Kubernetes 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 (
"encoding/json"
"fmt"
"os"
"os/exec"
"path"
"strings"
"time"
2018-03-06 22:33:57 +00:00
"github.com/golang/glog"
2018-07-20 08:46:44 +00:00
"k8s.io/apimachinery/pkg/util/sets"
2018-03-06 22:33:57 +00:00
"k8s.io/kubernetes/pkg/util/keymutex"
2018-01-09 18:59:50 +00:00
)
const (
imageWatcherStr = "watcher="
rbdImageFormat1 = "1"
rbdImageFormat2 = "2"
imageSizeStr = "size "
sizeDivStr = " MB in"
kubeLockMagic = "kubelet_lock_magic_"
// The following three values are used for 30 seconds timeout
// while waiting for RBD Watcher to expire.
rbdImageWatcherInitDelay = 1 * time . Second
rbdImageWatcherFactor = 1.4
rbdImageWatcherSteps = 10
2018-09-18 14:09:12 +00:00
rbdDefaultMounter = "rbd"
2018-01-09 18:59:50 +00:00
)
2018-03-06 22:33:57 +00:00
type rbdVolume struct {
2018-07-20 08:46:44 +00:00
VolName string ` json:"volName" `
VolID string ` json:"volID" `
Monitors string ` json:"monitors" `
Pool string ` json:"pool" `
ImageFormat string ` json:"imageFormat" `
ImageFeatures string ` json:"imageFeatures" `
VolSize int64 ` json:"volSize" `
2018-08-09 13:07:00 +00:00
AdminId string ` json:"adminId" `
UserId string ` json:"userId" `
2018-09-18 14:09:12 +00:00
Mounter string ` json:"mounter" `
2018-01-09 18:59:50 +00:00
}
2018-08-08 05:42:17 +00:00
type rbdSnapshot struct {
SourceVolumeID string ` json:"sourceVolumeID" `
VolName string ` json:"volName" `
SnapName string ` json:"snapName" `
SnapID string ` json:"sanpID" `
Monitors string ` json:"monitors" `
Pool string ` json:"pool" `
CreatedAt int64 ` json:"createdAt" `
SizeBytes int64 ` json:"sizeBytes" `
2018-08-09 13:07:00 +00:00
AdminId string ` json:"adminId" `
UserId string ` json:"userId" `
2018-08-08 05:42:17 +00:00
}
2018-07-20 08:46:44 +00:00
var (
attachdetachMutex = keymutex . NewKeyMutex ( )
supportedFeatures = sets . NewString ( "layering" )
)
2018-01-09 18:59:50 +00:00
2018-03-06 22:33:57 +00:00
func getRBDKey ( id string , credentials map [ string ] string ) ( string , error ) {
if key , ok := credentials [ id ] ; ok {
return key , nil
}
return "" , fmt . Errorf ( "RBD key for ID: %s not found" , id )
}
2018-01-09 18:59:50 +00:00
// CreateImage creates a new ceph image with provision and volume options.
2018-08-09 13:07:00 +00:00
func createRBDImage ( pOpts * rbdVolume , volSz int , adminId string , credentials map [ string ] string ) error {
2018-01-09 18:59:50 +00:00
var output [ ] byte
var err error
// rbd create
mon := pOpts . Monitors
2018-01-16 01:52:28 +00:00
image := pOpts . VolName
2018-01-09 18:59:50 +00:00
volSzGB := fmt . Sprintf ( "%dG" , volSz )
2018-08-09 13:07:00 +00:00
key , err := getRBDKey ( adminId , credentials )
2018-03-06 22:33:57 +00:00
if err != nil {
return err
}
2018-01-09 18:59:50 +00:00
if pOpts . ImageFormat == rbdImageFormat2 {
2018-08-09 13:07:00 +00:00
glog . V ( 4 ) . Infof ( "rbd: create %s size %s format %s (features: %s) using mon %s, pool %s id %s key %s" , image , volSzGB , pOpts . ImageFormat , pOpts . ImageFeatures , mon , pOpts . Pool , adminId , key )
2018-01-09 18:59:50 +00:00
} else {
2018-08-09 13:07:00 +00:00
glog . V ( 4 ) . Infof ( "rbd: create %s size %s format %s using mon %s, pool %s id %s key %s" , image , volSzGB , pOpts . ImageFormat , mon , pOpts . Pool , adminId , key )
2018-01-09 18:59:50 +00:00
}
2018-08-09 13:07:00 +00:00
args := [ ] string { "create" , image , "--size" , volSzGB , "--pool" , pOpts . Pool , "--id" , adminId , "-m" , mon , "--key=" + key , "--image-format" , pOpts . ImageFormat }
2018-01-09 18:59:50 +00:00
if pOpts . ImageFormat == rbdImageFormat2 {
2018-07-20 08:46:44 +00:00
args = append ( args , "--image-feature" , pOpts . ImageFeatures )
2018-01-09 18:59:50 +00:00
}
output , err = execCommand ( "rbd" , args )
if err != nil {
return fmt . Errorf ( "failed to create rbd image: %v, command output: %s" , err , string ( output ) )
}
return nil
}
// rbdStatus checks if there is watcher on the image.
// It returns true if there is a watcher onthe image, otherwise returns false.
2018-08-09 13:07:00 +00:00
func rbdStatus ( pOpts * rbdVolume , userId string , credentials map [ string ] string ) ( bool , string , error ) {
2018-01-09 18:59:50 +00:00
var err error
var output string
var cmd [ ] byte
2018-03-06 22:33:57 +00:00
image := pOpts . VolName
2018-01-09 18:59:50 +00:00
// If we don't have admin id/secret (e.g. attaching), fallback to user id/secret.
2018-08-09 13:07:00 +00:00
key , err := getRBDKey ( userId , credentials )
2018-03-06 22:33:57 +00:00
if err != nil {
return false , "" , err
2018-01-09 18:59:50 +00:00
}
2018-08-09 13:07:00 +00:00
glog . V ( 4 ) . Infof ( "rbd: status %s using mon %s, pool %s id %s key %s" , image , pOpts . Monitors , pOpts . Pool , userId , key )
args := [ ] string { "status" , image , "--pool" , pOpts . Pool , "-m" , pOpts . Monitors , "--id" , userId , "--key=" + key }
2018-01-09 18:59:50 +00:00
cmd , err = execCommand ( "rbd" , args )
output = string ( cmd )
if err , ok := err . ( * exec . Error ) ; ok {
if err . Err == exec . ErrNotFound {
glog . Errorf ( "rbd cmd not found" )
// fail fast if command not found
return false , output , err
}
}
// If command never succeed, returns its last error.
if err != nil {
return false , output , err
}
if strings . Contains ( output , imageWatcherStr ) {
glog . V ( 4 ) . Infof ( "rbd: watchers on %s: %s" , image , output )
return true , output , nil
} else {
glog . Warningf ( "rbd: no watchers on %s" , image )
return false , output , nil
}
}
// DeleteImage deletes a ceph image with provision and volume options.
2018-08-09 13:07:00 +00:00
func deleteRBDImage ( pOpts * rbdVolume , adminId string , credentials map [ string ] string ) error {
2018-01-09 18:59:50 +00:00
var output [ ] byte
2018-03-06 22:33:57 +00:00
image := pOpts . VolName
2018-08-09 13:07:00 +00:00
found , _ , err := rbdStatus ( pOpts , adminId , credentials )
2018-01-09 18:59:50 +00:00
if err != nil {
return err
}
if found {
glog . Info ( "rbd is still being used " , image )
return fmt . Errorf ( "rbd %s is still being used" , image )
}
2018-08-09 13:07:00 +00:00
key , err := getRBDKey ( adminId , credentials )
2018-03-06 22:33:57 +00:00
if err != nil {
return err
2018-01-09 18:59:50 +00:00
}
2018-08-09 13:07:00 +00:00
glog . V ( 4 ) . Infof ( "rbd: rm %s using mon %s, pool %s id %s key %s" , image , pOpts . Monitors , pOpts . Pool , adminId , key )
args := [ ] string { "rm" , image , "--pool" , pOpts . Pool , "--id" , adminId , "-m" , pOpts . Monitors , "--key=" + key }
2018-01-09 18:59:50 +00:00
output , err = execCommand ( "rbd" , args )
if err == nil {
return nil
}
glog . Errorf ( "failed to delete rbd image: %v, command output: %s" , err , string ( output ) )
return err
}
func execCommand ( command string , args [ ] string ) ( [ ] byte , error ) {
cmd := exec . Command ( command , args ... )
return cmd . CombinedOutput ( )
}
2018-03-06 22:33:57 +00:00
func getRBDVolumeOptions ( volOptions map [ string ] string ) ( * rbdVolume , error ) {
2018-01-09 18:59:50 +00:00
var ok bool
2018-03-06 22:33:57 +00:00
rbdVol := & rbdVolume { }
rbdVol . Pool , ok = volOptions [ "pool" ]
2018-01-09 18:59:50 +00:00
if ! ok {
return nil , fmt . Errorf ( "Missing required parameter pool" )
}
2018-03-06 22:33:57 +00:00
rbdVol . Monitors , ok = volOptions [ "monitors" ]
2018-01-09 18:59:50 +00:00
if ! ok {
return nil , fmt . Errorf ( "Missing required parameter monitors" )
}
2018-03-06 22:33:57 +00:00
rbdVol . ImageFormat , ok = volOptions [ "imageFormat" ]
2018-01-18 19:13:08 +00:00
if ! ok {
2018-07-20 08:46:44 +00:00
rbdVol . ImageFormat = rbdImageFormat2
}
if rbdVol . ImageFormat == rbdImageFormat2 {
// if no image features is provided, it results in empty string
// which disable all RBD image format 2 features as we expected
imageFeatures , ok := volOptions [ "imageFeatures" ]
if ok {
arr := strings . Split ( imageFeatures , "," )
for _ , f := range arr {
if ! supportedFeatures . Has ( f ) {
return nil , fmt . Errorf ( "invalid feature %q for volume csi-rbdplugin, supported features are: %v" , f , supportedFeatures )
}
}
rbdVol . ImageFeatures = imageFeatures
}
2018-01-09 18:59:50 +00:00
}
2018-08-09 13:07:00 +00:00
rbdVol . AdminId , ok = volOptions [ "adminid" ]
if ! ok {
rbdVol . AdminId = rbdDefaultAdminId
}
rbdVol . UserId , ok = volOptions [ "userid" ]
if ! ok {
rbdVol . UserId = rbdDefaultUserId
}
2018-09-18 14:09:12 +00:00
rbdVol . Mounter , ok = volOptions [ "mounter" ]
if ! ok {
rbdVol . Mounter = rbdDefaultMounter
}
2018-03-06 22:33:57 +00:00
return rbdVol , nil
2018-01-09 18:59:50 +00:00
}
2018-08-08 05:42:17 +00:00
func getRBDSnapshotOptions ( snapOptions map [ string ] string ) ( * rbdSnapshot , error ) {
var ok bool
rbdSnap := & rbdSnapshot { }
rbdSnap . Pool , ok = snapOptions [ "pool" ]
if ! ok {
return nil , fmt . Errorf ( "Missing required parameter pool" )
}
rbdSnap . Monitors , ok = snapOptions [ "monitors" ]
if ! ok {
return nil , fmt . Errorf ( "Missing required parameter monitors" )
}
2018-08-09 13:07:00 +00:00
rbdSnap . AdminId , ok = snapOptions [ "adminid" ]
if ! ok {
rbdSnap . AdminId = rbdDefaultAdminId
}
rbdSnap . UserId , ok = snapOptions [ "userid" ]
if ! ok {
rbdSnap . UserId = rbdDefaultUserId
}
2018-08-08 05:42:17 +00:00
return rbdSnap , nil
}
2018-08-09 13:07:13 +00:00
func hasSnapshotFeature ( imageFeatures string ) bool {
arr := strings . Split ( imageFeatures , "," )
for _ , f := range arr {
if f == "layering" {
return true
}
}
return false
}
2018-03-06 22:33:57 +00:00
func persistVolInfo ( image string , persistentStoragePath string , volInfo * rbdVolume ) error {
2018-01-09 18:59:50 +00:00
file := path . Join ( persistentStoragePath , image + ".json" )
fp , err := os . Create ( file )
if err != nil {
2018-03-06 22:33:57 +00:00
glog . Errorf ( "rbd: failed to create persistent storage file %s with error: %v\n" , file , err )
2018-01-09 18:59:50 +00:00
return fmt . Errorf ( "rbd: create err %s/%s" , file , err )
}
defer fp . Close ( )
encoder := json . NewEncoder ( fp )
if err = encoder . Encode ( volInfo ) ; err != nil {
2018-03-06 22:33:57 +00:00
glog . Errorf ( "rbd: failed to encode volInfo: %+v for file: %s with error: %v\n" , volInfo , file , err )
return fmt . Errorf ( "rbd: encode err: %v" , err )
2018-01-09 18:59:50 +00:00
}
2018-03-06 22:33:57 +00:00
glog . Infof ( "rbd: successfully saved volInfo: %+v into file: %s\n" , volInfo , file )
2018-01-09 18:59:50 +00:00
return nil
}
2018-03-06 22:33:57 +00:00
func loadVolInfo ( image string , persistentStoragePath string , volInfo * rbdVolume ) error {
2018-01-09 18:59:50 +00:00
file := path . Join ( persistentStoragePath , image + ".json" )
fp , err := os . Open ( file )
if err != nil {
return fmt . Errorf ( "rbd: open err %s/%s" , file , err )
}
defer fp . Close ( )
decoder := json . NewDecoder ( fp )
if err = decoder . Decode ( volInfo ) ; err != nil {
return fmt . Errorf ( "rbd: decode err: %v." , err )
}
return nil
}
func deleteVolInfo ( image string , persistentStoragePath string ) error {
file := path . Join ( persistentStoragePath , image + ".json" )
2018-03-06 22:33:57 +00:00
glog . Infof ( "rbd: Deleting file for Volume: %s at: %s resulting path: %+v\n" , image , persistentStoragePath , file )
2018-01-09 18:59:50 +00:00
err := os . Remove ( file )
if err != nil {
if err != os . ErrNotExist {
2018-03-06 22:33:57 +00:00
return fmt . Errorf ( "rbd: error removing file: %s/%s" , file , err )
2018-01-09 18:59:50 +00:00
}
}
return nil
}
2018-03-06 22:33:57 +00:00
2018-08-08 05:42:17 +00:00
func persistSnapInfo ( snapshot string , persistentStoragePath string , snapInfo * rbdSnapshot ) error {
file := path . Join ( persistentStoragePath , snapshot + ".json" )
fp , err := os . Create ( file )
if err != nil {
glog . Errorf ( "rbd: failed to create persistent storage file %s with error: %v\n" , file , err )
return fmt . Errorf ( "rbd: create err %s/%s" , file , err )
}
defer fp . Close ( )
encoder := json . NewEncoder ( fp )
if err = encoder . Encode ( snapInfo ) ; err != nil {
glog . Errorf ( "rbd: failed to encode snapInfo: %+v for file: %s with error: %v\n" , snapInfo , file , err )
return fmt . Errorf ( "rbd: encode err: %v" , err )
}
glog . Infof ( "rbd: successfully saved snapInfo: %+v into file: %s\n" , snapInfo , file )
return nil
}
func loadSnapInfo ( snapshot string , persistentStoragePath string , snapInfo * rbdSnapshot ) error {
file := path . Join ( persistentStoragePath , snapshot + ".json" )
fp , err := os . Open ( file )
if err != nil {
return fmt . Errorf ( "rbd: open err %s/%s" , file , err )
}
defer fp . Close ( )
decoder := json . NewDecoder ( fp )
if err = decoder . Decode ( snapInfo ) ; err != nil {
return fmt . Errorf ( "rbd: decode err: %v." , err )
}
return nil
}
func deleteSnapInfo ( snapshot string , persistentStoragePath string ) error {
file := path . Join ( persistentStoragePath , snapshot + ".json" )
glog . Infof ( "rbd: Deleting file for Snapshot: %s at: %s resulting path: %+v\n" , snapshot , persistentStoragePath , file )
err := os . Remove ( file )
if err != nil {
if err != os . ErrNotExist {
return fmt . Errorf ( "rbd: error removing file: %s/%s" , file , err )
}
}
return nil
}
2018-08-09 13:06:51 +00:00
func getRBDVolumeByID ( volumeID string ) ( * rbdVolume , error ) {
2018-03-06 22:33:57 +00:00
if rbdVol , ok := rbdVolumes [ volumeID ] ; ok {
return rbdVol , nil
}
2018-08-09 13:06:51 +00:00
return nil , fmt . Errorf ( "volume id %s does not exit in the volumes list" , volumeID )
2018-03-06 22:33:57 +00:00
}
2018-08-09 13:06:51 +00:00
func getRBDVolumeByName ( volName string ) ( * rbdVolume , error ) {
2018-03-06 22:33:57 +00:00
for _ , rbdVol := range rbdVolumes {
if rbdVol . VolName == volName {
return rbdVol , nil
}
}
2018-08-09 13:06:51 +00:00
return nil , fmt . Errorf ( "volume name %s does not exit in the volumes list" , volName )
2018-03-06 22:33:57 +00:00
}
2018-08-08 05:42:17 +00:00
2018-08-09 13:06:51 +00:00
func getRBDSnapshotByName ( snapName string ) ( * rbdSnapshot , error ) {
2018-08-08 05:42:17 +00:00
for _ , rbdSnap := range rbdSnapshots {
if rbdSnap . SnapName == snapName {
return rbdSnap , nil
}
}
2018-08-09 13:06:51 +00:00
return nil , fmt . Errorf ( "snapshot name %s does not exit in the snapshots list" , snapName )
2018-08-08 05:42:17 +00:00
}
2018-08-09 13:07:00 +00:00
func protectSnapshot ( pOpts * rbdSnapshot , adminId string , credentials map [ string ] string ) error {
2018-08-08 05:42:17 +00:00
var output [ ] byte
var err error
mon := pOpts . Monitors
image := pOpts . VolName
snapID := pOpts . SnapID
2018-08-09 13:07:00 +00:00
key , err := getRBDKey ( adminId , credentials )
2018-08-08 05:42:17 +00:00
if err != nil {
return err
}
2018-08-09 13:07:00 +00:00
glog . V ( 4 ) . Infof ( "rbd: snap protect %s using mon %s, pool %s id %s key %s" , image , pOpts . Monitors , pOpts . Pool , adminId , key )
args := [ ] string { "snap" , "protect" , "--pool" , pOpts . Pool , "--snap" , snapID , image , "--id" , adminId , "-m" , mon , "--key=" + key }
2018-08-08 05:42:17 +00:00
output , err = execCommand ( "rbd" , args )
if err != nil {
return fmt . Errorf ( "failed to protect snapshot: %v, command output: %s" , err , string ( output ) )
}
return nil
}
2018-08-09 13:07:00 +00:00
func createSnapshot ( pOpts * rbdSnapshot , adminId string , credentials map [ string ] string ) error {
2018-08-08 05:42:17 +00:00
var output [ ] byte
var err error
mon := pOpts . Monitors
image := pOpts . VolName
snapID := pOpts . SnapID
2018-08-09 13:07:00 +00:00
key , err := getRBDKey ( adminId , credentials )
2018-08-08 05:42:17 +00:00
if err != nil {
return err
}
2018-08-09 13:07:00 +00:00
glog . V ( 4 ) . Infof ( "rbd: snap create %s using mon %s, pool %s id %s key %s" , image , pOpts . Monitors , pOpts . Pool , adminId , key )
args := [ ] string { "snap" , "create" , "--pool" , pOpts . Pool , "--snap" , snapID , image , "--id" , adminId , "-m" , mon , "--key=" + key }
2018-08-08 05:42:17 +00:00
output , err = execCommand ( "rbd" , args )
if err != nil {
return fmt . Errorf ( "failed to create snapshot: %v, command output: %s" , err , string ( output ) )
}
return nil
}
2018-08-09 13:07:00 +00:00
func unprotectSnapshot ( pOpts * rbdSnapshot , adminId string , credentials map [ string ] string ) error {
2018-08-08 05:42:17 +00:00
var output [ ] byte
var err error
mon := pOpts . Monitors
image := pOpts . VolName
snapID := pOpts . SnapID
2018-08-09 13:07:00 +00:00
key , err := getRBDKey ( adminId , credentials )
2018-08-08 05:42:17 +00:00
if err != nil {
return err
}
2018-08-09 13:07:00 +00:00
glog . V ( 4 ) . Infof ( "rbd: snap unprotect %s using mon %s, pool %s id %s key %s" , image , pOpts . Monitors , pOpts . Pool , adminId , key )
args := [ ] string { "snap" , "unprotect" , "--pool" , pOpts . Pool , "--snap" , snapID , image , "--id" , adminId , "-m" , mon , "--key=" + key }
2018-08-08 05:42:17 +00:00
output , err = execCommand ( "rbd" , args )
if err != nil {
return fmt . Errorf ( "failed to unprotect snapshot: %v, command output: %s" , err , string ( output ) )
}
return nil
}
2018-08-09 13:07:00 +00:00
func deleteSnapshot ( pOpts * rbdSnapshot , adminId string , credentials map [ string ] string ) error {
2018-08-08 05:42:17 +00:00
var output [ ] byte
var err error
mon := pOpts . Monitors
image := pOpts . VolName
snapID := pOpts . SnapID
2018-08-09 13:07:00 +00:00
key , err := getRBDKey ( adminId , credentials )
2018-08-08 05:42:17 +00:00
if err != nil {
return err
}
2018-08-09 13:07:00 +00:00
glog . V ( 4 ) . Infof ( "rbd: snap rm %s using mon %s, pool %s id %s key %s" , image , pOpts . Monitors , pOpts . Pool , adminId , key )
args := [ ] string { "snap" , "rm" , "--pool" , pOpts . Pool , "--snap" , snapID , image , "--id" , adminId , "-m" , mon , "--key=" + key }
2018-08-08 05:42:17 +00:00
output , err = execCommand ( "rbd" , args )
if err != nil {
return fmt . Errorf ( "failed to delete snapshot: %v, command output: %s" , err , string ( output ) )
}
return nil
}
2018-08-09 13:07:00 +00:00
func restoreSnapshot ( pVolOpts * rbdVolume , pSnapOpts * rbdSnapshot , adminId string , credentials map [ string ] string ) error {
2018-08-08 05:42:17 +00:00
var output [ ] byte
var err error
mon := pVolOpts . Monitors
image := pVolOpts . VolName
snapID := pSnapOpts . SnapID
2018-08-09 13:07:00 +00:00
key , err := getRBDKey ( adminId , credentials )
2018-08-08 05:42:17 +00:00
if err != nil {
return err
}
2018-08-09 13:07:00 +00:00
glog . V ( 4 ) . Infof ( "rbd: clone %s using mon %s, pool %s id %s key %s" , image , pVolOpts . Monitors , pVolOpts . Pool , adminId , key )
args := [ ] string { "clone" , pSnapOpts . Pool + "/" + pSnapOpts . VolName + "@" + snapID , pVolOpts . Pool + "/" + image , "--id" , adminId , "-m" , mon , "--key=" + key }
2018-08-08 05:42:17 +00:00
output , err = execCommand ( "rbd" , args )
if err != nil {
return fmt . Errorf ( "failed to restore snapshot: %v, command output: %s" , err , string ( output ) )
}
return nil
}