mirror of
https://github.com/weaveworks/scope.git
synced 2026-07-28 17:51:21 +00:00
Add Kubernetes storage class resource to weave scope
This will: - Add StorageClass resource. Storage classes are mentioned in the PVC spec. We're using storage class name from PVC spec to add adjacency to the PVC node. - Add square sheet shape for StorageClass. - Add storage filter in the PODS topology. Storage Filter will allow user to see distinct view of stateful applications. - Add visually distinct edge to show storage adjacency. Signed-off-by: Satyam Zode <satyam.zode@openebs.io>
This commit is contained in:
@@ -48,6 +48,14 @@ var (
|
||||
{Value: "hide", Label: "Hide Unmanaged", filter: render.IsNotPseudo, filterPseudo: true},
|
||||
},
|
||||
}
|
||||
storageFilter = APITopologyOptionGroup{
|
||||
ID: "storage",
|
||||
Default: "hide",
|
||||
Options: []APITopologyOption{
|
||||
{Value: "show", Label: "Show Storage", filter: render.IsStorageComponent, filterPseudo: false},
|
||||
{Value: "hide", Label: "Hide Storage", filter: render.IsNotStorageComponent, filterPseudo: false},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// namespaceFilters generates a namespace selector option group based on the given namespaces
|
||||
@@ -230,7 +238,7 @@ func MakeRegistry() *Registry {
|
||||
renderer: render.PodRenderer,
|
||||
Name: "Pods",
|
||||
Rank: 3,
|
||||
Options: []APITopologyOptionGroup{unmanagedFilter},
|
||||
Options: []APITopologyOptionGroup{storageFilter, unmanagedFilter},
|
||||
HideIfEmpty: true,
|
||||
},
|
||||
APITopologyDesc{
|
||||
@@ -249,14 +257,6 @@ func MakeRegistry() *Registry {
|
||||
Options: []APITopologyOptionGroup{unmanagedFilter},
|
||||
HideIfEmpty: true,
|
||||
},
|
||||
APITopologyDesc{
|
||||
id: persistentVolumesID,
|
||||
parent: podsID,
|
||||
renderer: render.PersistentVolumeRenderer,
|
||||
Name: "persistent volumes",
|
||||
Options: []APITopologyOptionGroup{},
|
||||
HideIfEmpty: true,
|
||||
},
|
||||
APITopologyDesc{
|
||||
id: ecsTasksID,
|
||||
renderer: render.ECSTaskRenderer,
|
||||
|
||||
@@ -5,6 +5,20 @@ import classNames from 'classnames';
|
||||
import { enterEdge, leaveEdge } from '../actions/app-actions';
|
||||
import { encodeIdAttribute, decodeIdAttribute } from '../utils/dom-utils';
|
||||
|
||||
|
||||
function getAdjacencyClass(id) {
|
||||
const topologyId = id.split('---');
|
||||
const from = topologyId[0].split(';');
|
||||
const to = topologyId[1].split(';');
|
||||
if (from[1] !== undefined && to[1] !== undefined) {
|
||||
from[1] = from[1].slice(1, -1);
|
||||
to[1] = to[1].slice(1, -1);
|
||||
if ((from[1] === 'persistent_volume' || from[1] === 'storage_class' || from[1] === 'persistent_volume_claim') || (to[1] === 'persistent_volume' || to[1] === 'storage_class' || to[1] === 'persistent_volume_claim')) {
|
||||
return 'link-storage';
|
||||
}
|
||||
}
|
||||
return 'link-none';
|
||||
}
|
||||
class Edge extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
@@ -18,7 +32,6 @@ class Edge extends React.Component {
|
||||
} = this.props;
|
||||
const shouldRenderMarker = (focused || highlighted) && (source !== target);
|
||||
const className = classNames('edge', { highlighted });
|
||||
|
||||
return (
|
||||
<g
|
||||
id={encodeIdAttribute(id)}
|
||||
@@ -27,6 +40,11 @@ class Edge extends React.Component {
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
>
|
||||
<path className="shadow" d={path} style={{ strokeWidth: 10 * thickness }} />
|
||||
<path
|
||||
className={getAdjacencyClass(id)}
|
||||
d={path}
|
||||
style={{ strokeWidth: 5 }}
|
||||
/>
|
||||
<path
|
||||
className="link"
|
||||
d={path}
|
||||
|
||||
@@ -20,7 +20,8 @@ import {
|
||||
octagonShapeProps,
|
||||
cloudShapeProps,
|
||||
cylinderShapeProps,
|
||||
dottedCylinderShapeProps
|
||||
dottedCylinderShapeProps,
|
||||
sheetShapeProps
|
||||
} from '../utils/node-shape-utils';
|
||||
import { encodeIdAttribute } from '../utils/dom-utils';
|
||||
|
||||
@@ -87,3 +88,4 @@ export const NodeShapeOctagon = props => NodeShape('octagon', pathElement, octag
|
||||
export const NodeShapeCloud = props => NodeShape('cloud', pathElement, cloudShapeProps, props);
|
||||
export const NodeShapeCylinder = props => NodeShape('cylinder', pathElement, cylinderShapeProps, props);
|
||||
export const NodeShapeDottedCylinder = props => NodeShape('dottedcylinder', pathElement, dottedCylinderShapeProps, props);
|
||||
export const NodeShapeSheet = props => NodeShape('sheet', pathElement, sheetShapeProps, props);
|
||||
|
||||
@@ -23,7 +23,8 @@ import {
|
||||
NodeShapeOctagon,
|
||||
NodeShapeCloud,
|
||||
NodeShapeCylinder,
|
||||
NodeShapeDottedCylinder
|
||||
NodeShapeDottedCylinder,
|
||||
NodeShapeSheet
|
||||
} from './node-shapes';
|
||||
|
||||
|
||||
@@ -38,7 +39,8 @@ const nodeShapes = {
|
||||
octagon: NodeShapeOctagon,
|
||||
cloud: NodeShapeCloud,
|
||||
cylinder: NodeShapeCylinder,
|
||||
dottedcylinder: NodeShapeDottedCylinder
|
||||
dottedcylinder: NodeShapeDottedCylinder,
|
||||
storagesheet: NodeShapeSheet
|
||||
};
|
||||
|
||||
function stackedShape(Shape) {
|
||||
|
||||
@@ -32,6 +32,9 @@ export const UNIT_CYLINDER_PATH = 'm -1 -1.25' // this line is responsible for a
|
||||
+ 'v -1.8'
|
||||
+ 'a 1 0.4 0 0 0 -2 0';
|
||||
|
||||
// Node Storage Sheet Shape
|
||||
export const SHEET = 'm -1.2 -1.6 m 0.4 0 v 2.4 m -0.4 -2.4 v 2.4 h 2 v -2.4 z m 0 0.4 h 2';
|
||||
|
||||
// NOTE: This value represents the node unit radius (in pixels). Since zooming is
|
||||
// controlled at the top level now, this renormalization would be obsolete (i.e.
|
||||
// value 1 could be used instead), if it wasn't for the following factors:
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import range from 'lodash/range';
|
||||
import { line, curveCardinalClosed } from 'd3-shape';
|
||||
|
||||
import { UNIT_CLOUD_PATH, UNIT_CYLINDER_PATH } from '../constants/styles';
|
||||
import { UNIT_CLOUD_PATH, UNIT_CYLINDER_PATH, SHEET } from '../constants/styles';
|
||||
|
||||
|
||||
export const pathElement = React.createFactory('path');
|
||||
@@ -31,3 +31,4 @@ export const octagonShapeProps = { d: curvedUnitPolygonPath(8) };
|
||||
export const cloudShapeProps = { d: UNIT_CLOUD_PATH };
|
||||
export const cylinderShapeProps = { d: UNIT_CYLINDER_PATH };
|
||||
export const dottedCylinderShapeProps = { d: UNIT_CYLINDER_PATH };
|
||||
export const sheetShapeProps = { d: SHEET };
|
||||
|
||||
@@ -533,9 +533,19 @@ a {
|
||||
}
|
||||
|
||||
.edge {
|
||||
.link {
|
||||
.link-none {
|
||||
fill: none;
|
||||
display: none;
|
||||
}
|
||||
.link-storage {
|
||||
fill: none;
|
||||
stroke: $edge-color;
|
||||
stroke-dasharray: 1, 30;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
.link {
|
||||
fill: none;
|
||||
stroke: $edge-color
|
||||
}
|
||||
.shadow {
|
||||
fill: none;
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
apibatchv2alpha1 "k8s.io/api/batch/v2alpha1"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
apiextensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||
storagev1 "k8s.io/api/storage/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
@@ -39,6 +40,7 @@ type Client interface {
|
||||
WalkNamespaces(f func(NamespaceResource) error) error
|
||||
WalkPersistentVolumes(f func(PersistentVolume) error) error
|
||||
WalkPersistentVolumeClaims(f func(PersistentVolumeClaim) error) error
|
||||
WalkStorageClasses(f func(StorageClass) error) error
|
||||
|
||||
WatchPods(f func(Event, Pod))
|
||||
|
||||
@@ -62,6 +64,7 @@ type client struct {
|
||||
namespaceStore cache.Store
|
||||
persistentVolumeStore cache.Store
|
||||
persistentVolumeClaimStore cache.Store
|
||||
storageClassStore cache.Store
|
||||
|
||||
podWatchesMutex sync.Mutex
|
||||
podWatches []func(Event, Pod)
|
||||
@@ -148,6 +151,7 @@ func NewClient(config ClientConfig) (Client, error) {
|
||||
result.cronJobStore = result.setupStore("cronjobs")
|
||||
result.persistentVolumeStore = result.setupStore("persistentvolumes")
|
||||
result.persistentVolumeClaimStore = result.setupStore("persistentvolumeclaims")
|
||||
result.storageClassStore = result.setupStore("storageclasses")
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -190,6 +194,8 @@ func (c *client) clientAndType(resource string) (rest.Interface, interface{}, er
|
||||
return c.client.CoreV1().RESTClient(), &apiv1.PersistentVolume{}, nil
|
||||
case "persistentvolumeclaims":
|
||||
return c.client.CoreV1().RESTClient(), &apiv1.PersistentVolumeClaim{}, nil
|
||||
case "storageclasses":
|
||||
return c.client.StorageV1().RESTClient(), &storagev1.StorageClass{}, nil
|
||||
case "deployments":
|
||||
return c.client.ExtensionsV1beta1().RESTClient(), &apiextensionsv1beta1.Deployment{}, nil
|
||||
case "daemonsets":
|
||||
@@ -292,6 +298,16 @@ func (c *client) WalkPersistentVolumeClaims(f func(PersistentVolumeClaim) error)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) WalkStorageClasses(f func(StorageClass) error) error {
|
||||
for _, m := range c.storageClassStore.List() {
|
||||
sc := m.(*storagev1.StorageClass)
|
||||
if err := f(NewStorageClass(sc)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) WalkServices(f func(Service) error) error {
|
||||
for _, m := range c.serviceStore.List() {
|
||||
s := m.(*apiv1.Service)
|
||||
|
||||
@@ -32,6 +32,7 @@ const (
|
||||
Status = report.KubernetesStatus
|
||||
Message = report.KubernetesMessage
|
||||
VolumeName = report.KubernetesVolumeName
|
||||
Provisioner = report.KubernetesProvisioner
|
||||
)
|
||||
|
||||
// Exposed for testing
|
||||
@@ -123,6 +124,12 @@ var (
|
||||
AccessModes: {ID: AccessModes, Label: "Access Modes", From: report.FromLatest, Priority: 5},
|
||||
}
|
||||
|
||||
StorageClassMetadataTemplates = report.MetadataTemplates{
|
||||
NodeType: {ID: NodeType, Label: "Type", From: report.FromLatest, Priority: 1},
|
||||
Name: {ID: Name, Label: "Name", From: report.FromLatest, Priority: 2},
|
||||
Provisioner: {ID: Provisioner, Label: "Provisioner", From: report.FromLatest, Priority: 3},
|
||||
}
|
||||
|
||||
TableTemplates = report.TableTemplates{
|
||||
LabelPrefix: {
|
||||
ID: LabelPrefix,
|
||||
@@ -292,6 +299,10 @@ func (r *Reporter) Report() (report.Report, error) {
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
storageClassTopology, _, err := r.storageClassTopology()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
result.Pod = result.Pod.Merge(podTopology)
|
||||
result.Service = result.Service.Merge(serviceTopology)
|
||||
result.Host = result.Host.Merge(hostTopology)
|
||||
@@ -302,6 +313,7 @@ func (r *Reporter) Report() (report.Report, error) {
|
||||
result.Namespace = result.Namespace.Merge(namespaceTopology)
|
||||
result.PersistentVolume = result.PersistentVolume.Merge(persistentVolumeTopology)
|
||||
result.PersistentVolumeClaim = result.PersistentVolumeClaim.Merge(persistentVolumeClaimTopology)
|
||||
result.StorageClass = result.StorageClass.Merge(storageClassTopology)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -437,6 +449,19 @@ func (r *Reporter) persistentVolumeClaimTopology() (report.Topology, []Persisten
|
||||
return result, persistentVolumeClaims, err
|
||||
}
|
||||
|
||||
func (r *Reporter) storageClassTopology() (report.Topology, []StorageClass, error) {
|
||||
storageClasses := []StorageClass{}
|
||||
result := report.MakeTopology().
|
||||
WithMetadataTemplates(StorageClassMetadataTemplates).
|
||||
WithTableTemplates(TableTemplates)
|
||||
err := r.client.WalkStorageClasses(func(p StorageClass) error {
|
||||
result.AddNode(p.GetNode(r.probeID))
|
||||
storageClasses = append(storageClasses, p)
|
||||
return nil
|
||||
})
|
||||
return result, storageClasses, err
|
||||
}
|
||||
|
||||
type labelledChild interface {
|
||||
Labels() map[string]string
|
||||
AddParent(string, string)
|
||||
|
||||
@@ -154,6 +154,9 @@ func (c *mockClient) WalkPersistentVolumes(f func(kubernetes.PersistentVolume) e
|
||||
func (c *mockClient) WalkPersistentVolumeClaims(f func(kubernetes.PersistentVolumeClaim) error) error {
|
||||
return nil
|
||||
}
|
||||
func (c *mockClient) WalkStorageClasses(f func(kubernetes.StorageClass) 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]
|
||||
|
||||
33
probe/kubernetes/storageclass.go
Normal file
33
probe/kubernetes/storageclass.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"github.com/weaveworks/scope/report"
|
||||
storagev1 "k8s.io/api/storage/v1"
|
||||
)
|
||||
|
||||
// StorageClass represent kubernetes StorageClass interface
|
||||
type StorageClass interface {
|
||||
Meta
|
||||
GetNode(probeID string) report.Node
|
||||
}
|
||||
|
||||
// storageClass represents kubernetes storage classes
|
||||
type storageClass struct {
|
||||
*storagev1.StorageClass
|
||||
Meta
|
||||
}
|
||||
|
||||
// NewStorageClass returns new Storage Class type
|
||||
func NewStorageClass(p *storagev1.StorageClass) StorageClass {
|
||||
return &storageClass{StorageClass: p, Meta: meta{p.ObjectMeta}}
|
||||
}
|
||||
|
||||
// GetNode returns StorageClass as Node
|
||||
func (p *storageClass) GetNode(probeID string) report.Node {
|
||||
return p.MetaNode(report.MakeStorageClassNodeID(p.UID())).WithLatests(map[string]string{
|
||||
report.ControlProbeID: probeID,
|
||||
NodeType: "Storage Class",
|
||||
Name: p.GetName(),
|
||||
Provisioner: p.Provisioner,
|
||||
})
|
||||
}
|
||||
@@ -199,6 +199,13 @@ var nodeSummaryGroupSpecs = []struct {
|
||||
Columns: []Column{},
|
||||
},
|
||||
},
|
||||
{
|
||||
topologyID: report.StorageClass,
|
||||
NodeSummaryGroup: NodeSummaryGroup{
|
||||
Label: "Storage Classes",
|
||||
Columns: []Column{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func children(rc RenderContext, n report.Node) []NodeSummaryGroup {
|
||||
|
||||
@@ -82,6 +82,7 @@ var renderers = map[string]func(BasicNodeSummary, report.Node) BasicNodeSummary{
|
||||
report.Endpoint: nil, // Do not render
|
||||
report.PersistentVolume: persistentVolumeNodeSummary,
|
||||
report.PersistentVolumeClaim: persistentVolumeClaimNodeSummary,
|
||||
report.StorageClass: storageClassNodeSummary,
|
||||
}
|
||||
|
||||
// For each report.Topology, map to a 'primary' API topology. This can then be used in a variety of places.
|
||||
@@ -99,8 +100,9 @@ var primaryAPITopology = map[string]string{
|
||||
report.ECSService: "ecs-services",
|
||||
report.SwarmService: "swarm-services",
|
||||
report.Host: "hosts",
|
||||
report.PersistentVolume: "persistent-volumes",
|
||||
report.PersistentVolumeClaim: "persistent-volumes",
|
||||
report.PersistentVolume: "pods",
|
||||
report.PersistentVolumeClaim: "pods",
|
||||
report.StorageClass: "pods",
|
||||
}
|
||||
|
||||
// MakeBasicNodeSummary returns a basic summary of a node, if
|
||||
@@ -385,6 +387,11 @@ func persistentVolumeClaimNodeSummary(base BasicNodeSummary, n report.Node) Basi
|
||||
return base
|
||||
}
|
||||
|
||||
func storageClassNodeSummary(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 {
|
||||
|
||||
@@ -329,6 +329,14 @@ var (
|
||||
}
|
||||
|
||||
RenderedPersistentVolume = report.Nodes{
|
||||
fixture.ClientPodNodeID: pod(fixture.ClientPodNodeID, fixture.PersistentVolumeClaimNodeID, fixture.ServerPodNodeID).
|
||||
WithLatests(map[string]string{
|
||||
kubernetes.Name: "pong-a",
|
||||
kubernetes.Namespace: "ping",
|
||||
kubernetes.State: "running",
|
||||
kubernetes.VolumeClaim: "pvc-6124",
|
||||
}).WithChild(report.MakeNode(fixture.PersistentVolumeClaimNodeID).WithTopology(report.Pod)),
|
||||
|
||||
fixture.PersistentVolumeClaimNodeID: persistentVolumeClaim(fixture.PersistentVolumeClaimNodeID, fixture.PersistentVolumeNodeID).
|
||||
WithLatests(map[string]string{
|
||||
kubernetes.Name: "pvc-6124",
|
||||
|
||||
@@ -131,6 +131,35 @@ func IsConnected(node report.Node) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsStorageComponent check whether given node is PV, PVC, SC or not
|
||||
func IsStorageComponent(node report.Node) bool {
|
||||
var storageComponent bool
|
||||
if node.Topology == "persistent_volume" || node.Topology == "persistent_volume_claim" || node.Topology == "storage_class" {
|
||||
storageComponent = true
|
||||
}
|
||||
if node.Topology == "pod" {
|
||||
volumeClaim, ok := node.Latest.Lookup(kubernetes.VolumeClaim)
|
||||
if !ok {
|
||||
storageComponent = false
|
||||
} else if volumeClaim == "" {
|
||||
storageComponent = false
|
||||
} else {
|
||||
storageComponent = true
|
||||
}
|
||||
}
|
||||
return storageComponent
|
||||
}
|
||||
|
||||
// IsNotStorageComponent check whether given node is PV, PVC, SC or not
|
||||
func IsNotStorageComponent(node report.Node) bool {
|
||||
var ok bool
|
||||
ok = true
|
||||
if node.Topology == "persistent_volume" || node.Topology == "persistent_volume_claim" || node.Topology == "storage_class" {
|
||||
ok = false
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// 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{} {
|
||||
|
||||
@@ -5,93 +5,34 @@ import (
|
||||
"github.com/weaveworks/scope/report"
|
||||
)
|
||||
|
||||
// PersistentVolumeRenderer is the common renderer for all the storage components.
|
||||
var PersistentVolumeRenderer = Memoise(
|
||||
MakeReduce(
|
||||
ConnectionStorageJoin(
|
||||
Map2PVName,
|
||||
report.PersistentVolumeClaim,
|
||||
),
|
||||
MakeFilter(
|
||||
func(n report.Node) bool {
|
||||
claimName, ok := n.Latest.Lookup(kubernetes.VolumeClaim)
|
||||
if claimName == "" {
|
||||
return !ok
|
||||
}
|
||||
return ok
|
||||
},
|
||||
MakeReduce(
|
||||
PropagateSingleMetrics(report.Container,
|
||||
MakeMap(
|
||||
Map3Parent([]string{report.Pod}),
|
||||
MakeFilter(
|
||||
ComposeFilterFuncs(
|
||||
IsRunning,
|
||||
Complement(isPauseContainer),
|
||||
),
|
||||
ContainerWithImageNameRenderer,
|
||||
),
|
||||
),
|
||||
),
|
||||
ConnectionStorageJoin(
|
||||
Map2PVCName,
|
||||
report.Pod,
|
||||
),
|
||||
),
|
||||
),
|
||||
MapStorageEndpoints(
|
||||
Map2PVNode,
|
||||
report.PersistentVolume,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
// Map3Parent returns a MapFunc which maps Nodes to some parent grouping.
|
||||
func Map3Parent(
|
||||
// The topology IDs to look for parents in
|
||||
topologies []string,
|
||||
) MapFunc {
|
||||
return func(n report.Node) report.Nodes {
|
||||
result := report.Nodes{}
|
||||
for _, topology := range topologies {
|
||||
if groupIDs, ok := n.Parents.Lookup(topology); ok {
|
||||
for _, id := range groupIDs {
|
||||
node := NewDerivedNode(id, n).WithTopology(topology)
|
||||
node.Counters = node.Counters.Add(n.Topology, 1)
|
||||
result[id] = node
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// ConnectionStorageJoin returns connectionStorageJoin object
|
||||
func ConnectionStorageJoin(toPV func(report.Node) string, topology string) Renderer {
|
||||
func ConnectionStorageJoin(toPV func(report.Node) []string, topology string) Renderer {
|
||||
return connectionStorageJoin{toPV: toPV, topology: topology}
|
||||
}
|
||||
|
||||
// connectionStorageJoin holds the information about mapping of storage components
|
||||
// along with TopologySelector
|
||||
type connectionStorageJoin struct {
|
||||
toPV func(report.Node) string
|
||||
toPV func(report.Node) []string
|
||||
topology string
|
||||
}
|
||||
|
||||
func (c connectionStorageJoin) Render(rpt report.Report) Nodes {
|
||||
inputNodes := TopologySelector(c.topology).Render(rpt).Nodes
|
||||
|
||||
var pvcNodes = map[string]string{}
|
||||
var pvcNodes = map[string][]string{}
|
||||
for _, n := range inputNodes {
|
||||
pvName := c.toPV(n)
|
||||
pvcNodes[pvName] = n.ID
|
||||
for _, name := range pvName {
|
||||
pvcNodes[name] = append(pvcNodes[name], n.ID)
|
||||
}
|
||||
}
|
||||
|
||||
return MapStorageEndpoints(
|
||||
func(m report.Node) string {
|
||||
func(m report.Node) []string {
|
||||
pvName, ok := m.Latest.Lookup(kubernetes.Name)
|
||||
if !ok {
|
||||
return ""
|
||||
return []string{""}
|
||||
}
|
||||
id := pvcNodes[pvName]
|
||||
return id
|
||||
@@ -99,57 +40,81 @@ func (c connectionStorageJoin) Render(rpt report.Report) Nodes {
|
||||
}
|
||||
|
||||
// Map2PVName accepts PV Node and returns Volume name associated with PV Node.
|
||||
func Map2PVName(m report.Node) string {
|
||||
func Map2PVName(m report.Node) []string {
|
||||
pvName, ok := m.Latest.Lookup(kubernetes.VolumeName)
|
||||
scName, ok1 := m.Latest.Lookup(kubernetes.StorageClassName)
|
||||
if !ok {
|
||||
pvName = ""
|
||||
}
|
||||
return pvName
|
||||
if !ok1 {
|
||||
scName = ""
|
||||
}
|
||||
return []string{pvName, scName}
|
||||
}
|
||||
|
||||
// Map2PVCName returns pvc name
|
||||
func Map2PVCName(m report.Node) string {
|
||||
func Map2PVCName(m report.Node) []string {
|
||||
pvcName, ok := m.Latest.Lookup(kubernetes.VolumeClaim)
|
||||
if !ok {
|
||||
pvcName = ""
|
||||
}
|
||||
return pvcName
|
||||
return []string{pvcName}
|
||||
}
|
||||
|
||||
// Map2PVNode returns pv node ID
|
||||
func Map2PVNode(n report.Node) string {
|
||||
func Map2PVNode(n report.Node) []string {
|
||||
if pvNodeID, ok := n.Latest.Lookup(report.MakePersistentVolumeNodeID(n.ID)); ok {
|
||||
return pvNodeID
|
||||
return []string{pvNodeID}
|
||||
}
|
||||
return ""
|
||||
return []string{""}
|
||||
}
|
||||
|
||||
type storageEndpointMapFunc func(report.Node) []string
|
||||
|
||||
// mapStorageEndpoints is the Renderer for rendering storage components together.
|
||||
type mapStorageEndpoints struct {
|
||||
f endpointMapFunc
|
||||
f storageEndpointMapFunc
|
||||
topology string
|
||||
}
|
||||
|
||||
// MapStorageEndpoints instantiates mapStorageEndpoints and returns same
|
||||
func MapStorageEndpoints(f endpointMapFunc, topology string) Renderer {
|
||||
func MapStorageEndpoints(f storageEndpointMapFunc, topology string) Renderer {
|
||||
return mapStorageEndpoints{f: f, topology: topology}
|
||||
}
|
||||
|
||||
func (e mapStorageEndpoints) Render(rpt report.Report) Nodes {
|
||||
|
||||
var endpoints Nodes
|
||||
if e.topology == "persistent_volume_claim" {
|
||||
if e.topology == report.PersistentVolumeClaim {
|
||||
endpoints = SelectPersistentVolume.Render(rpt)
|
||||
endpoints.Merge(SelectStorageClass.Render(rpt))
|
||||
}
|
||||
if e.topology == "pod" {
|
||||
if e.topology == report.Pod {
|
||||
endpoints = SelectPersistentVolumeClaim.Render(rpt)
|
||||
}
|
||||
ret := newJoinResults(TopologySelector(e.topology).Render(rpt).Nodes)
|
||||
|
||||
for _, n := range endpoints.Nodes {
|
||||
if id := e.f(n); id != "" {
|
||||
ret.addChild(n, id, e.topology)
|
||||
if id := e.f(n); len(id) > 0 {
|
||||
for _, nodeID := range id {
|
||||
if nodeID != "" {
|
||||
ret.addChild(n, nodeID, e.topology)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if e.topology == report.PersistentVolumeClaim {
|
||||
ret.storageResult(endpoints)
|
||||
endpoints = SelectStorageClass.Render(rpt)
|
||||
for _, n := range endpoints.Nodes {
|
||||
if id := e.f(n); len(id) > 0 {
|
||||
for _, nodeID := range id {
|
||||
if nodeID != "" {
|
||||
ret.addChild(n, nodeID, e.topology)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret.storageResult(endpoints)
|
||||
}
|
||||
return ret.storageResult(endpoints)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ func renderKubernetesTopologies(rpt report.Report) bool {
|
||||
&rpt.DaemonSet,
|
||||
&rpt.StatefulSet,
|
||||
&rpt.CronJob,
|
||||
&rpt.PersistentVolume,
|
||||
&rpt.PersistentVolumeClaim,
|
||||
&rpt.StorageClass,
|
||||
}
|
||||
for _, t := range topologies {
|
||||
if len(t.Nodes) > 0 {
|
||||
@@ -62,6 +65,22 @@ var PodRenderer = Memoise(ConditionalRenderer(renderKubernetesTopologies,
|
||||
),
|
||||
),
|
||||
ConnectionJoin(MapPod2IP, report.Pod),
|
||||
ConnectionStorageJoin(
|
||||
Map2PVName,
|
||||
report.PersistentVolumeClaim,
|
||||
),
|
||||
ConnectionStorageJoin(
|
||||
Map2PVCName,
|
||||
report.Pod,
|
||||
),
|
||||
MapStorageEndpoints(
|
||||
Map2PVNode,
|
||||
report.PersistentVolume,
|
||||
),
|
||||
MapStorageEndpoints(
|
||||
Map2PVNode,
|
||||
report.StorageClass,
|
||||
),
|
||||
),
|
||||
),
|
||||
))
|
||||
|
||||
@@ -267,6 +267,9 @@ func (ret *joinResults) storageResult(input Nodes) Nodes {
|
||||
}
|
||||
// Since PV and PVC will have only single adjacency
|
||||
ret.storageAdjacency(outID, n.ID)
|
||||
for _, outID := range ret.multi[n.ID] {
|
||||
ret.storageAdjacency(outID, n.ID)
|
||||
}
|
||||
}
|
||||
return Nodes{Nodes: ret.nodes}
|
||||
}
|
||||
|
||||
@@ -34,4 +34,5 @@ var (
|
||||
SelectOverlay = TopologySelector(report.Overlay)
|
||||
SelectPersistentVolume = TopologySelector(report.PersistentVolume)
|
||||
SelectPersistentVolumeClaim = TopologySelector(report.PersistentVolumeClaim)
|
||||
SelectStorageClass = TopologySelector(report.StorageClass)
|
||||
)
|
||||
|
||||
@@ -169,6 +169,12 @@ var (
|
||||
|
||||
// ParsePersistentVolumeClaimNodeID parses a Persistent Volume Claim node ID
|
||||
ParsePersistentVolumeClaimNodeID = parseSingleComponentID("persistent_volume_claim")
|
||||
|
||||
// MakeStorageClassNodeID produces a storage class node ID from its composite parts.
|
||||
MakeStorageClassNodeID = makeSingleComponentID("storage_class")
|
||||
|
||||
// ParseStorageClassNodeID parses a storage class node ID
|
||||
ParseStorageClassNodeID = parseSingleComponentID("storage_class")
|
||||
)
|
||||
|
||||
// makeSingleComponentID makes a single-component node id encoder
|
||||
|
||||
@@ -78,6 +78,7 @@ const (
|
||||
KubernetesStatus = "kubernetes_status"
|
||||
KubernetesMessage = "kubernetes_message"
|
||||
KubernetesVolumeName = "kubernetes_volume_name"
|
||||
KubernetesProvisioner = "kubernetes_provisioner"
|
||||
// probe/awsecs
|
||||
ECSCluster = "ecs_cluster"
|
||||
ECSCreatedAt = "ecs_created_at"
|
||||
@@ -112,6 +113,7 @@ var commonKeys = map[string]string{
|
||||
SwarmService: SwarmService,
|
||||
PersistentVolume: PersistentVolume,
|
||||
PersistentVolumeClaim: PersistentVolumeClaim,
|
||||
StorageClass: StorageClass,
|
||||
|
||||
HostNodeID: HostNodeID,
|
||||
ControlProbeID: ControlProbeID,
|
||||
|
||||
@@ -31,6 +31,7 @@ const (
|
||||
SwarmService = "swarm_service"
|
||||
PersistentVolume = "persistent_volume"
|
||||
PersistentVolumeClaim = "persistent_volume_claim"
|
||||
StorageClass = "storage_class"
|
||||
|
||||
// Shapes used for different nodes
|
||||
Circle = "circle"
|
||||
@@ -43,6 +44,7 @@ const (
|
||||
Cloud = "cloud"
|
||||
Cylinder = "cylinder"
|
||||
DottedCylinder = "dottedcylinder"
|
||||
StorageSheet = "storagesheet"
|
||||
|
||||
// Used when counting the number of containers
|
||||
ContainersKey = "containers"
|
||||
@@ -69,6 +71,7 @@ var topologyNames = []string{
|
||||
SwarmService,
|
||||
PersistentVolume,
|
||||
PersistentVolumeClaim,
|
||||
StorageClass,
|
||||
}
|
||||
|
||||
// Report is the core data type. It's produced by probes, and consumed and
|
||||
@@ -165,6 +168,10 @@ type Report struct {
|
||||
// Metadata is limited for now, more to come later.
|
||||
PersistentVolumeClaim Topology
|
||||
|
||||
// Storage Class represent all kubernetes Storage Classes on hosts running probes.
|
||||
// Metadata is limited for now, more to come later.
|
||||
StorageClass Topology
|
||||
|
||||
DNS DNSRecords
|
||||
|
||||
// Sampling data for this report.
|
||||
@@ -266,6 +273,10 @@ func MakeReport() Report {
|
||||
WithShape(DottedCylinder).
|
||||
WithLabel("persistent volume claim", "persistent volume claims"),
|
||||
|
||||
StorageClass: MakeTopology().
|
||||
WithShape(StorageSheet).
|
||||
WithLabel("storage class", "storage classes"),
|
||||
|
||||
DNS: DNSRecords{},
|
||||
|
||||
Sampling: Sampling{},
|
||||
@@ -371,6 +382,8 @@ func (r *Report) topology(name string) *Topology {
|
||||
return &r.PersistentVolume
|
||||
case PersistentVolumeClaim:
|
||||
return &r.PersistentVolumeClaim
|
||||
case StorageClass:
|
||||
return &r.StorageClass
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user