From 6ec49df225fa608aedf25ea863f11fd30f2c4953 Mon Sep 17 00:00:00 2001 From: Pawan Prakash Sharma Date: Mon, 1 Mar 2021 23:56:42 +0530 Subject: [PATCH] fix(restore): adding support to restore in an encrypted pool (#292) Encrypted pool does not allow the volume to be pre created for the restore purpose. Here changing the design to do the restore first and then create the ZFSVolume object which will bind the volume already created while doing restore. Signed-off-by: Pawan --- changelogs/unreleased/292-pawanpraka1 | 1 + deploy/yamls/zfsrestore-crd.yaml | 145 ++++++++++++++++++ deploy/zfs-operator.yaml | 145 ++++++++++++++++++ pkg/apis/openebs.io/zfs/v1/zfsrestore.go | 1 + .../zfs/v1/zz_generated.deepcopy.go | 1 + pkg/builder/restorebuilder/build.go | 6 + pkg/zfs/zfs_util.go | 89 +++++++++-- 7 files changed, 375 insertions(+), 13 deletions(-) create mode 100644 changelogs/unreleased/292-pawanpraka1 diff --git a/changelogs/unreleased/292-pawanpraka1 b/changelogs/unreleased/292-pawanpraka1 new file mode 100644 index 0000000..46a2562 --- /dev/null +++ b/changelogs/unreleased/292-pawanpraka1 @@ -0,0 +1 @@ +adding support to restore in an encrypted pool diff --git a/deploy/yamls/zfsrestore-crd.yaml b/deploy/yamls/zfsrestore-crd.yaml index c1af836..955cc4b 100644 --- a/deploy/yamls/zfsrestore-crd.yaml +++ b/deploy/yamls/zfsrestore-crd.yaml @@ -76,6 +76,151 @@ spec: - InProgress - Invalid type: string + volSpec: + description: VolumeInfo defines ZFS volume parameters for all modes in which + ZFS volumes can be created like - ZFS volume with filesystem, ZFS Volume + exposed as zfs or ZFS volume exposed as raw block device. Some of the + parameters can be only set during creation time (as specified in the details + of the parameter), and a few are editable. In case of Cloned volumes, + the parameters are assigned the same values as the source volume. + properties: + capacity: + description: Capacity of the volume + minLength: 1 + type: string + compression: + description: 'Compression specifies the block-level compression algorithm + to be applied to the ZFS Volume. The value "on" indicates ZFS to use + the default compression algorithm. The default compression algorithm + used by ZFS will be either lzjb or, if the lz4_compress feature is + enabled, lz4. Compression property can be edited after the volume + has been created. The change will only be applied to the newly-written + data. For instance, if the Volume was created with "off" and the next + day the compression was modified to "on", the data written prior to + setting "on" will not be compressed. Default Value: off.' + pattern: ^(on|off|lzjb|gzip|gzip-[1-9]|zle|lz4)$ + type: string + dedup: + description: 'Deduplication is the process for removing redundant data + at the block level, reducing the total amount of data stored. If a + file system has the dedup property enabled, duplicate data blocks + are removed synchronously. The result is that only unique data is + stored and common components are shared among files. Deduplication + can consume significant processing power (CPU) and memory as well + as generate additional disk IO. Before creating a pool with deduplication + enabled, ensure that you have planned your hardware requirements appropriately + and implemented appropriate recovery practices, such as regular backups. + As an alternative to deduplication consider using compression=lz4, + as a less resource-intensive alternative. should be enabled on the + zvol. Dedup property can be edited after the volume has been created. + Default Value: off.' + enum: + - "on" + - "off" + type: string + encryption: + description: 'Enabling the encryption feature allows for the creation + of encrypted filesystems and volumes. ZFS will encrypt file and zvol + data, file attributes, ACLs, permission bits, directory listings, + FUID mappings, and userused / groupused data. ZFS will not encrypt + metadata related to the pool structure, including dataset and snapshot + names, dataset hierarchy, properties, file size, file holes, and deduplication + tables (though the deduplicated data itself is encrypted). Default + Value: off.' + pattern: ^(on|off|aes-128-[c,g]cm|aes-192-[c,g]cm|aes-256-[c,g]cm)$ + type: string + fsType: + description: 'FsType specifies filesystem type for the zfs volume/dataset. + If FsType is provided as "zfs", then the driver will create a ZFS + dataset, formatting is not required as underlying filesystem is ZFS + anyway. If FsType is ext2, ext3, ext4 or xfs, then the driver will + create a ZVOL and format the volume accordingly. FsType can not be + modified once volume has been provisioned. Default Value: ext4.' + type: string + keyformat: + description: KeyFormat specifies format of the encryption key The supported + KeyFormats are passphrase, raw, hex. + enum: + - passphrase + - raw + - hex + type: string + keylocation: + description: KeyLocation is the location of key for the encryption + type: string + ownerNodeID: + description: OwnerNodeID is the Node ID where the ZPOOL is running which + is where the volume has been provisioned. OwnerNodeID can not be edited + after the volume has been provisioned. + minLength: 1 + type: string + poolName: + description: poolName specifies the name of the pool where the volume + has been created. PoolName can not be edited after the volume has + been provisioned. + minLength: 1 + type: string + recordsize: + description: 'Specifies a suggested block size for files in the file + system. The size specified must be a power of two greater than or + equal to 512 and less than or equal to 128 Kbytes. RecordSize property + can be edited after the volume has been created. Changing the file + system''s recordsize affects only files created afterward; existing + files are unaffected. Default Value: 128k.' + minLength: 1 + type: string + shared: + description: Shared specifies whether the volume can be shared among + multiple pods. If it is not set to "yes", then the ZFS-LocalPV Driver + will not allow the volumes to be mounted by more than one pods. + enum: + - "yes" + - "no" + type: string + snapname: + description: SnapName specifies the name of the snapshot where the volume + has been cloned from. Snapname can not be edited after the volume + has been provisioned. + type: string + thinProvision: + description: 'ThinProvision describes whether space reservation for + the source volume is required or not. The value "yes" indicates that + volume should be thin provisioned and "no" means thick provisioning + of the volume. If thinProvision is set to "yes" then volume can be + provisioned even if the ZPOOL does not have the enough capacity. If + thinProvision is set to "no" then volume can be provisioned only if + the ZPOOL has enough capacity and capacity required by volume can + be reserved. ThinProvision can not be modified once volume has been + provisioned. Default Value: no.' + enum: + - "yes" + - "no" + type: string + volblocksize: + description: 'VolBlockSize specifies the block size for the zvol. The + volsize can only be set to a multiple of volblocksize, and cannot + be zero. VolBlockSize can not be edited after the volume has been + provisioned. Default Value: 8k.' + minLength: 1 + type: string + volumeType: + description: volumeType determines whether the volume is of type "DATASET" + or "ZVOL". If fstype provided in the storageclass is "zfs", a volume + of type dataset will be created. If "ext4", "ext3", "ext2" or "xfs" + is mentioned as fstype in the storageclass, then a volume of type + zvol will be created, which will be further formatted as the fstype + provided in the storageclass. VolumeType can not be modified once + volume has been provisioned. + enum: + - ZVOL + - DATASET + type: string + required: + - capacity + - ownerNodeID + - poolName + - volumeType + type: object required: - spec - status diff --git a/deploy/zfs-operator.yaml b/deploy/zfs-operator.yaml index 969f2fc..1916559 100644 --- a/deploy/zfs-operator.yaml +++ b/deploy/zfs-operator.yaml @@ -1022,6 +1022,151 @@ spec: - InProgress - Invalid type: string + volSpec: + description: VolumeInfo defines ZFS volume parameters for all modes in which + ZFS volumes can be created like - ZFS volume with filesystem, ZFS Volume + exposed as zfs or ZFS volume exposed as raw block device. Some of the + parameters can be only set during creation time (as specified in the details + of the parameter), and a few are editable. In case of Cloned volumes, + the parameters are assigned the same values as the source volume. + properties: + capacity: + description: Capacity of the volume + minLength: 1 + type: string + compression: + description: 'Compression specifies the block-level compression algorithm + to be applied to the ZFS Volume. The value "on" indicates ZFS to use + the default compression algorithm. The default compression algorithm + used by ZFS will be either lzjb or, if the lz4_compress feature is + enabled, lz4. Compression property can be edited after the volume + has been created. The change will only be applied to the newly-written + data. For instance, if the Volume was created with "off" and the next + day the compression was modified to "on", the data written prior to + setting "on" will not be compressed. Default Value: off.' + pattern: ^(on|off|lzjb|gzip|gzip-[1-9]|zle|lz4)$ + type: string + dedup: + description: 'Deduplication is the process for removing redundant data + at the block level, reducing the total amount of data stored. If a + file system has the dedup property enabled, duplicate data blocks + are removed synchronously. The result is that only unique data is + stored and common components are shared among files. Deduplication + can consume significant processing power (CPU) and memory as well + as generate additional disk IO. Before creating a pool with deduplication + enabled, ensure that you have planned your hardware requirements appropriately + and implemented appropriate recovery practices, such as regular backups. + As an alternative to deduplication consider using compression=lz4, + as a less resource-intensive alternative. should be enabled on the + zvol. Dedup property can be edited after the volume has been created. + Default Value: off.' + enum: + - "on" + - "off" + type: string + encryption: + description: 'Enabling the encryption feature allows for the creation + of encrypted filesystems and volumes. ZFS will encrypt file and zvol + data, file attributes, ACLs, permission bits, directory listings, + FUID mappings, and userused / groupused data. ZFS will not encrypt + metadata related to the pool structure, including dataset and snapshot + names, dataset hierarchy, properties, file size, file holes, and deduplication + tables (though the deduplicated data itself is encrypted). Default + Value: off.' + pattern: ^(on|off|aes-128-[c,g]cm|aes-192-[c,g]cm|aes-256-[c,g]cm)$ + type: string + fsType: + description: 'FsType specifies filesystem type for the zfs volume/dataset. + If FsType is provided as "zfs", then the driver will create a ZFS + dataset, formatting is not required as underlying filesystem is ZFS + anyway. If FsType is ext2, ext3, ext4 or xfs, then the driver will + create a ZVOL and format the volume accordingly. FsType can not be + modified once volume has been provisioned. Default Value: ext4.' + type: string + keyformat: + description: KeyFormat specifies format of the encryption key The supported + KeyFormats are passphrase, raw, hex. + enum: + - passphrase + - raw + - hex + type: string + keylocation: + description: KeyLocation is the location of key for the encryption + type: string + ownerNodeID: + description: OwnerNodeID is the Node ID where the ZPOOL is running which + is where the volume has been provisioned. OwnerNodeID can not be edited + after the volume has been provisioned. + minLength: 1 + type: string + poolName: + description: poolName specifies the name of the pool where the volume + has been created. PoolName can not be edited after the volume has + been provisioned. + minLength: 1 + type: string + recordsize: + description: 'Specifies a suggested block size for files in the file + system. The size specified must be a power of two greater than or + equal to 512 and less than or equal to 128 Kbytes. RecordSize property + can be edited after the volume has been created. Changing the file + system''s recordsize affects only files created afterward; existing + files are unaffected. Default Value: 128k.' + minLength: 1 + type: string + shared: + description: Shared specifies whether the volume can be shared among + multiple pods. If it is not set to "yes", then the ZFS-LocalPV Driver + will not allow the volumes to be mounted by more than one pods. + enum: + - "yes" + - "no" + type: string + snapname: + description: SnapName specifies the name of the snapshot where the volume + has been cloned from. Snapname can not be edited after the volume + has been provisioned. + type: string + thinProvision: + description: 'ThinProvision describes whether space reservation for + the source volume is required or not. The value "yes" indicates that + volume should be thin provisioned and "no" means thick provisioning + of the volume. If thinProvision is set to "yes" then volume can be + provisioned even if the ZPOOL does not have the enough capacity. If + thinProvision is set to "no" then volume can be provisioned only if + the ZPOOL has enough capacity and capacity required by volume can + be reserved. ThinProvision can not be modified once volume has been + provisioned. Default Value: no.' + enum: + - "yes" + - "no" + type: string + volblocksize: + description: 'VolBlockSize specifies the block size for the zvol. The + volsize can only be set to a multiple of volblocksize, and cannot + be zero. VolBlockSize can not be edited after the volume has been + provisioned. Default Value: 8k.' + minLength: 1 + type: string + volumeType: + description: volumeType determines whether the volume is of type "DATASET" + or "ZVOL". If fstype provided in the storageclass is "zfs", a volume + of type dataset will be created. If "ext4", "ext3", "ext2" or "xfs" + is mentioned as fstype in the storageclass, then a volume of type + zvol will be created, which will be further formatted as the fstype + provided in the storageclass. VolumeType can not be modified once + volume has been provisioned. + enum: + - ZVOL + - DATASET + type: string + required: + - capacity + - ownerNodeID + - poolName + - volumeType + type: object required: - spec - status diff --git a/pkg/apis/openebs.io/zfs/v1/zfsrestore.go b/pkg/apis/openebs.io/zfs/v1/zfsrestore.go index e832261..8a3891c 100644 --- a/pkg/apis/openebs.io/zfs/v1/zfsrestore.go +++ b/pkg/apis/openebs.io/zfs/v1/zfsrestore.go @@ -29,6 +29,7 @@ type ZFSRestore struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` // set name to restore name + volume name + something like csp tag Spec ZFSRestoreSpec `json:"spec"` + VolSpec VolumeInfo `json:"volSpec,omitempty"` // +kubebuilder:validation:Required // +kubebuilder:validation:Enum=Init;Done;Failed;Pending;InProgress;Invalid Status ZFSRestoreStatus `json:"status"` diff --git a/pkg/apis/openebs.io/zfs/v1/zz_generated.deepcopy.go b/pkg/apis/openebs.io/zfs/v1/zz_generated.deepcopy.go index 54737fd..e16e707 100644 --- a/pkg/apis/openebs.io/zfs/v1/zz_generated.deepcopy.go +++ b/pkg/apis/openebs.io/zfs/v1/zz_generated.deepcopy.go @@ -154,6 +154,7 @@ func (in *ZFSRestore) DeepCopyInto(out *ZFSRestore) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) out.Spec = in.Spec + out.VolSpec = in.VolSpec return } diff --git a/pkg/builder/restorebuilder/build.go b/pkg/builder/restorebuilder/build.go index c7e28bb..c6c3b5d 100644 --- a/pkg/builder/restorebuilder/build.go +++ b/pkg/builder/restorebuilder/build.go @@ -99,6 +99,12 @@ func (b *Builder) WithVolume(name string) *Builder { return b } +// WithVolSpec copies volume spec to ZFSRestore Object +func (b *Builder) WithVolSpec(vspec apis.VolumeInfo) *Builder { + b.rstr.Object.VolSpec = vspec + return b +} + // WithNode sets the node id for ZFSRestore func (b *Builder) WithNode(node string) *Builder { if node == "" { diff --git a/pkg/zfs/zfs_util.go b/pkg/zfs/zfs_util.go index 625d106..3fccad0 100644 --- a/pkg/zfs/zfs_util.go +++ b/pkg/zfs/zfs_util.go @@ -21,6 +21,8 @@ import ( "path/filepath" "fmt" + "os" + "time" "strings" @@ -329,11 +331,12 @@ func buildVolumeBackupArgs(bkp *apis.ZFSBackup, vol *apis.ZFSVolume) ([]string, } // builldVolumeRestoreArgs returns volume recv command for receiving the zfs volume -func buildVolumeRestoreArgs(rstr *apis.ZFSRestore, vol *apis.ZFSVolume) ([]string, error) { +func buildVolumeRestoreArgs(rstr *apis.ZFSRestore) ([]string, error) { var ZFSVolArg []string + var ZFSRecvParam string restoreSrc := rstr.Spec.RestoreSrc - volume := vol.Spec.PoolName + "/" + vol.Name + volume := rstr.VolSpec.PoolName + "/" + rstr.Spec.VolumeName rstrAddr := strings.Split(restoreSrc, ":") if len(rstrAddr) != 2 { @@ -342,7 +345,36 @@ func buildVolumeRestoreArgs(rstr *apis.ZFSRestore, vol *apis.ZFSVolume) ([]strin source := "nc -w 3 " + rstrAddr[0] + " " + rstrAddr[1] + " | " - cmd := source + ZFSVolCmd + " " + ZFSRecvArg + " -F " + volume + if rstr.VolSpec.VolumeType == VolTypeDataset { + if len(rstr.VolSpec.Capacity) != 0 { + ZFSRecvParam += " -o quota=" + rstr.VolSpec.Capacity + } + if len(rstr.VolSpec.RecordSize) != 0 { + ZFSRecvParam += " -o recordsize=" + rstr.VolSpec.RecordSize + } + if rstr.VolSpec.ThinProvision == "no" { + ZFSRecvParam += " -o reservation=" + rstr.VolSpec.Capacity + } + ZFSRecvParam += " -o mountpoint=legacy" + } + + if len(rstr.VolSpec.Dedup) != 0 { + ZFSRecvParam += " -o dedup=" + rstr.VolSpec.Dedup + } + if len(rstr.VolSpec.Compression) != 0 { + ZFSRecvParam += " -o compression=" + rstr.VolSpec.Compression + } + if len(rstr.VolSpec.Encryption) != 0 { + ZFSRecvParam += " -o encryption=" + rstr.VolSpec.Encryption + } + if len(rstr.VolSpec.KeyLocation) != 0 { + ZFSRecvParam += " -o keylocation=" + rstr.VolSpec.KeyLocation + } + if len(rstr.VolSpec.KeyFormat) != 0 { + ZFSRecvParam += " -o keyformat=" + rstr.VolSpec.KeyFormat + } + + cmd := source + ZFSVolCmd + " " + ZFSRecvArg + ZFSRecvParam + " -F " + volume ZFSVolArg = append(ZFSVolArg, "-c", cmd) @@ -794,18 +826,43 @@ func DestoryBackup(bkp *apis.ZFSBackup) error { return err } +// getDevice waits for the device to be created and returns the devpath +func getDevice(volume string) (string, error) { + device := ZFSDevPath + volume + // device should be created within 5 seconds + timeout := time.After(5 * time.Second) + for { + select { + case <-timeout: + return "", fmt.Errorf("zfs: not able to get the device: %s", device) + default: + if _, err := os.Stat(device); err == nil { + return device, nil + } + } + time.Sleep(1 * time.Second) + } +} + // CreateRestore creates the restore func CreateRestore(rstr *apis.ZFSRestore) error { - vol, err := GetZFSVolume(rstr.Spec.VolumeName) - if err != nil { - return err - } - volume := vol.Spec.PoolName + "/" + vol.Name - args, err := buildVolumeRestoreArgs(rstr, vol) + if len(rstr.VolSpec.PoolName) == 0 { + // for backward compatibility, older version of + // velero will not add spec in the ZFSRestore Object + // query it here and fill that information + vol, err := GetZFSVolume(rstr.Spec.VolumeName) + if err != nil { + return err + } + rstr.VolSpec = vol.Spec + } + args, err := buildVolumeRestoreArgs(rstr) if err != nil { return err } + volume := rstr.VolSpec.PoolName + "/" + rstr.Spec.VolumeName + cmd := exec.Command("bash", args...) out, err := cmd.CombinedOutput() @@ -820,12 +877,18 @@ func CreateRestore(rstr *apis.ZFSRestore) error { * need to generate a new uuid for zfs and btrfs volumes * so that we can mount it. */ - if vol.Spec.FsType == "xfs" { - device := ZFSDevPath + volume + if rstr.VolSpec.FsType == "xfs" { + device, err := getDevice(volume) + if err != nil { + return err + } return xfs.GenerateUUID(device) } - if vol.Spec.FsType == "btrfs" { - device := ZFSDevPath + volume + if rstr.VolSpec.FsType == "btrfs" { + device, err := getDevice(volume) + if err != nil { + return err + } return btrfs.GenerateUUID(device) }