Skip to content

Commit

Permalink
Merge pull request #3355 from openebs/volume-snapshot
Browse files Browse the repository at this point in the history
Add volume snapshot and clone support
  • Loading branch information
bboreham authored Oct 18, 2018
2 parents 2a5f871 + 5b8a8c9 commit 1b71a10
Show file tree
Hide file tree
Showing 57 changed files with 3,557 additions and 28 deletions.
11 changes: 10 additions & 1 deletion app/api_topologies.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"time"

"context"

"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"

Expand Down Expand Up @@ -55,6 +56,14 @@ var (
{Value: "hide", Label: "Hide storage", filter: render.IsPodComponent, filterPseudo: false},
},
}
snapshotFilter = APITopologyOptionGroup{
ID: "snapshot",
Default: "hide",
Options: []APITopologyOption{
{Value: "show", Label: "Show snapshots", filter: nil, filterPseudo: false},
{Value: "hide", Label: "Hide snapshots", filter: render.IsNonSnapshotComponent, filterPseudo: false},
},
}
)

// namespaceFilters generates a namespace selector option group based on the given namespaces
Expand Down Expand Up @@ -237,7 +246,7 @@ func MakeRegistry() *Registry {
renderer: render.PodRenderer,
Name: "Pods",
Rank: 3,
Options: []APITopologyOptionGroup{storageFilter, unmanagedFilter},
Options: []APITopologyOptionGroup{snapshotFilter, storageFilter, unmanagedFilter},
HideIfEmpty: true,
},
APITopologyDesc{
Expand Down
6 changes: 2 additions & 4 deletions client/app/scripts/charts/edge.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ import { enterEdge, leaveEdge } from '../actions/app-actions';
import { encodeIdAttribute, decodeIdAttribute } from '../utils/dom-utils';

function isStorageComponent(id) {
if (id === '<persistent_volume>' || id === '<storage_class>' || id === '<persistent_volume_claim>') {
return true;
}
return false;
const storageComponents = ['<persistent_volume>', '<storage_class>', '<persistent_volume_claim>', '<volume_snapshot>', '<volume_snapshot_data>'];
return storageComponents.includes(id);
}

// getAdjacencyClass takes id which contains information about edge as a topology
Expand Down
1 change: 1 addition & 0 deletions client/app/scripts/charts/node-container.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class NodeContainer extends React.Component {
<GraphNode
id={this.props.id}
shape={this.props.shape}
tag={this.props.tag}
label={this.props.label}
labelMinor={this.props.labelMinor}
labelOffset={labelOffset}
Expand Down
1 change: 1 addition & 0 deletions client/app/scripts/charts/nodes-chart-elements.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ class NodesChartElements extends React.Component {
focused={node.get('focused')}
highlighted={node.get('highlighted')}
shape={shape}
tag={node.get('tag')}
stacked={node.get('stack')}
key={node.get('id')}
id={node.get('id')}
Expand Down
8 changes: 8 additions & 0 deletions examples/k8s/cluster-role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,11 @@ rules:
- podsecuritypolicies
verbs:
- use
- apiGroups:
- volumesnapshot.external-storage.k8s.io
resources:
- volumesnapshots
- volumesnapshotdatas
verbs:
- list
- watch
137 changes: 135 additions & 2 deletions probe/kubernetes/client.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package kubernetes

import (
"errors"
"fmt"
"io"
"strings"
"sync"
"time"

"github.com/weaveworks/common/backoff"

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"
Expand All @@ -17,6 +22,7 @@ import (
apiextensionsv1beta1 "k8s.io/api/extensions/v1beta1"
storagev1 "k8s.io/api/storage/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime/schema"
Expand All @@ -41,18 +47,24 @@ type Client interface {
WalkPersistentVolumes(f func(PersistentVolume) error) error
WalkPersistentVolumeClaims(f func(PersistentVolumeClaim) error) error
WalkStorageClasses(f func(StorageClass) error) error
WalkVolumeSnapshots(f func(VolumeSnapshot) error) error
WalkVolumeSnapshotData(f func(VolumeSnapshotData) error) error

WatchPods(f func(Event, Pod))

CloneVolumeSnapshot(namespaceID, volumeSnapshotID, persistentVolumeClaimID, capacity string) error
CreateVolumeSnapshot(namespaceID, persistentVolumeClaimID, capacity string) error
GetLogs(namespaceID, podID string, containerNames []string) (io.ReadCloser, error)
DeletePod(namespaceID, podID string) error
DeleteVolumeSnapshot(namespaceID, volumeSnapshotID string) error
ScaleUp(resource, namespaceID, id string) error
ScaleDown(resource, namespaceID, id string) error
}

type client struct {
quit chan struct{}
client *kubernetes.Clientset
snapshotClient *snapshot.Clientset
podStore cache.Store
serviceStore cache.Store
deploymentStore cache.Store
Expand All @@ -65,6 +77,8 @@ type client struct {
persistentVolumeStore cache.Store
persistentVolumeClaimStore cache.Store
storageClassStore cache.Store
volumeSnapshotStore cache.Store
volumeSnapshotDataStore cache.Store

podWatchesMutex sync.Mutex
podWatches []func(Event, Pod)
Expand Down Expand Up @@ -133,9 +147,15 @@ func NewClient(config ClientConfig) (Client, error) {
return nil, err
}

sc, err := snapshot.NewForConfig(restConfig)
if err != nil {
return nil, err
}

result := &client{
quit: make(chan struct{}),
client: c,
quit: make(chan struct{}),
client: c,
snapshotClient: sc,
}

result.podStore = NewEventStore(result.triggerPodWatches, cache.MetaNamespaceKeyFunc)
Expand All @@ -152,6 +172,8 @@ func NewClient(config ClientConfig) (Client, error) {
result.persistentVolumeStore = result.setupStore("persistentvolumes")
result.persistentVolumeClaimStore = result.setupStore("persistentvolumeclaims")
result.storageClassStore = result.setupStore("storageclasses")
result.volumeSnapshotStore = result.setupStore("volumesnapshots")
result.volumeSnapshotDataStore = result.setupStore("volumesnapshotdatas")

return result, nil
}
Expand Down Expand Up @@ -204,6 +226,10 @@ func (c *client) clientAndType(resource string) (rest.Interface, interface{}, er
return c.client.BatchV1().RESTClient(), &apibatchv1.Job{}, nil
case "statefulsets":
return c.client.AppsV1beta1().RESTClient(), &apiappsv1beta1.StatefulSet{}, nil
case "volumesnapshots":
return c.snapshotClient.VolumesnapshotV1().RESTClient(), &snapshotv1.VolumeSnapshot{}, nil
case "volumesnapshotdatas":
return c.snapshotClient.VolumesnapshotV1().RESTClient(), &snapshotv1.VolumeSnapshotData{}, nil
case "cronjobs":
ok, err := c.isResourceSupported(c.client.BatchV1beta1().RESTClient().APIVersion(), resource)
if err != nil {
Expand Down Expand Up @@ -388,6 +414,109 @@ func (c *client) WalkNamespaces(f func(NamespaceResource) error) error {
return nil
}

func (c *client) WalkVolumeSnapshots(f func(VolumeSnapshot) error) error {
for _, m := range c.volumeSnapshotStore.List() {
volumeSnapshot := m.(*snapshotv1.VolumeSnapshot)
if err := f(NewVolumeSnapshot(volumeSnapshot)); err != nil {
return err
}
}
return nil
}

func (c *client) WalkVolumeSnapshotData(f func(VolumeSnapshotData) error) error {
for _, m := range c.volumeSnapshotDataStore.List() {
volumeSnapshotData := m.(*snapshotv1.VolumeSnapshotData)
if err := f(NewVolumeSnapshotData(volumeSnapshotData)); err != nil {
return err
}
}
return nil
}

func (c *client) CloneVolumeSnapshot(namespaceID, volumeSnapshotID, persistentVolumeClaimID, capacity string) error {
var scName string
var claimSize string
UID := strings.Split(uuid.New(), "-")
scProvisionerName := "volumesnapshot.external-storage.k8s.io/snapshot-promoter"
scList, err := c.client.StorageV1().StorageClasses().List(metav1.ListOptions{})
if err != nil {
return err
}
// Retrieve the first snapshot-promoter storage class
for _, sc := range scList.Items {
if sc.Provisioner == scProvisionerName {
scName = sc.Name
break
}
}
if scName == "" {
return errors.New("snapshot-promoter storage class is not present")
}
volumeSnapshot, _ := c.snapshotClient.VolumesnapshotV1().VolumeSnapshots(namespaceID).Get(volumeSnapshotID, metav1.GetOptions{})
if volumeSnapshot.Spec.PersistentVolumeClaimName != "" {
persistentVolumeClaim, err := c.client.CoreV1().PersistentVolumeClaims(namespaceID).Get(volumeSnapshot.Spec.PersistentVolumeClaimName, metav1.GetOptions{})
if err == nil {
storage := persistentVolumeClaim.Spec.Resources.Requests[apiv1.ResourceStorage]
if storage.String() != "" {
claimSize = storage.String()
}
}
}
// Set default volume size to the one stored in volume snapshot annotation,
// if unable to get PVC size.
if claimSize == "" {
claimSize = capacity
}

persistentVolumeClaim := &apiv1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "clone-" + persistentVolumeClaimID + "-" + UID[1],
Namespace: namespaceID,
Annotations: map[string]string{
"snapshot.alpha.kubernetes.io/snapshot": volumeSnapshotID,
},
},
Spec: apiv1.PersistentVolumeClaimSpec{
StorageClassName: &scName,
AccessModes: []apiv1.PersistentVolumeAccessMode{
apiv1.ReadWriteOnce,
},
Resources: apiv1.ResourceRequirements{
Requests: apiv1.ResourceList{
apiv1.ResourceName(apiv1.ResourceStorage): resource.MustParse(claimSize),
},
},
},
}
_, err = c.client.CoreV1().PersistentVolumeClaims(namespaceID).Create(persistentVolumeClaim)
if err != nil {
return err
}
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 {
Expand Down Expand Up @@ -416,6 +545,10 @@ func (c *client) DeletePod(namespaceID, podID string) error {
return c.client.CoreV1().Pods(namespaceID).Delete(podID, &metav1.DeleteOptions{})
}

func (c *client) DeleteVolumeSnapshot(namespaceID, volumeSnapshotID string) error {
return c.snapshotClient.VolumesnapshotV1().VolumeSnapshots(namespaceID).Delete(volumeSnapshotID, &metav1.DeleteOptions{})
}

func (c *client) ScaleUp(resource, namespaceID, id string) error {
return c.modifyScale(resource, namespaceID, id, func(scale *apiextensionsv1beta1.Scale) {
scale.Spec.Replicas++
Expand Down
Loading

0 comments on commit 1b71a10

Please sign in to comment.