k8s.io/kubernetes@v1.29.3/pkg/controller/garbagecollector/dump.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 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 // these place metadata in the correct location, but don't conform to any normal attribute for rendering 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 // NewDOTVertex creates a new dotVertex. 140 func NewDOTVertex(node *node) *dotVertex { 141 gv, err := schema.ParseGroupVersion(node.identity.APIVersion) 142 if err != nil { 143 // this indicates a bad data serialization that should be prevented during storage of the API 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 // NewMissingdotVertex creates a new dotVertex. 158 func NewMissingdotVertex(ownerRef metav1.OwnerReference) *dotVertex { 159 gv, err := schema.ParseGroupVersion(ownerRef.APIVersion) 160 if err != nil { 161 // this indicates a bad data serialization that should be prevented during storage of the API 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 // add the vertices first, then edges. That avoids having to deal with missing refs. 196 for _, node := range uidToNode { 197 // skip adding objects that don't have owner references and aren't referred to. 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 // build the set of nodes to inspect first, then use the normal construction on the subset 241 for i := 0; i < len(uidsToCheck); i++ { 242 uid := uidsToCheck[i] 243 // if we've already been observed, there was a bug, but skip it so we don't loop forever 244 if _, ok := interestingNodes[uid]; ok { 245 continue 246 } 247 node, ok := uidToNode[uid] 248 // if there is no node for the UID, skip over it. We may add it to the list multiple times 249 // but we won't loop forever and hopefully the condition doesn't happen very often 250 if !ok { 251 continue 252 } 253 254 interestingNodes[node.identity.UID] = node 255 256 for _, ownerRef := range node.owners { 257 // if we've already inspected this UID, don't add it to be inspected again 258 if _, ok := interestingNodes[ownerRef.UID]; ok { 259 continue 260 } 261 uidsToCheck = append(uidsToCheck, ownerRef.UID) 262 } 263 for dependent := range node.dependents { 264 // if we've already inspected this UID, don't add it to be inspected again 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 // NewDebugHandler creates a new debugHTTPHandler. 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 }