diff --git a/app/api_topologies.go b/app/api_topologies.go index 6b286a57f..7298542b4 100644 --- a/app/api_topologies.go +++ b/app/api_topologies.go @@ -10,6 +10,7 @@ import ( "time" "context" + "github.com/gorilla/mux" log "github.com/sirupsen/logrus" @@ -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 @@ -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{ diff --git a/client/app/scripts/charts/edge.js b/client/app/scripts/charts/edge.js index c4f9d6b98..d6d35e7b7 100644 --- a/client/app/scripts/charts/edge.js +++ b/client/app/scripts/charts/edge.js @@ -6,10 +6,8 @@ import { enterEdge, leaveEdge } from '../actions/app-actions'; import { encodeIdAttribute, decodeIdAttribute } from '../utils/dom-utils'; function isStorageComponent(id) { - if (id === '' || id === '' || id === '') { - return true; - } - return false; + const storageComponents = ['', '', '', '', '']; + return storageComponents.includes(id); } // getAdjacencyClass takes id which contains information about edge as a topology diff --git a/probe/kubernetes/client.go b/probe/kubernetes/client.go index edaa0a5a6..54d07d78b 100644 --- a/probe/kubernetes/client.go +++ b/probe/kubernetes/client.go @@ -8,6 +8,8 @@ import ( "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" log "github.com/sirupsen/logrus" apiappsv1beta1 "k8s.io/api/apps/v1beta1" apibatchv1 "k8s.io/api/batch/v1" @@ -41,6 +43,8 @@ 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)) @@ -53,6 +57,7 @@ type Client interface { type client struct { quit chan struct{} client *kubernetes.Clientset + snapshotClient *snapshot.Clientset podStore cache.Store serviceStore cache.Store deploymentStore cache.Store @@ -65,6 +70,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) @@ -133,9 +140,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) @@ -152,6 +165,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 } @@ -204,6 +219,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 { @@ -388,6 +407,26 @@ 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) GetLogs(namespaceID, podID string, containerNames []string) (io.ReadCloser, error) { readClosersWithLabel := map[io.ReadCloser]string{} for _, container := range containerNames { diff --git a/probe/kubernetes/reporter.go b/probe/kubernetes/reporter.go index 3dfa8c793..23a72a83c 100644 --- a/probe/kubernetes/reporter.go +++ b/probe/kubernetes/reporter.go @@ -34,6 +34,8 @@ const ( VolumeName = report.KubernetesVolumeName Provisioner = report.KubernetesProvisioner StorageDriver = report.KubernetesStorageDriver + VolumeSnapshotName = report.KubernetesVolumeSnapshotName + SnapshotData = report.KubernetesSnapshotData ) // Exposed for testing @@ -130,6 +132,19 @@ var ( Provisioner: {ID: Provisioner, Label: "Provisioner", From: report.FromLatest, Priority: 3}, } + VolumeSnapshotMetadataTemplates = report.MetadataTemplates{ + NodeType: {ID: NodeType, Label: "Type", From: report.FromLatest, Priority: 1}, + Namespace: {ID: Namespace, Label: "Name", From: report.FromLatest, Priority: 2}, + VolumeClaim: {ID: VolumeClaim, Label: "Persistent volume claim", From: report.FromLatest, Priority: 3}, + SnapshotData: {ID: SnapshotData, Label: "Volume snapshot data", From: report.FromLatest, Priority: 4}, + } + + VolumeSnapshotDataMetadataTemplates = report.MetadataTemplates{ + NodeType: {ID: NodeType, Label: "Type", From: report.FromLatest, Priority: 1}, + VolumeName: {ID: VolumeName, Label: "Persistent volume", From: report.FromLatest, Priority: 2}, + VolumeSnapshotName: {ID: VolumeSnapshotName, Label: "Volume snapshot", From: report.FromLatest, Priority: 3}, + } + TableTemplates = report.TableTemplates{ LabelPrefix: { ID: LabelPrefix, @@ -300,6 +315,14 @@ func (r *Reporter) Report() (report.Report, error) { if err != nil { return result, err } + volumeSnapshotTopology, _, err := r.volumeSnapshotTopology() + if err != nil { + return result, err + } + volumeSnapshotDataTopology, _, err := r.volumeSnapshotDataTopology() + if err != nil { + return result, err + } result.Pod = result.Pod.Merge(podTopology) result.Service = result.Service.Merge(serviceTopology) result.Host = result.Host.Merge(hostTopology) @@ -311,6 +334,8 @@ func (r *Reporter) Report() (report.Report, error) { result.PersistentVolume = result.PersistentVolume.Merge(persistentVolumeTopology) result.PersistentVolumeClaim = result.PersistentVolumeClaim.Merge(persistentVolumeClaimTopology) result.StorageClass = result.StorageClass.Merge(storageClassTopology) + result.VolumeSnapshot = result.VolumeSnapshot.Merge(volumeSnapshotTopology) + result.VolumeSnapshotData = result.VolumeSnapshotData.Merge(volumeSnapshotDataTopology) return result, nil } @@ -459,6 +484,32 @@ func (r *Reporter) storageClassTopology() (report.Topology, []StorageClass, erro return result, storageClasses, err } +func (r *Reporter) volumeSnapshotTopology() (report.Topology, []VolumeSnapshot, error) { + volumeSnapshots := []VolumeSnapshot{} + result := report.MakeTopology(). + WithMetadataTemplates(VolumeSnapshotMetadataTemplates). + WithTableTemplates(TableTemplates) + err := r.client.WalkVolumeSnapshots(func(p VolumeSnapshot) error { + result.AddNode(p.GetNode(r.probeID)) + volumeSnapshots = append(volumeSnapshots, p) + return nil + }) + return result, volumeSnapshots, err +} + +func (r *Reporter) volumeSnapshotDataTopology() (report.Topology, []VolumeSnapshotData, error) { + volumeSnapshotData := []VolumeSnapshotData{} + result := report.MakeTopology(). + WithMetadataTemplates(VolumeSnapshotDataMetadataTemplates). + WithTableTemplates(TableTemplates) + err := r.client.WalkVolumeSnapshotData(func(p VolumeSnapshotData) error { + result.AddNode(p.GetNode(r.probeID)) + volumeSnapshotData = append(volumeSnapshotData, p) + return nil + }) + return result, volumeSnapshotData, err +} + type labelledChild interface { Labels() map[string]string AddParent(string, string) diff --git a/probe/kubernetes/reporter_test.go b/probe/kubernetes/reporter_test.go index 70e083b3a..dea19c64c 100644 --- a/probe/kubernetes/reporter_test.go +++ b/probe/kubernetes/reporter_test.go @@ -164,6 +164,12 @@ func (c *mockClient) WalkPersistentVolumeClaims(f func(kubernetes.PersistentVolu func (c *mockClient) WalkStorageClasses(f func(kubernetes.StorageClass) error) error { return nil } +func (c *mockClient) WalkVolumeSnapshots(f func(kubernetes.VolumeSnapshot) error) error { + return nil +} +func (c *mockClient) WalkVolumeSnapshotData(f func(kubernetes.VolumeSnapshotData) error) error { + return nil +} func (*mockClient) WatchPods(func(kubernetes.Event, kubernetes.Pod)) {} func (c *mockClient) GetLogs(namespaceID, podName string, _ []string) (io.ReadCloser, error) { r, ok := c.logs[namespaceID+";"+podName] diff --git a/probe/kubernetes/volumesnapshot.go b/probe/kubernetes/volumesnapshot.go new file mode 100644 index 000000000..153bf23c2 --- /dev/null +++ b/probe/kubernetes/volumesnapshot.go @@ -0,0 +1,39 @@ +package kubernetes + +import ( + snapshotv1 "github.com/openebs/k8s-snapshot-client/snapshot/pkg/apis/volumesnapshot/v1" + "github.com/weaveworks/scope/report" +) + +// SnapshotPVName is the label key which provides PV name +const ( + SnapshotPVName = "SnapshotMetadata-PVName" +) + +// VolumeSnapshot represent kubernetes VolumeSnapshot interface +type VolumeSnapshot interface { + Meta + GetNode(probeID string) report.Node +} + +// volumeSnapshot represents kubernetes volume snapshots +type volumeSnapshot struct { + *snapshotv1.VolumeSnapshot + Meta +} + +// NewVolumeSnapshot returns new Volume Snapshot type +func NewVolumeSnapshot(p *snapshotv1.VolumeSnapshot) VolumeSnapshot { + return &volumeSnapshot{VolumeSnapshot: p, Meta: meta{p.ObjectMeta}} +} + +// GetNode returns VolumeSnapshot as Node +func (p *volumeSnapshot) GetNode(probeID string) report.Node { + return p.MetaNode(report.MakeVolumeSnapshotNodeID(p.UID())).WithLatests(map[string]string{ + report.ControlProbeID: probeID, + NodeType: "Volume Snapshot", + VolumeClaim: p.Spec.PersistentVolumeClaimName, + SnapshotData: p.Spec.SnapshotDataName, + VolumeName: p.GetLabels()[SnapshotPVName], + }) +} diff --git a/probe/kubernetes/volumesnapshotdata.go b/probe/kubernetes/volumesnapshotdata.go new file mode 100644 index 000000000..0516bf6ec --- /dev/null +++ b/probe/kubernetes/volumesnapshotdata.go @@ -0,0 +1,33 @@ +package kubernetes + +import ( + snapshotv1 "github.com/openebs/k8s-snapshot-client/snapshot/pkg/apis/volumesnapshot/v1" + "github.com/weaveworks/scope/report" +) + +// VolumeSnapshotData represent kubernetes VolumeSnapshotData interface +type VolumeSnapshotData interface { + Meta + GetNode(probeID string) report.Node +} + +// volumeSnapshotData represents kubernetes volume snapshot data +type volumeSnapshotData struct { + *snapshotv1.VolumeSnapshotData + Meta +} + +// NewVolumeSnapshotData returns new Volume Snapshot Data type +func NewVolumeSnapshotData(p *snapshotv1.VolumeSnapshotData) VolumeSnapshotData { + return &volumeSnapshotData{VolumeSnapshotData: p, Meta: meta{p.ObjectMeta}} +} + +// GetNode returns VolumeSnapshotData as Node +func (p *volumeSnapshotData) GetNode(probeID string) report.Node { + return p.MetaNode(report.MakeVolumeSnapshotDataNodeID(p.UID())).WithLatests(map[string]string{ + report.ControlProbeID: probeID, + NodeType: "Volume Snapshot Data", + VolumeName: p.Spec.PersistentVolumeRef.Name, + VolumeSnapshotName: p.Spec.VolumeSnapshotRef.Name, + }) +} diff --git a/render/detailed/node.go b/render/detailed/node.go index f7bdeef2e..459cc593f 100644 --- a/render/detailed/node.go +++ b/render/detailed/node.go @@ -206,6 +206,20 @@ var nodeSummaryGroupSpecs = []struct { Columns: []Column{}, }, }, + { + topologyID: report.VolumeSnapshot, + NodeSummaryGroup: NodeSummaryGroup{ + Label: "Volume Snapshots", + Columns: []Column{}, + }, + }, + { + topologyID: report.VolumeSnapshotData, + NodeSummaryGroup: NodeSummaryGroup{ + Label: "Volume Snapshot Data", + Columns: []Column{}, + }, + }, } func children(rc RenderContext, n report.Node) []NodeSummaryGroup { diff --git a/render/detailed/summary.go b/render/detailed/summary.go index e43ad19db..d50417eb6 100644 --- a/render/detailed/summary.go +++ b/render/detailed/summary.go @@ -84,6 +84,8 @@ var renderers = map[string]func(BasicNodeSummary, report.Node) BasicNodeSummary{ report.PersistentVolume: persistentVolumeNodeSummary, report.PersistentVolumeClaim: persistentVolumeClaimNodeSummary, report.StorageClass: storageClassNodeSummary, + report.VolumeSnapshot: volumeSnapshotNodeSummary, + report.VolumeSnapshotData: volumeSnapshotDataNodeSummary, } // For each report.Topology, map to a 'primary' API topology. This can then be used in a variety of places. @@ -104,6 +106,8 @@ var primaryAPITopology = map[string]string{ report.PersistentVolume: "pods", report.PersistentVolumeClaim: "pods", report.StorageClass: "pods", + report.VolumeSnapshot: "pods", + report.VolumeSnapshotData: "pods", } // MakeBasicNodeSummary returns a basic summary of a node, if @@ -394,6 +398,16 @@ func storageClassNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSumm return base } +func volumeSnapshotNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary { + base = addKubernetesLabelAndRank(base, n) + return base +} + +func volumeSnapshotDataNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary { + base = addKubernetesLabelAndRank(base, n) + return base +} + // groupNodeSummary renders the summary for a group node. n.Topology is // expected to be of the form: group:container:hostname func groupNodeSummary(base BasicNodeSummary, r report.Report, n report.Node) BasicNodeSummary { diff --git a/render/expected/expected.go b/render/expected/expected.go index f3171c725..adac162bb 100644 --- a/render/expected/expected.go +++ b/render/expected/expected.go @@ -44,6 +44,8 @@ var ( persistentVolume = node(report.PersistentVolume) persistentVolumeClaim = node(report.PersistentVolumeClaim) StorageClass = node(report.StorageClass) + volumeSnapshot = node(report.VolumeSnapshot) + volumeSnapshotData = node(report.VolumeSnapshotData) UnknownPseudoNode1ID = render.MakePseudoNodeID(fixture.UnknownClient1IP) UnknownPseudoNode2ID = render.MakePseudoNodeID(fixture.UnknownClient3IP) @@ -282,7 +284,7 @@ var ( kubernetes.StorageClassName: "standard", }).WithChild(report.MakeNode(fixture.PersistentVolumeNodeID).WithTopology(report.PersistentVolume)), - fixture.PersistentVolumeNodeID: persistentVolume(fixture.PersistentVolumeNodeID). + fixture.PersistentVolumeNodeID: persistentVolume(fixture.PersistentVolumeNodeID, fixture.VolumeSnapshotNodeID). WithLatests(map[string]string{ kubernetes.Name: "pongvolume", kubernetes.Namespace: "ping", @@ -291,7 +293,7 @@ var ( kubernetes.AccessModes: "ReadWriteOnce", kubernetes.StorageClassName: "standard", kubernetes.StorageDriver: "iSCSI", - }), + }).WithChild(report.MakeNode(fixture.VolumeSnapshotNodeID).WithTopology(report.VolumeSnapshot)), fixture.StorageClassNodeID: StorageClass(fixture.StorageClassNodeID, fixture.PersistentVolumeClaimNodeID). WithLatests(map[string]string{ @@ -299,6 +301,22 @@ var ( kubernetes.Provisioner: "pong", }).WithChild(report.MakeNode(fixture.PersistentVolumeClaimNodeID).WithTopology(report.PersistentVolumeClaim)), + fixture.VolumeSnapshotNodeID: volumeSnapshot(fixture.VolumeSnapshotNodeID, fixture.VolumeSnapshotDataNodeID). + WithLatests(map[string]string{ + kubernetes.Name: "vs-1234", + kubernetes.Namespace: "ping", + kubernetes.VolumeClaim: "pvc-6124", + kubernetes.SnapshotData: "vsd-1234", + kubernetes.VolumeName: "pongvolume", + }).WithChild(report.MakeNode(fixture.VolumeSnapshotDataNodeID).WithTopology(report.VolumeSnapshotData)), + + fixture.VolumeSnapshotDataNodeID: volumeSnapshotData(fixture.VolumeSnapshotDataNodeID). + WithLatests(map[string]string{ + kubernetes.Name: "vsd-1234", + kubernetes.VolumeName: "pongvolume", + kubernetes.VolumeSnapshotName: "vs-1234", + }), + UnmanagedServerID: unmanagedServerNode, render.IncomingInternetID: theIncomingInternetNode(fixture.ServerPodNodeID), render.OutgoingInternetID: theOutgoingInternetNode, diff --git a/render/filters.go b/render/filters.go index 208bbd64a..8d02a5918 100644 --- a/render/filters.go +++ b/render/filters.go @@ -138,6 +138,14 @@ func IsPodComponent(node report.Node) bool { return ok } +// IsNonSnapshotComponent checks whether given node is everything but Volume Snapshot, Volume Snapshot Data +func IsNonSnapshotComponent(node report.Node) bool { + if node.Topology == "volume_snapshot" || node.Topology == "volume_snapshot_data" { + return false + } + return true +} + // connected returns the node ids of nodes which have edges to/from // them, excluding edges to/from themselves. func connected(nodes report.Nodes) map[string]struct{} { diff --git a/render/persistentvolume.go b/render/persistentvolume.go index 98197473f..573c43c82 100644 --- a/render/persistentvolume.go +++ b/render/persistentvolume.go @@ -11,6 +11,8 @@ var KubernetesVolumesRenderer = MakeReduce( VolumesRenderer, PodToVolumeRenderer, PVCToStorageClassRenderer, + PVToSnapshotRenderer, + VolumeSnapshotRenderer, ) // VolumesRenderer is a Renderer which produces a renderable kubernetes PV & PVC @@ -25,13 +27,12 @@ func (v volumesRenderer) Render(rpt report.Report) Nodes { nodes := make(report.Nodes) for id, n := range rpt.PersistentVolumeClaim.Nodes { volume, _ := n.Latest.Lookup(kubernetes.VolumeName) - for pvNodeID, p := range rpt.PersistentVolume.Nodes { + for _, p := range rpt.PersistentVolume.Nodes { volumeName, _ := p.Latest.Lookup(kubernetes.Name) if volume == volumeName { n.Adjacency = n.Adjacency.Add(p.ID) n.Children = n.Children.Add(p) } - nodes[pvNodeID] = p } nodes[id] = n } @@ -86,3 +87,51 @@ func (v pvcToStorageClassRenderer) Render(rpt report.Report) Nodes { } return Nodes{Nodes: nodes} } + +//PVToSnapshotRenderer is a Renderer which produces a renderable kubernetes PV +var PVToSnapshotRenderer = pvToSnapshotRenderer{} + +//pvToSnapshotRenderer is a Renderer to render PV & Snapshot. +type pvToSnapshotRenderer struct{} + +//Render renders the PV & Snapshot nodes with adjacency. +func (v pvToSnapshotRenderer) Render(rpt report.Report) Nodes { + nodes := make(report.Nodes) + for pvNodeID, p := range rpt.PersistentVolume.Nodes { + volumeName, _ := p.Latest.Lookup(kubernetes.Name) + for _, volumeSnapshotNode := range rpt.VolumeSnapshot.Nodes { + snapshotPVName, _ := volumeSnapshotNode.Latest.Lookup(kubernetes.VolumeName) + if volumeName == snapshotPVName { + p.Adjacency = p.Adjacency.Add(volumeSnapshotNode.ID) + p.Children = p.Children.Add(volumeSnapshotNode) + } + } + nodes[pvNodeID] = p + } + return Nodes{Nodes: nodes} +} + +// VolumeSnapshotRenderer is a renderer which produces a renderable Kubernetes Volume Snapshot and Volume Snapshot Data +var VolumeSnapshotRenderer = volumeSnapshotRenderer{} + +// volumeSnapshotRenderer is a render to volume snapshot & volume snapshot data +type volumeSnapshotRenderer struct{} + +// Render renders the volumeSnapshots & volumeSnapshotData with adjacency +// It checks for the volumeSnapshotData name in volumeSnapshot, adjacency is created by matching the volumeSnapshotData name. +func (v volumeSnapshotRenderer) Render(rpt report.Report) Nodes { + nodes := make(report.Nodes) + for volumeSnapshotID, volumeSnapshotNode := range rpt.VolumeSnapshot.Nodes { + snapshotData, _ := volumeSnapshotNode.Latest.Lookup(kubernetes.SnapshotData) + for volumeSnapshotDataID, volumeSnapshotDataNode := range rpt.VolumeSnapshotData.Nodes { + snapshotDataName, _ := volumeSnapshotDataNode.Latest.Lookup(kubernetes.Name) + if snapshotDataName == snapshotData { + volumeSnapshotNode.Adjacency = volumeSnapshotNode.Adjacency.Add(volumeSnapshotDataNode.ID) + volumeSnapshotNode.Children = volumeSnapshotNode.Children.Add(volumeSnapshotDataNode) + } + nodes[volumeSnapshotDataID] = volumeSnapshotDataNode + } + nodes[volumeSnapshotID] = volumeSnapshotNode + } + return Nodes{Nodes: nodes} +} diff --git a/render/selectors.go b/render/selectors.go index 036f10720..0b995c38d 100644 --- a/render/selectors.go +++ b/render/selectors.go @@ -35,4 +35,6 @@ var ( SelectPersistentVolume = TopologySelector(report.PersistentVolume) SelectPersistentVolumeClaim = TopologySelector(report.PersistentVolumeClaim) SelectStorageClass = TopologySelector(report.StorageClass) + SelectVolumeSnapshot = TopologySelector(report.VolumeSnapshot) + SelectVolumeSnapshotData = TopologySelector(report.VolumeSnapshotData) ) diff --git a/report/id.go b/report/id.go index d6731f35b..be893a214 100644 --- a/report/id.go +++ b/report/id.go @@ -175,6 +175,18 @@ var ( // ParseStorageClassNodeID parses a storage class node ID ParseStorageClassNodeID = parseSingleComponentID("storage_class") + + // MakeVolumeSnapshotNodeID produces a volume snapshot node ID from its composite parts. + MakeVolumeSnapshotNodeID = makeSingleComponentID("volume_snapshot") + + // ParseVolumeSnapshotNodeID parses a volume snapshot node ID + ParseVolumeSnapshotNodeID = parseSingleComponentID("volume_snapshot") + + // MakeVolumeSnapshotDataNodeID produces a volume snapshot data node ID from its composite parts. + MakeVolumeSnapshotDataNodeID = makeSingleComponentID("volume_snapshot_data") + + // ParseVolumeSnapshotDataNodeID parses a volume snapshot data node ID + ParseVolumeSnapshotDataNodeID = parseSingleComponentID("volume_snapshot_data") ) // makeSingleComponentID makes a single-component node id encoder diff --git a/report/map_keys.go b/report/map_keys.go index fa5bea503..e6d39c810 100644 --- a/report/map_keys.go +++ b/report/map_keys.go @@ -81,6 +81,8 @@ const ( KubernetesVolumeName = "kubernetes_volume_name" KubernetesProvisioner = "kubernetes_provisioner" KubernetesStorageDriver = "kubernetes_storage_driver" + KubernetesVolumeSnapshotName = "kubernetes_volume_snapshot_name" + KubernetesSnapshotData = "kuberneets_snapshot_data" // probe/awsecs ECSCluster = "ecs_cluster" ECSCreatedAt = "ecs_created_at" @@ -116,6 +118,8 @@ var commonKeys = map[string]string{ PersistentVolume: PersistentVolume, PersistentVolumeClaim: PersistentVolumeClaim, StorageClass: StorageClass, + VolumeSnapshot: VolumeSnapshot, + VolumeSnapshotData: VolumeSnapshotData, HostNodeID: HostNodeID, ControlProbeID: ControlProbeID, diff --git a/report/report.go b/report/report.go index 0ae6703d3..32a28f4b9 100644 --- a/report/report.go +++ b/report/report.go @@ -31,6 +31,8 @@ const ( PersistentVolume = "persistent_volume" PersistentVolumeClaim = "persistent_volume_claim" StorageClass = "storage_class" + VolumeSnapshot = "volume_snapshot" + VolumeSnapshotData = "volume_snapshot_data" // Shapes used for different nodes Circle = "circle" @@ -72,6 +74,8 @@ var topologyNames = []string{ PersistentVolume, PersistentVolumeClaim, StorageClass, + VolumeSnapshot, + VolumeSnapshotData, } // Report is the core data type. It's produced by probes, and consumed and @@ -172,6 +176,12 @@ type Report struct { // Metadata is limited for now, more to come later. StorageClass Topology + // VolumeSnapshot represent all Kubernetes Volume Snapshots on hosts running probes. + VolumeSnapshot Topology + + // VolumeSnapshotData represent all Kubernetes Volume Snapshot Data on hosts running probes. + VolumeSnapshotData Topology + DNS DNSRecords // Sampling data for this report. @@ -277,6 +287,16 @@ func MakeReport() Report { WithShape(StorageSheet). WithLabel("storage class", "storage classes"), + VolumeSnapshot: MakeTopology(). + WithShape(DottedCylinder). + WithTag(Camera). + WithLabel("volume snapshot", "volume snapshots"), + + VolumeSnapshotData: MakeTopology(). + WithShape(Cylinder). + WithTag(Camera). + WithLabel("volume snapshot data", "volume snapshot data"), + DNS: DNSRecords{}, Sampling: Sampling{}, @@ -389,6 +409,10 @@ func (r *Report) topology(name string) *Topology { return &r.PersistentVolumeClaim case StorageClass: return &r.StorageClass + case VolumeSnapshot: + return &r.VolumeSnapshot + case VolumeSnapshotData: + return &r.VolumeSnapshotData } return nil } diff --git a/test/fixture/report_fixture.go b/test/fixture/report_fixture.go index f914077f2..2ea0608bb 100644 --- a/test/fixture/report_fixture.go +++ b/test/fixture/report_fixture.go @@ -107,6 +107,10 @@ var ( PersistentVolumeClaimNodeID = report.MakePersistentVolumeClaimNodeID(PersistentVolumeClaimUID) StorageClassUID = "sc1234" StorageClassNodeID = report.MakeStorageClassNodeID(StorageClassUID) + VolumeSnapshotUID = "vs1234" + VolumeSnapshotNodeID = report.MakeVolumeSnapshotNodeID(VolumeSnapshotUID) + VolumeSnapshotDataUID = "vsd1234" + VolumeSnapshotDataNodeID = report.MakeVolumeSnapshotDataNodeID(VolumeSnapshotDataUID) ClientProcess1CPUMetric = report.MakeSingletonMetric(Now.Add(-1*time.Second), 0.01) ClientProcess1MemoryMetric = report.MakeSingletonMetric(Now.Add(-2*time.Second), 0.02) @@ -396,6 +400,34 @@ var ( WithTopology(report.StorageClass), }, }.WithShape(report.StorageSheet).WithLabel("storage class", "storage classes"), + VolumeSnapshot: report.Topology{ + Nodes: report.Nodes{ + VolumeSnapshotNodeID: report.MakeNodeWith( + + VolumeSnapshotNodeID, map[string]string{ + kubernetes.Name: "vs-1234", + kubernetes.Namespace: "ping", + kubernetes.VolumeClaim: "pvc-6124", + kubernetes.SnapshotData: "vsd-1234", + kubernetes.VolumeName: "pongvolume", + }). + WithTopology(report.VolumeSnapshot), + }, + }.WithShape(report.DottedCylinder).WithLabel("volume snapshot", "volume snapshots"). + WithTag(report.Camera), + VolumeSnapshotData: report.Topology{ + Nodes: report.Nodes{ + VolumeSnapshotDataNodeID: report.MakeNodeWith( + + VolumeSnapshotDataNodeID, map[string]string{ + kubernetes.Name: "vsd-1234", + kubernetes.VolumeName: "pongvolume", + kubernetes.VolumeSnapshotName: "vs-1234", + }). + WithTopology(report.VolumeSnapshotData), + }, + }.WithShape(report.Cylinder).WithLabel("volume snapshot data", "volume snapshot data"). + WithTag(report.Camera), Sampling: report.Sampling{ Count: 1024, Total: 4096,