From 0178babb0e02434cdf1a238fc16b6a415c855f65 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Wed, 4 May 2016 17:02:48 +0100 Subject: [PATCH] Index services by UID, and refactor out common k8s metadata handling --- probe/kubernetes/pod.go | 42 +++------------------- probe/kubernetes/reporter.go | 17 +++++---- probe/kubernetes/reporter_test.go | 8 +++-- probe/kubernetes/service.go | 32 ++++------------- render/pod.go | 60 ++++++++++++++----------------- report/id.go | 40 ++++++++++----------- test/fixture/report_fixture.go | 7 ++-- 7 files changed, 76 insertions(+), 130 deletions(-) diff --git a/probe/kubernetes/pod.go b/probe/kubernetes/pod.go index b5a116515..42a9c3df1 100644 --- a/probe/kubernetes/pod.go +++ b/probe/kubernetes/pod.go @@ -1,12 +1,8 @@ package kubernetes import ( - "strings" - "time" - "github.com/weaveworks/scope/report" "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/labels" ) // These constants are keys used in node metadata @@ -24,26 +20,22 @@ const ( // Pod represents a Kubernetes pod type Pod interface { - UID() string - ID() string - Name() string - Namespace() string - Created() string + Meta AddServiceID(id string) - Labels() labels.Labels NodeName() string GetNode(probeID string) report.Node } type pod struct { *api.Pod + Meta serviceIDs report.StringSet Node *api.Node } // NewPod creates a new Pod func NewPod(p *api.Pod) Pod { - return &pod{Pod: p, serviceIDs: report.MakeStringSet()} + return &pod{Pod: p, Meta: meta{p.ObjectMeta}, serviceIDs: report.MakeStringSet()} } func (p *pod) UID() string { @@ -51,27 +43,7 @@ func (p *pod) UID() string { if hash, ok := p.ObjectMeta.Annotations["kubernetes.io/config.hash"]; ok { return hash } - return string(p.ObjectMeta.UID) -} - -func (p *pod) ID() string { - return p.ObjectMeta.Namespace + "/" + p.ObjectMeta.Name -} - -func (p *pod) Name() string { - return p.ObjectMeta.Name -} - -func (p *pod) Namespace() string { - return p.ObjectMeta.Namespace -} - -func (p *pod) Created() string { - return p.ObjectMeta.CreationTimestamp.Format(time.RFC822) -} - -func (p *pod) Labels() labels.Labels { - return labels.Set(p.ObjectMeta.Labels) + return p.Meta.UID() } func (p *pod) AddServiceID(id string) { @@ -97,12 +69,8 @@ func (p *pod) GetNode(probeID string) report.Node { report.ControlProbeID: probeID, }).WithSets(report.EmptySets.Add(ServiceIDs, p.serviceIDs)) for _, serviceID := range p.serviceIDs { - segments := strings.SplitN(serviceID, "/", 2) - if len(segments) != 2 { - continue - } n = n.WithParents(report.EmptySets. - Add(report.Service, report.MakeStringSet(report.MakeServiceNodeID(p.Namespace(), segments[1]))), + Add(report.Service, report.MakeStringSet(report.MakeServiceNodeID(serviceID))), ) } n = n.AddTable(PodLabelPrefix, p.ObjectMeta.Labels) diff --git a/probe/kubernetes/reporter.go b/probe/kubernetes/reporter.go index 6a124c315..2fabdfdcb 100644 --- a/probe/kubernetes/reporter.go +++ b/probe/kubernetes/reporter.go @@ -192,7 +192,14 @@ func (r *Reporter) podTopology(services []Service) (report.Topology, error) { pods = report.MakeTopology(). WithMetadataTemplates(PodMetadataTemplates). WithTableTemplates(PodTableTemplates) - selectors = map[string]labels.Selector{} + selectors = []func(Pod){} + match = func(selector labels.Selector, f func(Pod, string), id string) func(Pod) { + return func(p Pod) { + if selector.Matches(p.Labels()) { + f(p, id) + } + } + } ) pods.Controls.AddControl(report.Control{ ID: GetLogs, @@ -207,7 +214,7 @@ func (r *Reporter) podTopology(services []Service) (report.Topology, error) { Rank: 1, }) for _, service := range services { - selectors[service.ID()] = service.Selector() + selectors = append(selectors, match(service.Selector(), Pod.AddServiceID, service.UID())) } thisNodeName, err := GetNodeName(r) @@ -218,10 +225,8 @@ func (r *Reporter) podTopology(services []Service) (report.Topology, error) { if p.NodeName() != thisNodeName { return nil } - for serviceID, selector := range selectors { - if selector.Matches(p.Labels()) { - p.AddServiceID(serviceID) - } + for _, selector := range selectors { + selector(p) } pods = pods.AddNode(p.GetNode(r.probeID)) return nil diff --git a/probe/kubernetes/reporter_test.go b/probe/kubernetes/reporter_test.go index b7b65475d..985b9304a 100644 --- a/probe/kubernetes/reporter_test.go +++ b/probe/kubernetes/reporter_test.go @@ -22,6 +22,7 @@ var ( nodeName = "nodename" pod1UID = "a1b2c3d4e5" pod2UID = "f6g7h8i9j0" + serviceUID = "service1234" podTypeMeta = unversioned.TypeMeta{ Kind: "Pod", APIVersion: "v1", @@ -73,6 +74,7 @@ var ( }, ObjectMeta: api.ObjectMeta{ Name: "pongservice", + UID: types.UID(serviceUID), Namespace: "ping", CreationTimestamp: unversioned.Now(), }, @@ -166,7 +168,7 @@ func TestReporter(t *testing.T) { pod1ID := report.MakePodNodeID(pod1UID) pod2ID := report.MakePodNodeID(pod2UID) - serviceID := report.MakeServiceNodeID("ping", "pongservice") + serviceID := report.MakeServiceNodeID(serviceUID) rpt, _ := kubernetes.NewReporter(newMockClient(), nil, "", nil).Report() // Reporter should have added the following pods @@ -182,7 +184,7 @@ func TestReporter(t *testing.T) { kubernetes.Namespace: "ping", kubernetes.PodCreated: pod1.Created(), }, map[string]report.StringSet{ - kubernetes.ServiceIDs: report.MakeStringSet("ping/pongservice"), + kubernetes.ServiceIDs: report.MakeStringSet(serviceUID), }}, {pod2ID, serviceID, map[string]string{ kubernetes.PodID: "ping/pong-b", @@ -190,7 +192,7 @@ func TestReporter(t *testing.T) { kubernetes.Namespace: "ping", kubernetes.PodCreated: pod1.Created(), }, map[string]report.StringSet{ - kubernetes.ServiceIDs: report.MakeStringSet("ping/pongservice"), + kubernetes.ServiceIDs: report.MakeStringSet(serviceUID), }}, } { node, ok := rpt.Pod.Nodes[pod.id] diff --git a/probe/kubernetes/service.go b/probe/kubernetes/service.go index c5ca38a3f..c47c68d23 100644 --- a/probe/kubernetes/service.go +++ b/probe/kubernetes/service.go @@ -1,8 +1,6 @@ package kubernetes import ( - "time" - "github.com/weaveworks/scope/report" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/labels" @@ -20,37 +18,19 @@ const ( // Service represents a Kubernetes service type Service interface { - UID() string - ID() string - Name() string - Namespace() string + Meta GetNode() report.Node Selector() labels.Selector } type service struct { *api.Service + Meta } // NewService creates a new Service func NewService(s *api.Service) Service { - return &service{Service: s} -} - -func (s *service) UID() string { - return string(s.ObjectMeta.UID) -} - -func (s *service) ID() string { - return s.ObjectMeta.Namespace + "/" + s.ObjectMeta.Name -} - -func (s *service) Name() string { - return s.ObjectMeta.Name -} - -func (s *service) Namespace() string { - return s.ObjectMeta.Namespace + return &service{Service: s, Meta: meta{s.ObjectMeta}} } func (s *service) Selector() labels.Selector { @@ -64,7 +44,7 @@ func (s *service) GetNode() report.Node { latest := map[string]string{ ServiceID: s.ID(), ServiceName: s.Name(), - ServiceCreated: s.ObjectMeta.CreationTimestamp.Format(time.RFC822), + ServiceCreated: s.Created(), Namespace: s.Namespace(), ServiceIP: s.Spec.ClusterIP, } @@ -72,8 +52,8 @@ func (s *service) GetNode() report.Node { latest[ServicePublicIP] = s.Spec.LoadBalancerIP } return report.MakeNodeWith( - report.MakeServiceNodeID(s.Namespace(), s.Name()), + report.MakeServiceNodeID(s.UID()), latest, ). - AddTable(ServiceLabelPrefix, s.Labels) + AddTable(ServiceLabelPrefix, s.ObjectMeta.Labels) } diff --git a/render/pod.go b/render/pod.go index 7d3cf0948..f8b048961 100644 --- a/render/pod.go +++ b/render/pod.go @@ -95,40 +95,34 @@ func MapContainer2Pod(n report.Node, _ report.Networks) report.Nodes { return report.Nodes{id: node} } -// MapPod2Service maps pod Nodes to service Nodes. -// -// If this function is given a node without a kubernetes_pod_id -// (including other pseudo nodes), it will produce an "Uncontained" -// pseudo node. -// -// Otherwise, this function will produce a node with the correct ID -// format for a container, but without any Major or Minor labels. -// It does not have enough info to do that, and the resulting graph -// must be merged with a pod graph to get that info. -func MapPod2Service(pod report.Node, _ report.Networks) report.Nodes { - // Propagate all pseudo nodes - if pod.Topology == Pseudo { - return report.Nodes{pod.ID: pod} - } +// The various ways of grouping pods +var ( + MapPod2Service = MapPod2Grouping(report.Service, kubernetes.ServiceIDs, report.MakeServiceNodeID) +) - // Otherwise, if some some reason the pod doesn't have a service_ids (maybe - // slightly out of sync reports, or its not in a service), just drop it - namespace, ok := pod.Latest.Lookup(kubernetes.Namespace) - if !ok { - return report.Nodes{} - } - serviceIDs, ok := pod.Sets.Lookup(kubernetes.ServiceIDs) - if !ok { - return report.Nodes{} - } +// MapPod2Grouping maps pod Nodes to some kubernetes grouping. +func MapPod2Grouping(topology, setKey string, idMaker func(uid string) string) func(pod report.Node, _ report.Networks) report.Nodes { + return func(pod report.Node, _ report.Networks) report.Nodes { + // Propagate all pseudo nodes + if pod.Topology == Pseudo { + return report.Nodes{pod.ID: pod} + } - result := report.Nodes{} - for _, serviceID := range serviceIDs { - serviceName := strings.TrimPrefix(serviceID, namespace+"/") - id := report.MakeServiceNodeID(namespace, serviceName) - node := NewDerivedNode(id, pod).WithTopology(report.Service) - node.Counters = node.Counters.Add(pod.Topology, 1) - result[id] = node + // Otherwise, if some some reason the pod doesn't have any of these ids + // (maybe slightly out of sync reports, or its not in this group), just + // drop it + groupIDs, ok := pod.Sets.Lookup(setKey) + if !ok { + return report.Nodes{} + } + + result := report.Nodes{} + for _, groupID := range groupIDs { + id := idMaker(groupID) + node := NewDerivedNode(id, pod).WithTopology(topology) + node.Counters = node.Counters.Add(pod.Topology, 1) + result[id] = node + } + return result } - return result } diff --git a/report/id.go b/report/id.go index 9152286dd..ae4ebfc13 100644 --- a/report/id.go +++ b/report/id.go @@ -96,32 +96,28 @@ func MakeProcessNodeID(hostID, pid string) string { return hostID + ScopeDelim + pid } -// MakeHostNodeID produces a host node ID from its composite parts. -func MakeHostNodeID(hostID string) string { - // hostIDs come from the probe and are presumed to be globally-unique. - // But, suffix something to elicit failures if we try to use probe host - // IDs directly as node IDs in the host topology. - return hostID + ScopeDelim + "" -} +var ( + // MakeHostNodeID produces a host node ID from its composite parts. + MakeHostNodeID = singleComponentID("host") -// MakeContainerNodeID produces a container node ID from its composite parts. -func MakeContainerNodeID(containerID string) string { - return containerID + ScopeDelim + "" -} + // MakeContainerNodeID produces a container node ID from its composite parts. + MakeContainerNodeID = singleComponentID("container") -// MakeContainerImageNodeID produces a container image node ID from its composite parts. -func MakeContainerImageNodeID(containerImageID string) string { - return containerImageID + ScopeDelim + "" -} + // MakeContainerImageNodeID produces a container image node ID from its composite parts. + MakeContainerImageNodeID = singleComponentID("container_image") -// MakePodNodeID produces a pod node ID from its composite parts. -func MakePodNodeID(uid string) string { - return uid + ScopeDelim + "" -} + // MakePodNodeID produces a pod node ID from its composite parts. + MakePodNodeID = singleComponentID("pod") -// MakeServiceNodeID produces a service node ID from its composite parts. -func MakeServiceNodeID(namespaceID, serviceID string) string { - return namespaceID + ScopeDelim + serviceID + // MakeServiceNodeID produces a service node ID from its composite parts. + MakeServiceNodeID = singleComponentID("service") +) + +// singleComponentID makes a +func singleComponentID(tag string) func(string) string { + return func(id string) string { + return id + ScopeDelim + "<" + tag + ">" + } } // MakeOverlayNodeID produces an overlay topology node ID from a router peer's diff --git a/test/fixture/report_fixture.go b/test/fixture/report_fixture.go index 84b9d358d..91ef83886 100644 --- a/test/fixture/report_fixture.go +++ b/test/fixture/report_fixture.go @@ -97,7 +97,8 @@ var ( ClientPodNodeID = report.MakePodNodeID(ClientPodUID) ServerPodNodeID = report.MakePodNodeID(ServerPodUID) ServiceID = "ping/pongservice" - ServiceNodeID = report.MakeServiceNodeID(KubernetesNamespace, "pongservice") + ServiceUID = "service1234" + ServiceNodeID = report.MakeServiceNodeID(ServiceUID) ClientProcess1CPUMetric = report.MakeMetric().Add(Now, 0.01).WithFirst(Now.Add(-1 * time.Second)) ClientProcess1MemoryMetric = report.MakeMetric().Add(Now, 0.02).WithFirst(Now.Add(-2 * time.Second)) @@ -373,7 +374,7 @@ var ( report.HostNodeID: ClientHostNodeID, }). WithSets(report.EmptySets. - Add(kubernetes.ServiceIDs, report.MakeStringSet(ServiceID))). + Add(kubernetes.ServiceIDs, report.MakeStringSet(ServiceUID))). WithTopology(report.Pod).WithParents(report.EmptySets. Add("host", report.MakeStringSet(ClientHostNodeID)). Add("service", report.MakeStringSet(ServiceID)), @@ -387,7 +388,7 @@ var ( report.HostNodeID: ServerHostNodeID, }). WithSets(report.EmptySets. - Add(kubernetes.ServiceIDs, report.MakeStringSet(ServiceID))). + Add(kubernetes.ServiceIDs, report.MakeStringSet(ServiceUID))). WithTopology(report.Pod).WithParents(report.EmptySets. Add("host", report.MakeStringSet(ServerHostNodeID)). Add("service", report.MakeStringSet(ServiceID)),