1
16
17 package garbagecollector
18
19 import (
20 "bytes"
21 "fmt"
22 "io"
23 "net/http"
24 "sort"
25 "strings"
26
27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 "k8s.io/apimachinery/pkg/runtime/schema"
29 "k8s.io/apimachinery/pkg/types"
30 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
31 )
32
33 type dotVertex struct {
34 uid types.UID
35 gvk schema.GroupVersionKind
36 namespace string
37 name string
38 missingFromGraph bool
39 beingDeleted bool
40 deletingDependents bool
41 virtual bool
42 }
43
44 func (v *dotVertex) MarshalDOT(w io.Writer) error {
45 attrs := v.Attributes()
46 if _, err := fmt.Fprintf(w, " %q [\n", v.uid); err != nil {
47 return err
48 }
49 for _, a := range attrs {
50 if _, err := fmt.Fprintf(w, " %s=%q\n", a.Key, a.Value); err != nil {
51 return err
52 }
53 }
54 if _, err := fmt.Fprintf(w, " ];\n"); err != nil {
55 return err
56 }
57 return nil
58 }
59
60 func (v *dotVertex) String() string {
61 kind := v.gvk.Kind + "." + v.gvk.Version
62 if len(v.gvk.Group) > 0 {
63 kind = kind + "." + v.gvk.Group
64 }
65 missing := ""
66 if v.missingFromGraph {
67 missing = "(missing)"
68 }
69 deleting := ""
70 if v.beingDeleted {
71 deleting = "(deleting)"
72 }
73 deletingDependents := ""
74 if v.deletingDependents {
75 deleting = "(deletingDependents)"
76 }
77 virtual := ""
78 if v.virtual {
79 virtual = "(virtual)"
80 }
81 return fmt.Sprintf(`%s/%s[%s]-%v%s%s%s%s`, kind, v.name, v.namespace, v.uid, missing, deleting, deletingDependents, virtual)
82 }
83
84 type attribute struct {
85 Key string
86 Value string
87 }
88
89 func (v *dotVertex) Attributes() []attribute {
90 kubectlString := v.gvk.Kind + "." + v.gvk.Version
91 if len(v.gvk.Group) > 0 {
92 kubectlString = kubectlString + "." + v.gvk.Group
93 }
94 kubectlString = kubectlString + "/" + v.name
95
96 label := fmt.Sprintf(`uid=%v
97 namespace=%v
98 %v
99 `,
100 v.uid,
101 v.namespace,
102 kubectlString,
103 )
104
105 conditionStrings := []string{}
106 if v.beingDeleted {
107 conditionStrings = append(conditionStrings, "beingDeleted")
108 }
109 if v.deletingDependents {
110 conditionStrings = append(conditionStrings, "deletingDependents")
111 }
112 if v.virtual {
113 conditionStrings = append(conditionStrings, "virtual")
114 }
115 if v.missingFromGraph {
116 conditionStrings = append(conditionStrings, "missingFromGraph")
117 }
118 conditionString := strings.Join(conditionStrings, ",")
119 if len(conditionString) > 0 {
120 label = label + conditionString + "\n"
121 }
122
123 return []attribute{
124 {Key: "label", Value: label},
125
126 {Key: "group", Value: v.gvk.Group},
127 {Key: "version", Value: v.gvk.Version},
128 {Key: "kind", Value: v.gvk.Kind},
129 {Key: "namespace", Value: v.namespace},
130 {Key: "name", Value: v.name},
131 {Key: "uid", Value: string(v.uid)},
132 {Key: "missing", Value: fmt.Sprintf(`%v`, v.missingFromGraph)},
133 {Key: "beingDeleted", Value: fmt.Sprintf(`%v`, v.beingDeleted)},
134 {Key: "deletingDependents", Value: fmt.Sprintf(`%v`, v.deletingDependents)},
135 {Key: "virtual", Value: fmt.Sprintf(`%v`, v.virtual)},
136 }
137 }
138
139
140 func NewDOTVertex(node *node) *dotVertex {
141 gv, err := schema.ParseGroupVersion(node.identity.APIVersion)
142 if err != nil {
143
144 utilruntime.HandleError(err)
145 }
146 return &dotVertex{
147 uid: node.identity.UID,
148 gvk: gv.WithKind(node.identity.Kind),
149 namespace: node.identity.Namespace,
150 name: node.identity.Name,
151 beingDeleted: node.beingDeleted,
152 deletingDependents: node.deletingDependents,
153 virtual: node.virtual,
154 }
155 }
156
157
158 func NewMissingdotVertex(ownerRef metav1.OwnerReference) *dotVertex {
159 gv, err := schema.ParseGroupVersion(ownerRef.APIVersion)
160 if err != nil {
161
162 utilruntime.HandleError(err)
163 }
164 return &dotVertex{
165 uid: ownerRef.UID,
166 gvk: gv.WithKind(ownerRef.Kind),
167 name: ownerRef.Name,
168 missingFromGraph: true,
169 }
170 }
171
172 func (m *concurrentUIDToNode) ToDOTNodesAndEdges() ([]*dotVertex, []dotEdge) {
173 m.uidToNodeLock.Lock()
174 defer m.uidToNodeLock.Unlock()
175
176 return toDOTNodesAndEdges(m.uidToNode)
177 }
178
179 type dotEdge struct {
180 F types.UID
181 T types.UID
182 }
183
184 func (e dotEdge) MarshalDOT(w io.Writer) error {
185 _, err := fmt.Fprintf(w, " %q -> %q;\n", e.F, e.T)
186 return err
187 }
188
189 func toDOTNodesAndEdges(uidToNode map[types.UID]*node) ([]*dotVertex, []dotEdge) {
190 nodes := []*dotVertex{}
191 edges := []dotEdge{}
192
193 uidToVertex := map[types.UID]*dotVertex{}
194
195
196 for _, node := range uidToNode {
197
198 if len(node.dependents) == 0 && len(node.owners) == 0 {
199 continue
200 }
201 vertex := NewDOTVertex(node)
202 uidToVertex[node.identity.UID] = vertex
203 nodes = append(nodes, vertex)
204 }
205 for _, node := range uidToNode {
206 currVertex := uidToVertex[node.identity.UID]
207 for _, ownerRef := range node.owners {
208 currOwnerVertex, ok := uidToVertex[ownerRef.UID]
209 if !ok {
210 currOwnerVertex = NewMissingdotVertex(ownerRef)
211 uidToVertex[node.identity.UID] = currOwnerVertex
212 nodes = append(nodes, currOwnerVertex)
213 }
214 edges = append(edges, dotEdge{F: currVertex.uid, T: currOwnerVertex.uid})
215 }
216 }
217
218 sort.SliceStable(nodes, func(i, j int) bool { return nodes[i].uid < nodes[j].uid })
219 sort.SliceStable(edges, func(i, j int) bool {
220 if edges[i].F != edges[j].F {
221 return edges[i].F < edges[j].F
222 }
223 return edges[i].T < edges[j].T
224 })
225
226 return nodes, edges
227 }
228
229 func (m *concurrentUIDToNode) ToDOTNodesAndEdgesForObj(uids ...types.UID) ([]*dotVertex, []dotEdge) {
230 m.uidToNodeLock.Lock()
231 defer m.uidToNodeLock.Unlock()
232
233 return toDOTNodesAndEdgesForObj(m.uidToNode, uids...)
234 }
235
236 func toDOTNodesAndEdgesForObj(uidToNode map[types.UID]*node, uids ...types.UID) ([]*dotVertex, []dotEdge) {
237 uidsToCheck := append([]types.UID{}, uids...)
238 interestingNodes := map[types.UID]*node{}
239
240
241 for i := 0; i < len(uidsToCheck); i++ {
242 uid := uidsToCheck[i]
243
244 if _, ok := interestingNodes[uid]; ok {
245 continue
246 }
247 node, ok := uidToNode[uid]
248
249
250 if !ok {
251 continue
252 }
253
254 interestingNodes[node.identity.UID] = node
255
256 for _, ownerRef := range node.owners {
257
258 if _, ok := interestingNodes[ownerRef.UID]; ok {
259 continue
260 }
261 uidsToCheck = append(uidsToCheck, ownerRef.UID)
262 }
263 for dependent := range node.dependents {
264
265 if _, ok := interestingNodes[dependent.identity.UID]; ok {
266 continue
267 }
268 uidsToCheck = append(uidsToCheck, dependent.identity.UID)
269 }
270 }
271
272 return toDOTNodesAndEdges(interestingNodes)
273 }
274
275
276 func NewDebugHandler(controller *GarbageCollector) http.Handler {
277 return &debugHTTPHandler{controller: controller}
278 }
279
280 type debugHTTPHandler struct {
281 controller *GarbageCollector
282 }
283
284 func marshalDOT(w io.Writer, nodes []*dotVertex, edges []dotEdge) error {
285 if _, err := w.Write([]byte("strict digraph full {\n")); err != nil {
286 return err
287 }
288 if len(nodes) > 0 {
289 if _, err := w.Write([]byte(" // Node definitions.\n")); err != nil {
290 return err
291 }
292 for _, node := range nodes {
293 if err := node.MarshalDOT(w); err != nil {
294 return err
295 }
296 }
297 }
298 if len(edges) > 0 {
299 if _, err := w.Write([]byte(" // Edge definitions.\n")); err != nil {
300 return err
301 }
302 for _, edge := range edges {
303 if err := edge.MarshalDOT(w); err != nil {
304 return err
305 }
306 }
307 }
308 if _, err := w.Write([]byte("}\n")); err != nil {
309 return err
310 }
311 return nil
312 }
313
314 func (h *debugHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
315 if req.URL.Path != "/graph" {
316 http.Error(w, "", http.StatusNotFound)
317 return
318 }
319
320 var nodes []*dotVertex
321 var edges []dotEdge
322 if uidStrings := req.URL.Query()["uid"]; len(uidStrings) > 0 {
323 uids := []types.UID{}
324 for _, uidString := range uidStrings {
325 uids = append(uids, types.UID(uidString))
326 }
327 nodes, edges = h.controller.dependencyGraphBuilder.uidToNode.ToDOTNodesAndEdgesForObj(uids...)
328
329 } else {
330 nodes, edges = h.controller.dependencyGraphBuilder.uidToNode.ToDOTNodesAndEdges()
331 }
332
333 b := bytes.NewBuffer(nil)
334 if err := marshalDOT(b, nodes, edges); err != nil {
335 http.Error(w, err.Error(), http.StatusInternalServerError)
336 return
337 }
338
339 w.Header().Set("Content-Type", "text/vnd.graphviz")
340 w.Header().Set("X-Content-Type-Options", "nosniff")
341 w.Write(b.Bytes())
342 w.WriteHeader(http.StatusOK)
343 }
344
345 func (gc *GarbageCollector) DebuggingHandler() http.Handler {
346 return NewDebugHandler(gc)
347 }
348
View as plain text