From 6b08ce93c5247b3cc7f85b3fcf3c24e6bf7f8895 Mon Sep 17 00:00:00 2001 From: Akash Srivastava Date: Wed, 26 Sep 2018 11:48:04 +0530 Subject: [PATCH] Add control on PVC to take volume snapshot Signed-off-by: Akash Srivastava --- probe/kubernetes/client.go | 24 +++++++++++ probe/kubernetes/controls.go | 49 +++++++++++++++++++---- probe/kubernetes/persistentvolumeclaim.go | 35 ++++++++++++---- probe/kubernetes/reporter.go | 10 ++++- probe/kubernetes/reporter_test.go | 3 ++ report/map_keys.go | 2 + 6 files changed, 106 insertions(+), 17 deletions(-) diff --git a/probe/kubernetes/client.go b/probe/kubernetes/client.go index 54d07d78b5..cbc0fa8d59 100644 --- a/probe/kubernetes/client.go +++ b/probe/kubernetes/client.go @@ -3,6 +3,7 @@ package kubernetes import ( "fmt" "io" + "strings" "sync" "time" @@ -10,6 +11,7 @@ import ( snapshotv1 "github.com/openebs/k8s-snapshot-client/snapshot/pkg/apis/volumesnapshot/v1" snapshot "github.com/openebs/k8s-snapshot-client/snapshot/pkg/client/clientset/versioned" + "github.com/pborman/uuid" log "github.com/sirupsen/logrus" apiappsv1beta1 "k8s.io/api/apps/v1beta1" apibatchv1 "k8s.io/api/batch/v1" @@ -48,6 +50,7 @@ type Client interface { WatchPods(f func(Event, Pod)) + CreateVolumeSnapshot(namespaceID, persistentVolumeClaimID, capacity string) error GetLogs(namespaceID, podID string, containerNames []string) (io.ReadCloser, error) DeletePod(namespaceID, podID string) error ScaleUp(resource, namespaceID, id string) error @@ -427,6 +430,27 @@ func (c *client) WalkVolumeSnapshotData(f func(VolumeSnapshotData) error) error return nil } +func (c *client) CreateVolumeSnapshot(namespaceID, persistentVolumeClaimID, capacity string) error { + UID := strings.Split(uuid.New(), "-") + volumeSnapshot := &snapshotv1.VolumeSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: "snapshot-" + time.Now().Format("20060102150405") + "-" + UID[1], + Namespace: namespaceID, + Annotations: map[string]string{ + "capacity": capacity, + }, + }, + Spec: snapshotv1.VolumeSnapshotSpec{ + PersistentVolumeClaimName: persistentVolumeClaimID, + }, + } + _, err := c.snapshotClient.VolumesnapshotV1().VolumeSnapshots(namespaceID).Create(volumeSnapshot) + if err != nil { + return err + } + return nil +} + func (c *client) GetLogs(namespaceID, podID string, containerNames []string) (io.ReadCloser, error) { readClosersWithLabel := map[io.ReadCloser]string{} for _, container := range containerNames { diff --git a/probe/kubernetes/controls.go b/probe/kubernetes/controls.go index ce55b24d9e..797f571f1a 100644 --- a/probe/kubernetes/controls.go +++ b/probe/kubernetes/controls.go @@ -11,10 +11,11 @@ import ( // Control IDs used by the kubernetes integration. const ( - GetLogs = report.KubernetesGetLogs - DeletePod = report.KubernetesDeletePod - ScaleUp = report.KubernetesScaleUp - ScaleDown = report.KubernetesScaleDown + CreateVolumeSnapshot = report.KubernetesCreateVolumeSnapshot + GetLogs = report.KubernetesGetLogs + DeletePod = report.KubernetesDeletePod + ScaleUp = report.KubernetesScaleUp + ScaleDown = report.KubernetesScaleDown ) // GetLogs is the control to get the logs for a kubernetes pod @@ -43,6 +44,14 @@ func (r *Reporter) GetLogs(req xfer.Request, namespaceID, podID string, containe } } +func (r *Reporter) createVolumeSnapshot(req xfer.Request, namespaceID, persistentVolumeClaimID, capacity string) xfer.Response { + err := r.client.CreateVolumeSnapshot(namespaceID, persistentVolumeClaimID, capacity) + if err != nil { + return xfer.ResponseError(err) + } + return xfer.Response{} +} + func (r *Reporter) deletePod(req xfer.Request, namespaceID, podID string, _ []string) xfer.Response { if err := r.client.DeletePod(namespaceID, podID); err != nil { return xfer.ResponseError(err) @@ -95,6 +104,28 @@ func (r *Reporter) CaptureDeployment(f func(xfer.Request, string, string) xfer.R } } +// CapturePersistentVolumeClaim will return name, namespace and capacity of PVC +func (r *Reporter) CapturePersistentVolumeClaim(f func(xfer.Request, string, string, string) xfer.Response) func(xfer.Request) xfer.Response { + return func(req xfer.Request) xfer.Response { + uid, ok := report.ParsePersistentVolumeClaimNodeID(req.NodeID) + if !ok { + return xfer.ResponseErrorf("Invalid ID: %s", req.NodeID) + } + // find persistentVolumeClaim by UID + var persistentVolumeClaim PersistentVolumeClaim + r.client.WalkPersistentVolumeClaims(func(p PersistentVolumeClaim) error { + if p.UID() == uid { + persistentVolumeClaim = p + } + return nil + }) + if persistentVolumeClaim == nil { + return xfer.ResponseErrorf("Persistent volume claim not found: %s", uid) + } + return f(req, persistentVolumeClaim.Namespace(), persistentVolumeClaim.Name(), persistentVolumeClaim.GetCapacity()) + } +} + // ScaleUp is the control to scale up a deployment func (r *Reporter) ScaleUp(req xfer.Request, namespace, id string) xfer.Response { return xfer.ResponseError(r.client.ScaleUp(report.Deployment, namespace, id)) @@ -107,16 +138,18 @@ func (r *Reporter) ScaleDown(req xfer.Request, namespace, id string) xfer.Respon func (r *Reporter) registerControls() { controls := map[string]xfer.ControlHandlerFunc{ - GetLogs: r.CapturePod(r.GetLogs), - DeletePod: r.CapturePod(r.deletePod), - ScaleUp: r.CaptureDeployment(r.ScaleUp), - ScaleDown: r.CaptureDeployment(r.ScaleDown), + CreateVolumeSnapshot: r.CapturePersistentVolumeClaim(r.createVolumeSnapshot), + GetLogs: r.CapturePod(r.GetLogs), + DeletePod: r.CapturePod(r.deletePod), + ScaleUp: r.CaptureDeployment(r.ScaleUp), + ScaleDown: r.CaptureDeployment(r.ScaleDown), } r.handlerRegistry.Batch(nil, controls) } func (r *Reporter) deregisterControls() { controls := []string{ + CreateVolumeSnapshot, GetLogs, DeletePod, ScaleUp, diff --git a/probe/kubernetes/persistentvolumeclaim.go b/probe/kubernetes/persistentvolumeclaim.go index 665ef44041..6237acc57b 100644 --- a/probe/kubernetes/persistentvolumeclaim.go +++ b/probe/kubernetes/persistentvolumeclaim.go @@ -17,8 +17,9 @@ const ( type PersistentVolumeClaim interface { Meta Selector() (labels.Selector, error) - GetNode() report.Node + GetNode(string) report.Node GetStorageClass() string + GetCapacity() string } // persistentVolumeClaim represents kubernetes Persistent Volume Claims @@ -47,14 +48,32 @@ func (p *persistentVolumeClaim) GetStorageClass() string { return storageClassName } +// GetCapacity returns the storage size of PVC +func (p *persistentVolumeClaim) GetCapacity() string { + capacity := p.Spec.Resources.Requests[apiv1.ResourceStorage] + if capacity.String() != "" { + return capacity.String() + } + return "" +} + // GetNode returns Persistent Volume Claim as Node -func (p *persistentVolumeClaim) GetNode() report.Node { - return p.MetaNode(report.MakePersistentVolumeClaimNodeID(p.UID())).WithLatests(map[string]string{ - NodeType: "Persistent Volume Claim", - Status: string(p.Status.Phase), - VolumeName: p.Spec.VolumeName, - StorageClassName: p.GetStorageClass(), - }) +func (p *persistentVolumeClaim) GetNode(probeID string) report.Node { + latests := map[string]string{ + NodeType: "Persistent Volume Claim", + Status: string(p.Status.Phase), + VolumeName: p.Spec.VolumeName, + StorageClassName: p.GetStorageClass(), + report.ControlProbeID: probeID, + } + + if p.GetCapacity() != "" { + latests[VolumeCapacity] = p.GetCapacity() + } + + return p.MetaNode(report.MakePersistentVolumeClaimNodeID(p.UID())). + WithLatests(latests). + WithLatestActiveControls(CreateVolumeSnapshot) } // Selector returns all Persistent Volume Claim selector diff --git a/probe/kubernetes/reporter.go b/probe/kubernetes/reporter.go index 23a72a83cd..fc243ec4e5 100644 --- a/probe/kubernetes/reporter.go +++ b/probe/kubernetes/reporter.go @@ -36,6 +36,7 @@ const ( StorageDriver = report.KubernetesStorageDriver VolumeSnapshotName = report.KubernetesVolumeSnapshotName SnapshotData = report.KubernetesSnapshotData + VolumeCapacity = report.KubernetesVolumeCapacity ) // Exposed for testing @@ -124,6 +125,7 @@ var ( Status: {ID: Status, Label: "Status", From: report.FromLatest, Priority: 3}, VolumeName: {ID: VolumeName, Label: "Volume", From: report.FromLatest, Priority: 4}, StorageClassName: {ID: StorageClassName, Label: "Storage class", From: report.FromLatest, Priority: 5}, + VolumeCapacity: {ID: VolumeCapacity, Label: "Capacity", From: report.FromLatest, Priority: 6}, } StorageClassMetadataTemplates = report.MetadataTemplates{ @@ -463,8 +465,14 @@ func (r *Reporter) persistentVolumeClaimTopology() (report.Topology, []Persisten result := report.MakeTopology(). WithMetadataTemplates(PersistentVolumeClaimMetadataTemplates). WithTableTemplates(TableTemplates) + result.Controls.AddControl(report.Control{ + ID: CreateVolumeSnapshot, + Human: "Create snapshot", + Icon: "fa-camera", + Rank: 0, + }) err := r.client.WalkPersistentVolumeClaims(func(p PersistentVolumeClaim) error { - result.AddNode(p.GetNode()) + result.AddNode(p.GetNode(r.probeID)) persistentVolumeClaims = append(persistentVolumeClaims, p) return nil }) diff --git a/probe/kubernetes/reporter_test.go b/probe/kubernetes/reporter_test.go index dea19c64cd..2165d5b4c1 100644 --- a/probe/kubernetes/reporter_test.go +++ b/probe/kubernetes/reporter_test.go @@ -187,6 +187,9 @@ func (c *mockClient) ScaleUp(resource, namespaceID, id string) error { func (c *mockClient) ScaleDown(resource, namespaceID, id string) error { return nil } +func (c *mockClient) CreateVolumeSnapshot(namespaceID, persistentVolumeClaimID string) error { + return nil +} type mockPipeClient map[string]xfer.Pipe diff --git a/report/map_keys.go b/report/map_keys.go index e6d39c810d..97a7a49ef9 100644 --- a/report/map_keys.go +++ b/report/map_keys.go @@ -83,6 +83,8 @@ const ( KubernetesStorageDriver = "kubernetes_storage_driver" KubernetesVolumeSnapshotName = "kubernetes_volume_snapshot_name" KubernetesSnapshotData = "kuberneets_snapshot_data" + KubernetesCreateVolumeSnapshot = "kubernetes_create_volume_snapshot" + KubernetesVolumeCapacity = "kubernetes_volume_capacity" // probe/awsecs ECSCluster = "ecs_cluster" ECSCreatedAt = "ecs_created_at"