mirror of
https://github.com/weaveworks/scope.git
synced 2026-07-19 21:39:26 +00:00
Add adjacencies for kubernetes storage components
- Kubernetes storage components such as PV and PVC are connected based on two parameters Persistent volume claim name and Persistent Volume name. - PVC contains the volume name which is, PV name itself. Hence, we can show edge for PVC and PV. - This will bring higher level visibility for kubernetes storage components. Signed-off-by: Satyam Zode <satyam.zode@openebs.io>
This commit is contained in:
@@ -86,4 +86,4 @@ export const NodeShapeHeptagon = props => NodeShape('heptagon', pathElement, hep
|
||||
export const NodeShapeOctagon = props => NodeShape('octagon', pathElement, octagonShapeProps, props);
|
||||
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 NodeShapeDottedCylinder = props => NodeShape('dottedcylinder', pathElement, dottedCylinderShapeProps, props);
|
||||
|
||||
@@ -186,4 +186,4 @@ function mapStateToProps(state) {
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{ clickNode, enterNode, leaveNode }
|
||||
)(Node);
|
||||
)(Node);
|
||||
|
||||
@@ -99,4 +99,4 @@ export const NODE_DETAILS_TABLE_XS_LABEL = {
|
||||
count: '#',
|
||||
// TODO: consider changing the name of this field on the BE
|
||||
container: '#',
|
||||
};
|
||||
};
|
||||
|
||||
@@ -30,4 +30,4 @@ export const heptagonShapeProps = { d: curvedUnitPolygonPath(7) };
|
||||
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 dottedCylinderShapeProps = { d: UNIT_CYLINDER_PATH };
|
||||
|
||||
@@ -10,10 +10,11 @@ import (
|
||||
|
||||
// These constants are keys used in node metadata
|
||||
const (
|
||||
Name = report.KubernetesName
|
||||
Namespace = report.KubernetesNamespace
|
||||
Created = report.KubernetesCreated
|
||||
LabelPrefix = "kubernetes_labels_"
|
||||
Name = report.KubernetesName
|
||||
Namespace = report.KubernetesNamespace
|
||||
Created = report.KubernetesCreated
|
||||
LabelPrefix = "kubernetes_labels_"
|
||||
VolumeClaimName = report.KubernetesVolumeClaim
|
||||
)
|
||||
|
||||
// Meta represents a metadata information about a Kubernetes object
|
||||
|
||||
@@ -8,11 +8,17 @@ import (
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
const (
|
||||
// BetaStorageClassAnnotation is the annotation for default storage class
|
||||
BetaStorageClassAnnotation = "volume.beta.kubernetes.io/storage-class"
|
||||
)
|
||||
|
||||
// PersistentVolumeClaim represents kubernetes PVC interface
|
||||
type PersistentVolumeClaim interface {
|
||||
Meta
|
||||
Selector() (labels.Selector, error)
|
||||
GetNode(probeID string) report.Node
|
||||
GetStorageClass() string
|
||||
}
|
||||
|
||||
// persistentVolumeClaim represents kubernetes Persistent Volume Claims
|
||||
@@ -26,15 +32,30 @@ func NewPersistentVolumeClaim(p *apiv1.PersistentVolumeClaim) PersistentVolumeCl
|
||||
return &persistentVolumeClaim{PersistentVolumeClaim: p, Meta: meta{p.ObjectMeta}}
|
||||
}
|
||||
|
||||
// GetStorageClass will fetch storage class name from given PVC
|
||||
func (p *persistentVolumeClaim) GetStorageClass() string {
|
||||
|
||||
// Use Beta storage class annotation first
|
||||
storageClassName := p.Annotations[BetaStorageClassAnnotation]
|
||||
if storageClassName != "" {
|
||||
return storageClassName
|
||||
}
|
||||
if p.Spec.StorageClassName != nil {
|
||||
storageClassName = *p.Spec.StorageClassName
|
||||
}
|
||||
|
||||
return storageClassName
|
||||
}
|
||||
|
||||
// GetNode returns Persistent Volume Claim as Node
|
||||
func (p *persistentVolumeClaim) GetNode(probeID string) report.Node {
|
||||
return p.MetaNode(report.MakePersistentVolumeClaimNodeID(p.UID())).WithLatests(map[string]string{
|
||||
report.ControlProbeID: probeID,
|
||||
NodeType: "Persistent Volume Claim",
|
||||
Namespace: p.GetNamespace(),
|
||||
Status: string(p.Status.Phase),
|
||||
VolumeName: p.Spec.VolumeName,
|
||||
AccessModes: string(p.Spec.AccessModes[0]),
|
||||
StorageClassName: p.GetStorageClass(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -75,12 +75,24 @@ func (p *pod) RestartCount() uint {
|
||||
return count
|
||||
}
|
||||
|
||||
func (p *pod) VolumeClaimName() string {
|
||||
claimName := ""
|
||||
for _, volume := range p.Spec.Volumes {
|
||||
if volume.VolumeSource.PersistentVolumeClaim != nil {
|
||||
claimName = volume.VolumeSource.PersistentVolumeClaim.ClaimName
|
||||
break
|
||||
}
|
||||
}
|
||||
return claimName
|
||||
}
|
||||
|
||||
func (p *pod) GetNode(probeID string) report.Node {
|
||||
latests := map[string]string{
|
||||
State: p.State(),
|
||||
IP: p.Status.PodIP,
|
||||
report.ControlProbeID: probeID,
|
||||
RestartCount: strconv.FormatUint(uint64(p.RestartCount()), 10),
|
||||
VolumeClaim: p.VolumeClaimName(),
|
||||
}
|
||||
|
||||
if p.Pod.Spec.HostNetwork {
|
||||
|
||||
@@ -3,6 +3,7 @@ package expected
|
||||
import (
|
||||
"github.com/weaveworks/scope/probe/docker"
|
||||
"github.com/weaveworks/scope/probe/host"
|
||||
"github.com/weaveworks/scope/probe/kubernetes"
|
||||
"github.com/weaveworks/scope/probe/process"
|
||||
"github.com/weaveworks/scope/render"
|
||||
"github.com/weaveworks/scope/report"
|
||||
@@ -16,6 +17,7 @@ var (
|
||||
heptagon = "heptagon"
|
||||
hexagon = "hexagon"
|
||||
cloud = "cloud"
|
||||
cylinder = "cylinder"
|
||||
|
||||
// Helper to make a report.node with some common options
|
||||
node = func(topology string) func(id string, adjacent ...string) report.Node {
|
||||
@@ -37,6 +39,8 @@ var (
|
||||
pod = node(report.Pod)
|
||||
service = node(report.Service)
|
||||
hostNode = node(report.Host)
|
||||
persistentVolume = node(report.PersistentVolume)
|
||||
persistentVolumeClaim = node(report.PersistentVolumeClaim)
|
||||
|
||||
UnknownPseudoNode1ID = render.MakePseudoNodeID(fixture.UnknownClient1IP)
|
||||
UnknownPseudoNode2ID = render.MakePseudoNodeID(fixture.UnknownClient3IP)
|
||||
@@ -323,6 +327,28 @@ var (
|
||||
render.IncomingInternetID: theIncomingInternetNode(fixture.ServerHostNodeID),
|
||||
render.OutgoingInternetID: theOutgoingInternetNode,
|
||||
}
|
||||
|
||||
RenderedPersistentVolume = report.Nodes{
|
||||
fixture.PersistentVolumeClaimNodeID: persistentVolumeClaim(fixture.PersistentVolumeClaimNodeID, fixture.PersistentVolumeNodeID).
|
||||
WithLatests(map[string]string{
|
||||
kubernetes.Name: "pvc-6124",
|
||||
kubernetes.Namespace: "ping",
|
||||
kubernetes.Status: "bound",
|
||||
kubernetes.VolumeName: "pongvolume",
|
||||
kubernetes.AccessModes: "ReadWriteOnce",
|
||||
kubernetes.StorageClassName: "standard",
|
||||
}).WithChild(report.MakeNode(fixture.PersistentVolumeNodeID).WithTopology(report.PersistentVolume)),
|
||||
|
||||
fixture.PersistentVolumeNodeID: persistentVolume(fixture.PersistentVolumeNodeID).
|
||||
WithLatests(map[string]string{
|
||||
kubernetes.Name: "pongvolume",
|
||||
kubernetes.Namespace: "ping",
|
||||
kubernetes.Status: "bound",
|
||||
kubernetes.VolumeClaim: "pvc-6124",
|
||||
kubernetes.AccessModes: "ReadWriteOnce",
|
||||
kubernetes.StorageClassName: "standard",
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
func newu64(value uint64) *uint64 { return &value }
|
||||
|
||||
@@ -80,7 +80,10 @@ func (f FilterFunc) Transform(nodes Nodes) Nodes {
|
||||
newAdjacency = newAdjacency.Add(dstID)
|
||||
}
|
||||
}
|
||||
node.Adjacency = newAdjacency
|
||||
claimName, ok := node.Latest.Lookup(kubernetes.VolumeClaim)
|
||||
if claimName == "" || !ok {
|
||||
node.Adjacency = newAdjacency
|
||||
}
|
||||
output[id] = node
|
||||
}
|
||||
|
||||
|
||||
155
render/persistentvolume.go
Normal file
155
render/persistentvolume.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package render
|
||||
|
||||
import (
|
||||
"github.com/weaveworks/scope/probe/kubernetes"
|
||||
"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 {
|
||||
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
|
||||
topology string
|
||||
}
|
||||
|
||||
func (c connectionStorageJoin) Render(rpt report.Report) Nodes {
|
||||
inputNodes := TopologySelector(c.topology).Render(rpt).Nodes
|
||||
|
||||
var pvcNodes = map[string]string{}
|
||||
for _, n := range inputNodes {
|
||||
pvName := c.toPV(n)
|
||||
pvcNodes[pvName] = n.ID
|
||||
}
|
||||
|
||||
return MapStorageEndpoints(
|
||||
func(m report.Node) string {
|
||||
pvName, ok := m.Latest.Lookup(kubernetes.Name)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
id := pvcNodes[pvName]
|
||||
return id
|
||||
}, c.topology).Render(rpt)
|
||||
}
|
||||
|
||||
// Map2PVName accepts PV Node and returns Volume name associated with PV Node.
|
||||
func Map2PVName(m report.Node) string {
|
||||
pvName, ok := m.Latest.Lookup(kubernetes.VolumeName)
|
||||
if !ok {
|
||||
pvName = ""
|
||||
}
|
||||
return pvName
|
||||
}
|
||||
|
||||
// Map2PVCName returns pvc name
|
||||
func Map2PVCName(m report.Node) string {
|
||||
pvcName, ok := m.Latest.Lookup(kubernetes.VolumeClaim)
|
||||
if !ok {
|
||||
pvcName = ""
|
||||
}
|
||||
return pvcName
|
||||
}
|
||||
|
||||
// Map2PVNode returns pv node ID
|
||||
func Map2PVNode(n report.Node) string {
|
||||
if pvNodeID, ok := n.Latest.Lookup(report.MakePersistentVolumeNodeID(n.ID)); ok {
|
||||
return pvNodeID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// mapStorageEndpoints is the Renderer for rendering storage components together.
|
||||
type mapStorageEndpoints struct {
|
||||
f endpointMapFunc
|
||||
topology string
|
||||
}
|
||||
|
||||
// MapStorageEndpoints instantiates mapStorageEndpoints and returns same
|
||||
func MapStorageEndpoints(f endpointMapFunc, 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" {
|
||||
endpoints = SelectPersistentVolume.Render(rpt)
|
||||
}
|
||||
if e.topology == "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)
|
||||
}
|
||||
}
|
||||
return ret.storageResult(endpoints)
|
||||
}
|
||||
20
render/persistentvolume_test.go
Normal file
20
render/persistentvolume_test.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package render_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/weaveworks/common/test"
|
||||
"github.com/weaveworks/scope/render"
|
||||
"github.com/weaveworks/scope/render/expected"
|
||||
"github.com/weaveworks/scope/test/fixture"
|
||||
"github.com/weaveworks/scope/test/reflect"
|
||||
"github.com/weaveworks/scope/test/utils"
|
||||
)
|
||||
|
||||
func TestPersistentVolumeRenderer(t *testing.T) {
|
||||
have := utils.Prune(render.PersistentVolumeRenderer.Render(fixture.Report).Nodes)
|
||||
want := utils.Prune(expected.RenderedPersistentVolume)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error(test.Diff(want, have))
|
||||
}
|
||||
}
|
||||
@@ -251,6 +251,26 @@ func (ret *joinResults) rewriteAdjacency(outID string, adjacency report.IDList)
|
||||
ret.nodes[outID] = out
|
||||
}
|
||||
|
||||
// storageAdjacency sets adjacency for the given node ID
|
||||
func (ret *joinResults) storageAdjacency(outID string, adjacency string) {
|
||||
out := ret.nodes[outID]
|
||||
out.Adjacency = out.Adjacency.Add(adjacency)
|
||||
ret.nodes[outID] = out
|
||||
}
|
||||
|
||||
// storageResult returns Nodes for after adding adjacencies
|
||||
func (ret *joinResults) storageResult(input Nodes) Nodes {
|
||||
for _, n := range input.Nodes {
|
||||
outID, ok := ret.mapped[n.ID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// Since PV and PVC will have only single adjacency
|
||||
ret.storageAdjacency(outID, n.ID)
|
||||
}
|
||||
return Nodes{Nodes: ret.nodes}
|
||||
}
|
||||
|
||||
// ResetCache blows away the rendered node cache, and known service
|
||||
// cache.
|
||||
func ResetCache() {
|
||||
|
||||
@@ -93,14 +93,18 @@ var (
|
||||
ClientContainerImageName = "image/client"
|
||||
ServerContainerImageName = "image/server"
|
||||
|
||||
KubernetesNamespace = "ping"
|
||||
ClientPodUID = "5d4c3b2a1"
|
||||
ServerPodUID = "i9h8g7f6e"
|
||||
ClientPodNodeID = report.MakePodNodeID(ClientPodUID)
|
||||
ServerPodNodeID = report.MakePodNodeID(ServerPodUID)
|
||||
ServiceName = "pongservice"
|
||||
ServiceUID = "service1234"
|
||||
ServiceNodeID = report.MakeServiceNodeID(ServiceUID)
|
||||
KubernetesNamespace = "ping"
|
||||
ClientPodUID = "5d4c3b2a1"
|
||||
ServerPodUID = "i9h8g7f6e"
|
||||
ClientPodNodeID = report.MakePodNodeID(ClientPodUID)
|
||||
ServerPodNodeID = report.MakePodNodeID(ServerPodUID)
|
||||
ServiceName = "pongservice"
|
||||
ServiceUID = "service1234"
|
||||
ServiceNodeID = report.MakeServiceNodeID(ServiceUID)
|
||||
PersistentVolumeUID = "pv1234"
|
||||
PersistentVolumeNodeID = report.MakePersistentVolumeNodeID(PersistentVolumeUID)
|
||||
PersistentVolumeClaimUID = "pvc1234"
|
||||
PersistentVolumeClaimNodeID = report.MakePersistentVolumeClaimNodeID(PersistentVolumeClaimUID)
|
||||
|
||||
ClientProcess1CPUMetric = report.MakeSingletonMetric(Now.Add(-1*time.Second), 0.01)
|
||||
ClientProcess1MemoryMetric = report.MakeSingletonMetric(Now.Add(-2*time.Second), 0.02)
|
||||
@@ -348,6 +352,36 @@ var (
|
||||
WithTopology(report.Service),
|
||||
},
|
||||
}.WithShape(report.Heptagon).WithLabel("service", "services"),
|
||||
PersistentVolumeClaim: report.Topology{
|
||||
Nodes: report.Nodes{
|
||||
PersistentVolumeClaimNodeID: report.MakeNodeWith(
|
||||
|
||||
PersistentVolumeClaimNodeID, map[string]string{
|
||||
kubernetes.Name: "pvc-6124",
|
||||
kubernetes.Namespace: "ping",
|
||||
kubernetes.Status: "bound",
|
||||
kubernetes.VolumeName: "pongvolume",
|
||||
kubernetes.AccessModes: "ReadWriteOnce",
|
||||
kubernetes.StorageClassName: "standard",
|
||||
}).
|
||||
WithTopology(report.PersistentVolumeClaim),
|
||||
},
|
||||
}.WithShape(report.Cylinder).WithLabel("persistent volume claim", "persistent volume claims"),
|
||||
PersistentVolume: report.Topology{
|
||||
Nodes: report.Nodes{
|
||||
PersistentVolumeNodeID: report.MakeNodeWith(
|
||||
|
||||
PersistentVolumeNodeID, map[string]string{
|
||||
kubernetes.Name: "pongvolume",
|
||||
kubernetes.Namespace: "ping",
|
||||
kubernetes.Status: "bound",
|
||||
kubernetes.VolumeClaim: "pvc-6124",
|
||||
kubernetes.AccessModes: "ReadWriteOnce",
|
||||
kubernetes.StorageClassName: "standard",
|
||||
}).
|
||||
WithTopology(report.PersistentVolume),
|
||||
},
|
||||
}.WithShape(report.Cylinder).WithLabel("persistent volume", "persistent volumes"),
|
||||
Sampling: report.Sampling{
|
||||
Count: 1024,
|
||||
Total: 4096,
|
||||
|
||||
Reference in New Issue
Block a user