mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 02:00:43 +00:00
Merge pull request #1386 from weaveworks/1340-k8s-filter-namespace
Filter by Kubernetes Namespaces
This commit is contained in:
@@ -1,13 +1,16 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/weaveworks/scope/probe/kubernetes"
|
||||
"github.com/weaveworks/scope/render"
|
||||
"github.com/weaveworks/scope/report"
|
||||
)
|
||||
@@ -21,30 +24,6 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
serviceFilters := []APITopologyOptionGroup{
|
||||
{
|
||||
ID: "system",
|
||||
Default: "application",
|
||||
Options: []APITopologyOption{
|
||||
{"system", "System services", render.IsSystem},
|
||||
{"application", "Application services", render.IsApplication},
|
||||
{"both", "Both", render.Noop},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
podFilters := []APITopologyOptionGroup{
|
||||
{
|
||||
ID: "system",
|
||||
Default: "application",
|
||||
Options: []APITopologyOption{
|
||||
{"system", "System pods", render.IsSystem},
|
||||
{"application", "Application pods", render.IsApplication},
|
||||
{"both", "Both", render.Noop},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
containerFilters := []APITopologyOptionGroup{
|
||||
{
|
||||
ID: "system",
|
||||
@@ -52,7 +31,7 @@ func init() {
|
||||
Options: []APITopologyOption{
|
||||
{"system", "System containers", render.IsSystem},
|
||||
{"application", "Application containers", render.IsApplication},
|
||||
{"both", "Both", render.Noop},
|
||||
{"both", "Both", nil},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -61,7 +40,7 @@ func init() {
|
||||
Options: []APITopologyOption{
|
||||
{"stopped", "Stopped containers", render.IsStopped},
|
||||
{"running", "Running containers", render.IsRunning},
|
||||
{"both", "Both", render.Noop},
|
||||
{"both", "Both", nil},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -116,19 +95,12 @@ func init() {
|
||||
Name: "by DNS name",
|
||||
Options: containerFilters,
|
||||
},
|
||||
APITopologyDesc{
|
||||
id: "hosts",
|
||||
renderer: render.HostRenderer,
|
||||
Name: "Hosts",
|
||||
Rank: 4,
|
||||
},
|
||||
APITopologyDesc{
|
||||
id: "pods",
|
||||
renderer: render.PodRenderer,
|
||||
Name: "Pods",
|
||||
Rank: 3,
|
||||
HideIfEmpty: true,
|
||||
Options: podFilters,
|
||||
},
|
||||
APITopologyDesc{
|
||||
id: "pods-by-service",
|
||||
@@ -136,11 +108,63 @@ func init() {
|
||||
renderer: render.PodServiceRenderer,
|
||||
Name: "by service",
|
||||
HideIfEmpty: true,
|
||||
Options: serviceFilters,
|
||||
},
|
||||
APITopologyDesc{
|
||||
id: "hosts",
|
||||
renderer: render.HostRenderer,
|
||||
Name: "Hosts",
|
||||
Rank: 4,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// kubernetesFilters generates the current kubernetes filters based on the
|
||||
// available k8s topologies.
|
||||
func kubernetesFilters(namespaces ...string) APITopologyOptionGroup {
|
||||
options := APITopologyOptionGroup{ID: "namespace", Default: "all"}
|
||||
for _, namespace := range namespaces {
|
||||
options.Options = append(options.Options, APITopologyOption{namespace, namespace, render.IsNamespace(namespace)})
|
||||
}
|
||||
options.Options = append(options.Options, APITopologyOption{"all", "All Namespaces", nil})
|
||||
return options
|
||||
}
|
||||
|
||||
// updateFilters updates the available filters based on the current report.
|
||||
// Currently only kubernetes changes.
|
||||
func updateFilters(rpt report.Report, topologies []APITopologyDesc) []APITopologyDesc {
|
||||
namespaces := map[string]struct{}{}
|
||||
for _, t := range []report.Topology{rpt.Pod, rpt.Service} {
|
||||
for _, n := range t.Nodes {
|
||||
if state, ok := n.Latest.Lookup(kubernetes.PodState); ok && state == kubernetes.StateDeleted {
|
||||
continue
|
||||
}
|
||||
if namespace, ok := n.Latest.Lookup(kubernetes.Namespace); ok {
|
||||
namespaces[namespace] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
var ns []string
|
||||
for namespace := range namespaces {
|
||||
ns = append(ns, namespace)
|
||||
}
|
||||
sort.Strings(ns)
|
||||
for i, t := range topologies {
|
||||
if t.id == "pods" || t.id == "pods-by-service" {
|
||||
topologies[i] = updateTopologyFilters(t, []APITopologyOptionGroup{kubernetesFilters(ns...)})
|
||||
}
|
||||
}
|
||||
return topologies
|
||||
}
|
||||
|
||||
// updateTopologyFilters recursively sets the options on a topology description
|
||||
func updateTopologyFilters(t APITopologyDesc, options []APITopologyOptionGroup) APITopologyDesc {
|
||||
t.Options = options
|
||||
for i, sub := range t.SubTopologies {
|
||||
t.SubTopologies[i] = updateTopologyFilters(sub, options)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// registry is a threadsafe store of the available topologies
|
||||
type registry struct {
|
||||
sync.RWMutex
|
||||
@@ -239,23 +263,23 @@ func (r *registry) makeTopologyList(rep Reporter) CtxHandlerFunc {
|
||||
respondWith(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
topologies := r.renderTopologies(report, req)
|
||||
respondWith(w, http.StatusOK, topologies)
|
||||
respondWith(w, http.StatusOK, r.renderTopologies(report, req))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *registry) renderTopologies(rpt report.Report, req *http.Request) []APITopologyDesc {
|
||||
topologies := []APITopologyDesc{}
|
||||
req.ParseForm()
|
||||
r.walk(func(desc APITopologyDesc) {
|
||||
renderer, decorator := renderedForRequest(req, desc)
|
||||
renderer, decorator, _ := r.rendererForTopology(desc.id, req.Form, rpt)
|
||||
desc.Stats = decorateWithStats(rpt, renderer, decorator)
|
||||
for i := range desc.SubTopologies {
|
||||
renderer, decorator := renderedForRequest(req, desc.SubTopologies[i])
|
||||
for i, sub := range desc.SubTopologies {
|
||||
renderer, decorator, _ := r.rendererForTopology(sub.id, req.Form, rpt)
|
||||
desc.SubTopologies[i].Stats = decorateWithStats(rpt, renderer, decorator)
|
||||
}
|
||||
topologies = append(topologies, desc)
|
||||
})
|
||||
return topologies
|
||||
return updateFilters(rpt, topologies)
|
||||
}
|
||||
|
||||
func decorateWithStats(rpt report.Report, renderer render.Renderer, decorator render.Decorator) topologyStats {
|
||||
@@ -280,10 +304,16 @@ func decorateWithStats(rpt report.Report, renderer render.Renderer, decorator re
|
||||
}
|
||||
}
|
||||
|
||||
func renderedForRequest(r *http.Request, topology APITopologyDesc) (render.Renderer, render.Decorator) {
|
||||
func (r *registry) rendererForTopology(topologyID string, values url.Values, rpt report.Report) (render.Renderer, render.Decorator, error) {
|
||||
topology, ok := r.get(topologyID)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("topology not found: %s", topologyID)
|
||||
}
|
||||
topology = updateFilters(rpt, []APITopologyDesc{topology})[0]
|
||||
|
||||
var filters []render.FilterFunc
|
||||
for _, group := range topology.Options {
|
||||
value := r.FormValue(group.ID)
|
||||
value := values.Get(group.ID)
|
||||
for _, opt := range group.Options {
|
||||
if opt.filter == nil {
|
||||
continue
|
||||
@@ -299,23 +329,37 @@ func renderedForRequest(r *http.Request, topology APITopologyDesc) (render.Rende
|
||||
return render.MakeFilter(render.ComposeFilterFuncs(filters...), renderer)
|
||||
}
|
||||
}
|
||||
return topology.renderer, decorator
|
||||
return topology.renderer, decorator, nil
|
||||
}
|
||||
|
||||
type reportRenderHandler func(
|
||||
context.Context,
|
||||
Reporter, render.Renderer, render.Decorator,
|
||||
http.ResponseWriter, *http.Request,
|
||||
)
|
||||
type reporterHandler func(context.Context, Reporter, http.ResponseWriter, *http.Request)
|
||||
|
||||
func (r *registry) captureRenderer(rep Reporter, f reportRenderHandler) CtxHandlerFunc {
|
||||
func captureReporter(rep Reporter, f reporterHandler) CtxHandlerFunc {
|
||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
f(ctx, rep, w, r)
|
||||
}
|
||||
}
|
||||
|
||||
type rendererHandler func(context.Context, render.Renderer, render.Decorator, report.Report, http.ResponseWriter, *http.Request)
|
||||
|
||||
func (r *registry) captureRenderer(rep Reporter, f rendererHandler) CtxHandlerFunc {
|
||||
return func(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
||||
topology, ok := r.get(mux.Vars(req)["topology"])
|
||||
if !ok {
|
||||
topologyID := mux.Vars(req)["topology"]
|
||||
if _, ok := r.get(topologyID); !ok {
|
||||
http.NotFound(w, req)
|
||||
return
|
||||
}
|
||||
renderer, decorator := renderedForRequest(req, topology)
|
||||
f(ctx, rep, renderer, decorator, w, req)
|
||||
rpt, err := rep.Report(ctx)
|
||||
if err != nil {
|
||||
respondWith(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
req.ParseForm()
|
||||
renderer, decorator, err := r.rendererForTopology(topologyID, req.Form, rpt)
|
||||
if err != nil {
|
||||
respondWith(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
f(ctx, renderer, decorator, rpt, w, req)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/weaveworks/scope/common/xfer"
|
||||
"github.com/weaveworks/scope/render"
|
||||
"github.com/weaveworks/scope/render/detailed"
|
||||
"github.com/weaveworks/scope/report"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -28,26 +29,34 @@ type APINode struct {
|
||||
}
|
||||
|
||||
// Full topology.
|
||||
func handleTopology(
|
||||
ctx context.Context,
|
||||
rep Reporter, renderer render.Renderer, decorator render.Decorator,
|
||||
w http.ResponseWriter, r *http.Request,
|
||||
) {
|
||||
report, err := rep.Report(ctx)
|
||||
if err != nil {
|
||||
respondWith(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
func handleTopology(ctx context.Context, renderer render.Renderer, decorator render.Decorator, report report.Report, w http.ResponseWriter, r *http.Request) {
|
||||
respondWith(w, http.StatusOK, APITopology{
|
||||
Nodes: detailed.Summaries(report, renderer.Render(report, decorator)),
|
||||
})
|
||||
}
|
||||
|
||||
// Websocket for the full topology. This route overlaps with the next.
|
||||
func handleWs(
|
||||
// Individual nodes.
|
||||
func handleNode(ctx context.Context, renderer render.Renderer, _ render.Decorator, report report.Report, w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
topologyID = vars["topology"]
|
||||
nodeID = vars["id"]
|
||||
rendered = renderer.Render(report, nil)
|
||||
node, ok = rendered[nodeID]
|
||||
)
|
||||
if !ok {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
respondWith(w, http.StatusOK, APINode{Node: detailed.MakeNode(topologyID, report, rendered, node)})
|
||||
}
|
||||
|
||||
// Websocket for the full topology.
|
||||
func handleWebsocket(
|
||||
ctx context.Context,
|
||||
rep Reporter, renderer render.Renderer, decorator render.Decorator,
|
||||
w http.ResponseWriter, r *http.Request,
|
||||
rep Reporter,
|
||||
w http.ResponseWriter,
|
||||
r *http.Request,
|
||||
) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
respondWith(w, http.StatusInternalServerError, err.Error())
|
||||
@@ -61,43 +70,7 @@ func handleWs(
|
||||
return
|
||||
}
|
||||
}
|
||||
handleWebsocket(ctx, w, r, rep, renderer, decorator, loop)
|
||||
}
|
||||
|
||||
// Individual nodes.
|
||||
func handleNode(
|
||||
ctx context.Context,
|
||||
rep Reporter, renderer render.Renderer, _ render.Decorator,
|
||||
w http.ResponseWriter, r *http.Request,
|
||||
) {
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
topologyID = vars["topology"]
|
||||
nodeID = vars["id"]
|
||||
report, err = rep.Report(ctx)
|
||||
rendered = renderer.Render(report, render.FilterNoop)
|
||||
node, ok = rendered[nodeID]
|
||||
)
|
||||
if err != nil {
|
||||
respondWith(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
respondWith(w, http.StatusOK, APINode{Node: detailed.MakeNode(topologyID, report, rendered, node)})
|
||||
}
|
||||
|
||||
func handleWebsocket(
|
||||
ctx context.Context,
|
||||
w http.ResponseWriter,
|
||||
r *http.Request,
|
||||
rep Reporter,
|
||||
renderer render.Renderer,
|
||||
decorator render.Decorator,
|
||||
loop time.Duration,
|
||||
) {
|
||||
conn, err := xfer.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
// log.Info("Upgrade:", err)
|
||||
@@ -122,6 +95,7 @@ func handleWebsocket(
|
||||
previousTopo detailed.NodeSummaries
|
||||
tick = time.Tick(loop)
|
||||
wait = make(chan struct{}, 1)
|
||||
topologyID = mux.Vars(r)["topology"]
|
||||
)
|
||||
rep.WaitOn(ctx, wait)
|
||||
defer rep.UnWait(ctx, wait)
|
||||
@@ -132,6 +106,11 @@ func handleWebsocket(
|
||||
log.Errorf("Error generating report: %v", err)
|
||||
return
|
||||
}
|
||||
renderer, decorator, err := topologyRegistry.rendererForTopology(topologyID, r.Form, report)
|
||||
if err != nil {
|
||||
log.Errorf("Error generating report: %v", err)
|
||||
return
|
||||
}
|
||||
newTopo := detailed.Summaries(report, renderer.Render(report, decorator))
|
||||
diff := detailed.TopoDiff(previousTopo, newTopo)
|
||||
previousTopo = newTopo
|
||||
|
||||
@@ -91,7 +91,7 @@ func RegisterTopologyRoutes(router *mux.Router, r Reporter) {
|
||||
get.HandleFunc("/api/topology/{topology}",
|
||||
gzipHandler(requestContextDecorator(topologyRegistry.captureRenderer(r, handleTopology))))
|
||||
get.HandleFunc("/api/topology/{topology}/ws",
|
||||
requestContextDecorator(topologyRegistry.captureRenderer(r, handleWs))) // NB not gzip!
|
||||
requestContextDecorator(captureReporter(r, handleWebsocket))) // NB not gzip!
|
||||
get.MatcherFunc(URLMatcher("/api/topology/{topology}/{id}")).HandlerFunc(
|
||||
gzipHandler(requestContextDecorator(topologyRegistry.captureRenderer(r, handleNode))))
|
||||
get.HandleFunc("/api/report",
|
||||
|
||||
@@ -37,13 +37,13 @@ type Pod interface {
|
||||
|
||||
type pod struct {
|
||||
*api.Pod
|
||||
serviceIDs []string
|
||||
serviceIDs report.StringSet
|
||||
Node *api.Node
|
||||
}
|
||||
|
||||
// NewPod creates a new Pod
|
||||
func NewPod(p *api.Pod) Pod {
|
||||
return &pod{Pod: p}
|
||||
return &pod{Pod: p, serviceIDs: report.MakeStringSet()}
|
||||
}
|
||||
|
||||
func (p *pod) UID() string {
|
||||
@@ -75,7 +75,7 @@ func (p *pod) Labels() labels.Labels {
|
||||
}
|
||||
|
||||
func (p *pod) AddServiceID(id string) {
|
||||
p.serviceIDs = append(p.serviceIDs, id)
|
||||
p.serviceIDs = p.serviceIDs.Add(id)
|
||||
}
|
||||
|
||||
func (p *pod) State() string {
|
||||
@@ -95,10 +95,7 @@ func (p *pod) GetNode(probeID string) report.Node {
|
||||
PodState: p.State(),
|
||||
PodIP: p.Status.PodIP,
|
||||
report.ControlProbeID: probeID,
|
||||
})
|
||||
if len(p.serviceIDs) > 0 {
|
||||
n = n.WithLatests(map[string]string{ServiceIDs: strings.Join(p.serviceIDs, " ")})
|
||||
}
|
||||
}).WithSets(report.EmptySets.Add(ServiceIDs, p.serviceIDs))
|
||||
for _, serviceID := range p.serviceIDs {
|
||||
segments := strings.SplitN(serviceID, "/", 2)
|
||||
if len(segments) != 2 {
|
||||
|
||||
@@ -174,20 +174,23 @@ func TestReporter(t *testing.T) {
|
||||
id string
|
||||
parentService string
|
||||
latest map[string]string
|
||||
sets map[string]report.StringSet
|
||||
}{
|
||||
{pod1ID, serviceID, map[string]string{
|
||||
kubernetes.PodID: "ping/pong-a",
|
||||
kubernetes.PodName: "pong-a",
|
||||
kubernetes.Namespace: "ping",
|
||||
kubernetes.PodCreated: pod1.Created(),
|
||||
kubernetes.ServiceIDs: "ping/pongservice",
|
||||
}, map[string]report.StringSet{
|
||||
kubernetes.ServiceIDs: report.MakeStringSet("ping/pongservice"),
|
||||
}},
|
||||
{pod2ID, serviceID, map[string]string{
|
||||
kubernetes.PodID: "ping/pong-b",
|
||||
kubernetes.PodName: "pong-b",
|
||||
kubernetes.Namespace: "ping",
|
||||
kubernetes.PodCreated: pod1.Created(),
|
||||
kubernetes.ServiceIDs: "ping/pongservice",
|
||||
}, map[string]report.StringSet{
|
||||
kubernetes.ServiceIDs: report.MakeStringSet("ping/pongservice"),
|
||||
}},
|
||||
} {
|
||||
node, ok := rpt.Pod.Nodes[pod.id]
|
||||
@@ -204,6 +207,12 @@ func TestReporter(t *testing.T) {
|
||||
t.Errorf("Expected pod %s latest %q: %q, got %q", pod.id, k, want, have)
|
||||
}
|
||||
}
|
||||
|
||||
for k, want := range pod.sets {
|
||||
if have, ok := node.Sets.Lookup(k); !ok || !reflect.DeepEqual(want, have) {
|
||||
t.Errorf("Expected pod %s sets %q: %q, got %q", pod.id, k, want, have)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reporter should have added a service
|
||||
|
||||
@@ -238,8 +238,8 @@ var (
|
||||
render.OutgoingInternetID: theOutgoingInternetNode,
|
||||
}
|
||||
|
||||
unmanagedServerID = render.MakePseudoNodeID(render.UnmanagedID, fixture.ServerHostID)
|
||||
unmanagedServerNode = pseudo(unmanagedServerID, render.OutgoingInternetID).WithChildren(report.MakeNodeSet(
|
||||
UnmanagedServerID = render.MakePseudoNodeID(render.UnmanagedID, fixture.ServerHostID)
|
||||
unmanagedServerNode = pseudo(UnmanagedServerID, render.OutgoingInternetID).WithChildren(report.MakeNodeSet(
|
||||
uncontainedServerNode,
|
||||
RenderedEndpoints[fixture.NonContainerNodeID],
|
||||
RenderedProcesses[fixture.NonContainerProcessNodeID],
|
||||
@@ -262,7 +262,7 @@ var (
|
||||
RenderedContainers[fixture.ServerContainerNodeID],
|
||||
)),
|
||||
|
||||
unmanagedServerID: unmanagedServerNode,
|
||||
UnmanagedServerID: unmanagedServerNode,
|
||||
render.IncomingInternetID: theIncomingInternetNode(fixture.ServerPodNodeID),
|
||||
render.OutgoingInternetID: theOutgoingInternetNode,
|
||||
}
|
||||
@@ -282,7 +282,7 @@ var (
|
||||
RenderedPods[fixture.ServerPodNodeID],
|
||||
)),
|
||||
|
||||
unmanagedServerID: unmanagedServerNode,
|
||||
UnmanagedServerID: unmanagedServerNode,
|
||||
render.IncomingInternetID: theIncomingInternetNode(fixture.ServiceNodeID),
|
||||
render.OutgoingInternetID: theOutgoingInternetNode,
|
||||
}
|
||||
|
||||
@@ -275,6 +275,14 @@ func HasChildren(topology string) FilterFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// IsNamespace checks if the node is a pod/service in the specified namespace
|
||||
func IsNamespace(namespace string) FilterFunc {
|
||||
return func(n report.Node) bool {
|
||||
gotNamespace, ok := n.Latest.Lookup(kubernetes.Namespace)
|
||||
return !ok || namespace == gotNamespace
|
||||
}
|
||||
}
|
||||
|
||||
var systemContainerNames = map[string]struct{}{
|
||||
"weavescope": {},
|
||||
"weavedns": {},
|
||||
|
||||
@@ -16,37 +16,40 @@ const (
|
||||
|
||||
// PodRenderer is a Renderer which produces a renderable kubernetes
|
||||
// graph by merging the container graph and the pods topology.
|
||||
var PodRenderer = MakeFilter(
|
||||
func(n report.Node) bool {
|
||||
// Drop deleted or empty pods
|
||||
state, ok := n.Latest.Lookup(kubernetes.PodState)
|
||||
return HasChildren(report.Container)(n) && (!ok || state != kubernetes.StateDeleted)
|
||||
},
|
||||
MakeReduce(
|
||||
MakeFilter(
|
||||
func(n report.Node) bool {
|
||||
// Drop unconnected pseudo nodes (could appear due to filtering)
|
||||
_, isConnected := n.Latest.Lookup(IsConnected)
|
||||
return n.Topology != Pseudo || isConnected
|
||||
},
|
||||
ColorConnected(MakeMap(
|
||||
MapContainer2Pod,
|
||||
ContainerWithImageNameRenderer,
|
||||
)),
|
||||
var PodRenderer = ApplyDecorators(
|
||||
MakeFilter(
|
||||
func(n report.Node) bool {
|
||||
state, ok := n.Latest.Lookup(kubernetes.PodState)
|
||||
return (!ok || state != kubernetes.StateDeleted)
|
||||
},
|
||||
MakeReduce(
|
||||
MakeFilter(
|
||||
func(n report.Node) bool {
|
||||
// Drop unconnected pseudo nodes (could appear due to filtering)
|
||||
_, isConnected := n.Latest.Lookup(IsConnected)
|
||||
return n.Topology != Pseudo || isConnected
|
||||
},
|
||||
ColorConnected(MakeMap(
|
||||
MapContainer2Pod,
|
||||
ContainerWithImageNameRenderer,
|
||||
)),
|
||||
),
|
||||
SelectPod,
|
||||
),
|
||||
SelectPod,
|
||||
),
|
||||
)
|
||||
|
||||
// PodServiceRenderer is a Renderer which produces a renderable kubernetes services
|
||||
// graph by merging the pods graph and the services topology.
|
||||
var PodServiceRenderer = FilterEmpty(report.Pod,
|
||||
MakeReduce(
|
||||
MakeMap(
|
||||
MapPod2Service,
|
||||
PodRenderer,
|
||||
var PodServiceRenderer = ApplyDecorators(
|
||||
FilterEmpty(report.Pod,
|
||||
MakeReduce(
|
||||
MakeMap(
|
||||
MapPod2Service,
|
||||
PodRenderer,
|
||||
),
|
||||
SelectService,
|
||||
),
|
||||
SelectService,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -117,13 +120,13 @@ func MapPod2Service(pod report.Node, _ report.Networks) report.Nodes {
|
||||
if !ok {
|
||||
return report.Nodes{}
|
||||
}
|
||||
ids, ok := pod.Latest.Lookup(kubernetes.ServiceIDs)
|
||||
serviceIDs, ok := pod.Sets.Lookup(kubernetes.ServiceIDs)
|
||||
if !ok {
|
||||
return report.Nodes{}
|
||||
}
|
||||
|
||||
result := report.Nodes{}
|
||||
for _, serviceID := range strings.Fields(ids) {
|
||||
for _, serviceID := range serviceIDs {
|
||||
serviceName := strings.TrimPrefix(serviceID, namespace+"/")
|
||||
id := report.MakeServiceNodeID(namespace, serviceName)
|
||||
node := NewDerivedNode(id, pod).WithTopology(report.Service)
|
||||
|
||||
@@ -7,14 +7,13 @@ import (
|
||||
"github.com/weaveworks/scope/probe/kubernetes"
|
||||
"github.com/weaveworks/scope/render"
|
||||
"github.com/weaveworks/scope/render/expected"
|
||||
"github.com/weaveworks/scope/report"
|
||||
"github.com/weaveworks/scope/test"
|
||||
"github.com/weaveworks/scope/test/fixture"
|
||||
"github.com/weaveworks/scope/test/reflect"
|
||||
)
|
||||
|
||||
func TestPodRenderer(t *testing.T) {
|
||||
have := Prune(render.PodRenderer.Render(fixture.Report, render.FilterNoop))
|
||||
have := Prune(render.PodRenderer.Render(fixture.Report, nil))
|
||||
want := Prune(expected.RenderedPods)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error(test.Diff(want, have))
|
||||
@@ -26,7 +25,7 @@ func TestPodFilterRenderer(t *testing.T) {
|
||||
// it is filtered out correctly.
|
||||
input := fixture.Report.Copy()
|
||||
input.Pod.Nodes[fixture.ClientPodNodeID] = input.Pod.Nodes[fixture.ClientPodNodeID].WithLatests(map[string]string{
|
||||
kubernetes.PodID: "pod:kube-system/foo",
|
||||
kubernetes.PodID: "kube-system/foo",
|
||||
kubernetes.Namespace: "kube-system",
|
||||
kubernetes.PodName: "foo",
|
||||
})
|
||||
@@ -43,7 +42,7 @@ func TestPodFilterRenderer(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPodServiceRenderer(t *testing.T) {
|
||||
have := Prune(render.PodServiceRenderer.Render(fixture.Report, render.FilterNoop))
|
||||
have := Prune(render.PodServiceRenderer.Render(fixture.Report, nil))
|
||||
want := Prune(expected.RenderedPodServices)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error(test.Diff(want, have))
|
||||
@@ -54,25 +53,12 @@ func TestPodServiceFilterRenderer(t *testing.T) {
|
||||
// tag on containers or pod namespace in the topology and ensure
|
||||
// it is filtered out correctly.
|
||||
input := fixture.Report.Copy()
|
||||
input.Pod.Nodes[fixture.ClientPodNodeID] = input.Pod.Nodes[fixture.ClientPodNodeID].WithLatests(map[string]string{
|
||||
kubernetes.PodID: "pod:kube-system/foo",
|
||||
kubernetes.Namespace: "kube-system",
|
||||
kubernetes.PodName: "foo",
|
||||
})
|
||||
input.Container.Nodes[fixture.ClientContainerNodeID] = input.Container.Nodes[fixture.ClientContainerNodeID].WithLatests(map[string]string{
|
||||
docker.LabelPrefix + "io.kubernetes.pod.name": "kube-system/foo",
|
||||
})
|
||||
have := Prune(render.PodServiceRenderer.Render(input, render.FilterApplication))
|
||||
have := Prune(render.PodServiceRenderer.Render(input, render.FilterSystem))
|
||||
want := Prune(expected.RenderedPodServices.Copy())
|
||||
wantNode := want[fixture.ServiceNodeID]
|
||||
wantNode.Adjacency = nil
|
||||
wantNode.Children = report.MakeNodeSet(
|
||||
expected.RenderedEndpoints[fixture.Server80NodeID],
|
||||
expected.RenderedProcesses[fixture.ServerProcessNodeID],
|
||||
expected.RenderedContainers[fixture.ServerContainerNodeID],
|
||||
expected.RenderedPods[fixture.ServerPodNodeID],
|
||||
)
|
||||
want[fixture.ServiceNodeID] = wantNode
|
||||
delete(want, fixture.ServiceNodeID)
|
||||
delete(want, expected.UnmanagedServerID)
|
||||
delete(want, render.IncomingInternetID)
|
||||
delete(want, render.OutgoingInternetID)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error(test.Diff(want, have))
|
||||
}
|
||||
|
||||
@@ -367,25 +367,27 @@ var (
|
||||
Nodes: report.Nodes{
|
||||
ClientPodNodeID: report.MakeNodeWith(
|
||||
ClientPodNodeID, map[string]string{
|
||||
kubernetes.PodID: ClientPodID,
|
||||
kubernetes.PodName: "pong-a",
|
||||
kubernetes.Namespace: KubernetesNamespace,
|
||||
kubernetes.ServiceIDs: ServiceID,
|
||||
report.HostNodeID: ClientHostNodeID,
|
||||
kubernetes.PodID: ClientPodID,
|
||||
kubernetes.PodName: "pong-a",
|
||||
kubernetes.Namespace: KubernetesNamespace,
|
||||
report.HostNodeID: ClientHostNodeID,
|
||||
}).
|
||||
WithSets(report.EmptySets.
|
||||
Add(kubernetes.ServiceIDs, report.MakeStringSet(ServiceID))).
|
||||
WithTopology(report.Pod).WithParents(report.EmptySets.
|
||||
Add("host", report.MakeStringSet(ClientHostNodeID)).
|
||||
Add("service", report.MakeStringSet(ServiceID)),
|
||||
),
|
||||
ServerPodNodeID: report.MakeNodeWith(
|
||||
ServerPodNodeID, map[string]string{
|
||||
kubernetes.PodID: ServerPodID,
|
||||
kubernetes.PodName: "pong-b",
|
||||
kubernetes.Namespace: KubernetesNamespace,
|
||||
kubernetes.PodState: "running",
|
||||
kubernetes.ServiceIDs: ServiceID,
|
||||
report.HostNodeID: ServerHostNodeID,
|
||||
kubernetes.PodID: ServerPodID,
|
||||
kubernetes.PodName: "pong-b",
|
||||
kubernetes.Namespace: KubernetesNamespace,
|
||||
kubernetes.PodState: "running",
|
||||
report.HostNodeID: ServerHostNodeID,
|
||||
}).
|
||||
WithSets(report.EmptySets.
|
||||
Add(kubernetes.ServiceIDs, report.MakeStringSet(ServiceID))).
|
||||
WithTopology(report.Pod).WithParents(report.EmptySets.
|
||||
Add("host", report.MakeStringSet(ServerHostNodeID)).
|
||||
Add("service", report.MakeStringSet(ServiceID)),
|
||||
|
||||
Reference in New Issue
Block a user