Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add volume snapshot and clone support #3355

Merged
merged 7 commits into from
Oct 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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