Skip to content

Commit

Permalink
Added support to clone a volume by referring to the corresponding PVC…
Browse files Browse the repository at this point in the history
… name

Not quite a solution to #13 but a first step to support clones with Trident.
  • Loading branch information
kangarlou committed Nov 21, 2017
1 parent acd17ce commit 32ee704
Show file tree
Hide file tree
Showing 28 changed files with 224 additions and 128 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Enabled Trident installation on an EF-series (all-flash) array.

**Enhancements:**
- Enabled cloning volumes via a new PVC annotation.
- Added CHAP support for SolidFire (Issue [#42](https://github.com/NetApp/trident/issues/42)).
- Added support for the etcdv3 API (Issue [#45](https://github.com/NetApp/trident/issues/45)).
- Added the etcd-copy utility to migrate data between etcd clusters.
Expand Down
9 changes: 5 additions & 4 deletions core/orchestrator_core.go
Original file line number Diff line number Diff line change
Expand Up @@ -648,9 +648,10 @@ func (o *tridentOrchestrator) CloneVolume(
volumeConfig.Version = config.OrchestratorAPIVersion

// Get the source volume
sourceVolume, found := o.volumes[volumeConfig.SourceName]
sourceVolume, found := o.volumes[volumeConfig.CloneSourceVolume]
if !found {
return nil, fmt.Errorf("Source volume not found: %s", volumeConfig.SourceName)
return nil, fmt.Errorf("Source volume not found: %s",
volumeConfig.CloneSourceVolume)
}
sourceVolumeConfig := sourceVolume.Config

Expand All @@ -661,8 +662,8 @@ func (o *tridentOrchestrator) CloneVolume(
// Copy a few attributes from the request that will affect clone creation
cloneConfig.Name = volumeConfig.Name
cloneConfig.SplitOnClone = volumeConfig.SplitOnClone
cloneConfig.SourceName = volumeConfig.SourceName
cloneConfig.SourceSnapshotName = volumeConfig.SourceSnapshotName
cloneConfig.CloneSourceVolume = volumeConfig.CloneSourceVolume
cloneConfig.CloneSourceSnapshot = volumeConfig.CloneSourceSnapshot

// Add transaction in case the operation must be rolled back later
volTxn, err := o.addVolumeTransaction(volumeConfig)
Expand Down
6 changes: 3 additions & 3 deletions core/orchestrator_core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1006,9 +1006,9 @@ func TestCloneVolumes(t *testing.T) {
// Now clone the volume and ensure everything looks fine
cloneName := s.config.Name + "_clone"
cloneConfig := &storage.VolumeConfig{
Name: cloneName,
StorageClass: s.config.StorageClass,
SourceName: s.config.Name,
Name: cloneName,
StorageClass: s.config.StorageClass,
CloneSourceVolume: s.config.Name,
}
cloneResult, err := orchestrator.CloneVolume(cloneConfig)
if err != nil {
Expand Down
50 changes: 39 additions & 11 deletions docs/reference/concepts/objects.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,16 @@ defaults that you set in the backend configuration:
=================================== ================= ======================================================
Annotation Volume Option Supported Drivers
=================================== ================= ======================================================
trident.netapp.io/fileSystem fileSystem ontap-san, solidfire-san, eseries-iscsi
trident.netapp.io/reclaimPolicy N/A any
trident.netapp.io/cloneFromPVC cloneSourceVolume ontap-nas, ontap-san, solidfire-san
trident.netapp.io/splitOnClone splitOnClone ontap-nas, ontap-san
trident.netapp.io/protocol protocol any
trident.netapp.io/exportPolicy exportPolicy ontap-nas, ontap-nas-economy
trident.netapp.io/snapshotPolicy snapshotPolicy ontap-nas, ontap-nas-economy, ontap-san
trident.netapp.io/snapshotDirectory snapshotDirectory ontap-nas, ontap-nas-economy
trident.netapp.io/unixPermissions unixPermissions ontap-nas, ontap-nas-economy
trident.netapp.io/blockSize blockSize solidfire-san
trident.netapp.io/fileSystem fileSystem ontap-san, solidfire-san, eseries-iscsi
trident.netapp.io/reclaimPolicy N/A any
=================================== ================= ======================================================

The reclaim policy for the created PV can be determined by setting the
Expand All @@ -88,12 +90,35 @@ manually deleted. If the PV uses the ``Retain`` policy, Trident ignores it and
assumes the administrator will clean it up from Kubernetes and the backend,
allowing the volume to be backed up or inspected before its removal. Note that
deleting the PV will not cause Trident to delete the backing volume; it must be
removed manually via the REST API.

``sample-input/pvc-basic.yaml`` and ``sample-input/pvc-full.yaml`` contain
examples of PVC definitions for use with Trident. See
:ref:`Trident Volume objects` for a full description of the parameters and
settings associated with Trident volumes.
removed manually via the REST API (i.e., ``tridentctl``).

One novel aspect of Trident is that users can provision new volumes by cloning
existing volumes. Trident enables this functionality via the PVC annotation
``trident.netapp.io/cloneFromPVC``. For example, if a user already has a PVC
called ``mysql``, she can create a new PVC called ``mysqlclone`` by referring
to the ``mysql`` PVC: ``trident.netapp.io/cloneFromPVC: mysql``. With this
annotation set, Trident clones the volume corresponding to the ``mysql`` PVC,
instead of provisioning a volume from scratch. A few points worth considering
are the following: (1) We recommend cloning an idle volume, (2) a PVC and its
clone must be in the same Kubernetes namespace and have the same storage class,
and (3) with ``ontap-\*`` drivers, it might be desirable to set the PVC
annotation ``trident.netapp.io/splitOnClone`` in conjunction with
``trident.netapp.io/cloneFromPVC``. With ``trident.netapp.io/splitOnClone`` set
to ``true``, Trident splits the cloned volume from the parent volume; thus,
completely decoupling the life cycle of the cloned volume from its parent at
the expense of losing some storage efficiency. Not setting
``trident.netapp.io/splitOnClone`` or setting it to ``false`` results in
reduced space consumption on the backend at the expense of creating
dependencies between the parent and clone volumes such that the parent volume
cannot be deleted unless the clone is deleted first. A scenario where splitting
the clone makes sense is cloning an empty database volume where it's expected
for the volume and its clone to greatly diverge and not benefit from storage
efficiencies offered by ONTAP.

``sample-input/pvc-basic.yaml``, ``sample-input/pvc-basic-clone.yaml``, and
``sample-input/pvc-full.yaml`` contain examples of PVC definitions for use with
Trident. See :ref:`Trident Volume objects` for a full description of the
parameters and settings associated with Trident volumes.

Kubernetes PersistentVolume objects
-----------------------------------
Expand Down Expand Up @@ -165,6 +190,7 @@ provisioningType string thin, thick Pool supports t
backendType string ontap-nas, ontap-nas-economy, Pool belongs to this type of backend Backend specified All drivers
ontap-san, solidfire-san, eseries-iscsi
snapshots bool true, false Pool supports volumes with snapshots Volume with snapshots enabled ontap-nas, ontap-san, solidfire-san
clones bool true, false Pool supports cloning volumes Volume with clones enabled ontap-nas, ontap-san, solidfire-san
encryption bool true, false Pool supports encrypted volumes Volume with encryption enabled ontap-nas, ontap-nas-economy, ontap-san
IOPS int positive integer Pool is capable of guaranteeing IOPS in this range Volume guaranteed these IOPS solidfire-san
================= ====== ======================================= ========================================================== ============================== =========================================================
Expand Down Expand Up @@ -264,9 +290,9 @@ determines where that volume can be provisioned, along with a size.
A volume configuration defines the properties that a provisioned volume should
have.

================= ====== ======== ==============================================================
================= ====== ======== ================================================================
Attribute Type Required Description
================= ====== ======== ==============================================================
================= ====== ======== ================================================================
version string no Version of the Trident API ("1")
name string yes Name of volume to create
storageClass string yes Storage class to use when provisioning the volume
Expand All @@ -279,7 +305,9 @@ snapshotDirectory bool no ontap-nas\*: Whether the snapshot directory is
unixPermissions string no ontap-nas\*: Initial UNIX permissions
blockSize string no solidfire-\*: Block/sector size
fileSystem string no File system type
================= ====== ======== ==============================================================
cloneSourceVolume string no ontap-{nas|san} & solidfire-\*: Name of the volume to clone from
splitOnClone string no ontap-{nas|san}: Split the clone from its parent
================= ====== ======== ================================================================

As mentioned, Trident generates ``internalName`` when creating the volume. This
consists of two steps. First, it prepends the storage prefix -- either the
Expand Down
42 changes: 17 additions & 25 deletions frontend/docker/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,10 @@ func (p *DockerPlugin) Version() string {
func (p *DockerPlugin) Create(request *volume.CreateRequest) error {

log.WithFields(log.Fields{
"Method": "Create",
"Type": "DockerPlugin",
"method": "Create",
"name": request.Name,
"options": request.Options,
}).Debug("Create")
}).Debug("Docker frontend method is invoked.")

// Find a matching storage class, or register a new one
scConfig, err := getStorageClass(request.Options, p.orchestrator)
Expand All @@ -120,7 +119,7 @@ func (p *DockerPlugin) Create(request *volume.CreateRequest) error {
}

// Invoke the orchestrator to create or clone the new volume
if volConfig.SourceName != "" {
if volConfig.CloneSourceVolume != "" {
_, err = p.orchestrator.CloneVolume(volConfig)
} else {
_, err = p.orchestrator.AddVolume(volConfig)
Expand All @@ -131,9 +130,8 @@ func (p *DockerPlugin) Create(request *volume.CreateRequest) error {
func (p *DockerPlugin) List() (*volume.ListResponse, error) {

log.WithFields(log.Fields{
"Method": "List",
"Type": "DockerPlugin",
}).Debug("List")
"method": "List",
}).Debug("Docker frontend method is invoked.")

tridentVols := p.orchestrator.ListVolumes()
var dockerVols []*volume.Volume
Expand All @@ -149,10 +147,9 @@ func (p *DockerPlugin) List() (*volume.ListResponse, error) {
func (p *DockerPlugin) Get(request *volume.GetRequest) (*volume.GetResponse, error) {

log.WithFields(log.Fields{
"Method": "Get",
"Type": "DockerPlugin",
"method": "Get",
"name": request.Name,
}).Debug("Get")
}).Debug("Docker frontend method is invoked")

tridentVol := p.orchestrator.GetVolume(request.Name)
if tridentVol == nil {
Expand All @@ -177,10 +174,9 @@ func (p *DockerPlugin) Get(request *volume.GetRequest) (*volume.GetResponse, err
func (p *DockerPlugin) Remove(request *volume.RemoveRequest) error {

log.WithFields(log.Fields{
"Method": "Remove",
"Type": "DockerPlugin",
"method": "Remove",
"name": request.Name,
}).Debug("Remove")
}).Debug("Docker frontend method is invoked.")

found, err := p.orchestrator.DeleteVolume(request.Name)
if !found {
Expand All @@ -192,10 +188,9 @@ func (p *DockerPlugin) Remove(request *volume.RemoveRequest) error {
func (p *DockerPlugin) Path(request *volume.PathRequest) (*volume.PathResponse, error) {

log.WithFields(log.Fields{
"Method": "Path",
"Type": "DockerPlugin",
"method": "Path",
"name": request.Name,
}).Debug("Path")
}).Debug("Docker frontend method is invoked.")

tridentVol := p.orchestrator.GetVolume(request.Name)
if tridentVol == nil {
Expand All @@ -213,11 +208,10 @@ func (p *DockerPlugin) Path(request *volume.PathRequest) (*volume.PathResponse,
func (p *DockerPlugin) Mount(request *volume.MountRequest) (*volume.MountResponse, error) {

log.WithFields(log.Fields{
"Method": "Mount",
"Type": "DockerPlugin",
"method": "Mount",
"name": request.Name,
"id": request.ID,
}).Debug("Mount")
}).Debug("Docker frontend method is invoked.")

tridentVol := p.orchestrator.GetVolume(request.Name)
if tridentVol == nil {
Expand All @@ -240,11 +234,10 @@ func (p *DockerPlugin) Mount(request *volume.MountRequest) (*volume.MountRespons
func (p *DockerPlugin) Unmount(request *volume.UnmountRequest) error {

log.WithFields(log.Fields{
"Method": "Unmount",
"Type": "DockerPlugin",
"method": "Unmount",
"name": request.Name,
"id": request.ID,
}).Debug("Unmount")
}).Debug("Docker frontend method is invoked.")

tridentVol := p.orchestrator.GetVolume(request.Name)
if tridentVol == nil {
Expand All @@ -265,9 +258,8 @@ func (p *DockerPlugin) Unmount(request *volume.UnmountRequest) error {
func (p *DockerPlugin) Capabilities() *volume.CapabilitiesResponse {

log.WithFields(log.Fields{
"Method": "Capabilities",
"Type": "DockerPlugin",
}).Debug("Capabilities")
"method": "Capabilities",
}).Debug("Docker frontend method is invoked.")

return &volume.CapabilitiesResponse{Capabilities: volume.Capability{Scope: "global"}}
}
Expand Down
36 changes: 18 additions & 18 deletions frontend/docker/volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func makeStorageClass(options map[string]string, o core.Orchestrator) (*storage_
scConfig.Attributes = make(map[string]storage_attribute.Request)
for k, v := range options {
// format: attribute: "type:value"
req, err := storage_attribute.CreateAttributeRequestFromTypedValue(k, v)
req, err := storage_attribute.CreateAttributeRequestFromAttributeValue(k, v)
if err != nil {
log.WithFields(log.Fields{
"storageClass": scConfig.Name,
Expand Down Expand Up @@ -108,22 +108,22 @@ func getVolumeConfig(name, storageClass string, opts map[string]string) (*storag
delete(opts, "size")

return &storage.VolumeConfig{
Name: name,
Size: fmt.Sprintf("%d", sizeBytes),
StorageClass: storageClass,
Protocol: config.ProtocolAny,
AccessMode: config.ModeAny,
SpaceReserve: dvp_utils.GetV(opts, "spaceReserve", ""),
SecurityStyle: dvp_utils.GetV(opts, "securityStyle", ""),
SplitOnClone: dvp_utils.GetV(opts, "splitOnClone", ""),
SnapshotPolicy: dvp_utils.GetV(opts, "snapshotPolicy", ""),
ExportPolicy: dvp_utils.GetV(opts, "exportPolicy", ""),
SnapshotDir: dvp_utils.GetV(opts, "snapshotDir", ""),
UnixPermissions: dvp_utils.GetV(opts, "unixPermissions", ""),
BlockSize: dvp_utils.GetV(opts, "blocksize", ""),
FileSystem: dvp_utils.GetV(opts, "fstype|fileSystemType", ""),
Encryption: dvp_utils.GetV(opts, "encryption", ""),
SourceName: dvp_utils.GetV(opts, "from", ""),
SourceSnapshotName: dvp_utils.GetV(opts, "fromSnapshot", ""),
Name: name,
Size: fmt.Sprintf("%d", sizeBytes),
StorageClass: storageClass,
Protocol: config.ProtocolAny,
AccessMode: config.ModeAny,
SpaceReserve: dvp_utils.GetV(opts, "spaceReserve", ""),
SecurityStyle: dvp_utils.GetV(opts, "securityStyle", ""),
SplitOnClone: dvp_utils.GetV(opts, "splitOnClone", ""),
SnapshotPolicy: dvp_utils.GetV(opts, "snapshotPolicy", ""),
ExportPolicy: dvp_utils.GetV(opts, "exportPolicy", ""),
SnapshotDir: dvp_utils.GetV(opts, "snapshotDir", ""),
UnixPermissions: dvp_utils.GetV(opts, "unixPermissions", ""),
BlockSize: dvp_utils.GetV(opts, "blocksize", ""),
FileSystem: dvp_utils.GetV(opts, "fstype|fileSystemType", ""),
Encryption: dvp_utils.GetV(opts, "encryption", ""),
CloneSourceVolume: dvp_utils.GetV(opts, "from", ""),
CloneSourceSnapshot: dvp_utils.GetV(opts, "fromSnapshot", ""),
}, nil
}
2 changes: 2 additions & 0 deletions frontend/kubernetes/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ const (
AnnExportPolicy = AnnPrefix + "/exportPolicy"
AnnBlockSize = AnnPrefix + "/blockSize"
AnnFileSystem = AnnPrefix + "/fileSystem"
AnnCloneFromPVC = AnnPrefix + "/cloneFromPVC"
AnnSplitOnClone = AnnPrefix + "/splitOnClone"

// Minimum and maximum supported Kubernetes versions
KubernetesVersionMin = "v1.4.0"
Expand Down
Loading

0 comments on commit 32ee704

Please sign in to comment.