...

Source file src/k8s.io/kubernetes/pkg/controller/garbagecollector/dump.go

Documentation: k8s.io/kubernetes/pkg/controller/garbagecollector

     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  }
   348  

View as plain text