...

Source file src/github.com/linkerd/linkerd2/viz/metrics-api/util/api_utils.go

Documentation: github.com/linkerd/linkerd2/viz/metrics-api/util

     1  package util
     2  
     3  import (
     4  	"errors"
     5  	"time"
     6  
     7  	"github.com/linkerd/linkerd2/pkg/k8s"
     8  	pb "github.com/linkerd/linkerd2/viz/metrics-api/gen/viz"
     9  	corev1 "k8s.io/api/core/v1"
    10  )
    11  
    12  var (
    13  	defaultMetricTimeWindow    = "1m"
    14  	metricTimeWindowLowerBound = time.Second * 15 // the window value needs to equal or larger than that
    15  )
    16  
    17  // StatsBaseRequestParams contains parameters that are used to build requests
    18  // for metrics data.  This includes requests to StatSummary and TopRoutes.
    19  type StatsBaseRequestParams struct {
    20  	TimeWindow    string
    21  	Namespace     string
    22  	ResourceType  string
    23  	ResourceName  string
    24  	AllNamespaces bool
    25  }
    26  
    27  // StatsSummaryRequestParams contains parameters that are used to build
    28  // StatSummary requests.
    29  type StatsSummaryRequestParams struct {
    30  	StatsBaseRequestParams
    31  	ToNamespace   string
    32  	ToType        string
    33  	ToName        string
    34  	FromNamespace string
    35  	FromType      string
    36  	FromName      string
    37  	SkipStats     bool
    38  	TCPStats      bool
    39  	LabelSelector string
    40  }
    41  
    42  // EdgesRequestParams contains parameters that are used to build
    43  // Edges requests.
    44  type EdgesRequestParams struct {
    45  	Namespace     string
    46  	ResourceType  string
    47  	AllNamespaces bool
    48  }
    49  
    50  // TopRoutesRequestParams contains parameters that are used to build TopRoutes
    51  // requests.
    52  type TopRoutesRequestParams struct {
    53  	StatsBaseRequestParams
    54  	ToNamespace   string
    55  	ToType        string
    56  	ToName        string
    57  	LabelSelector string
    58  }
    59  
    60  // GatewayRequestParams contains parameters that are used to build a
    61  // GatewayRequest
    62  type GatewayRequestParams struct {
    63  	RemoteClusterName string
    64  	GatewayNamespace  string
    65  	TimeWindow        string
    66  }
    67  
    68  // BuildStatSummaryRequest builds a Public API StatSummaryRequest from a
    69  // StatsSummaryRequestParams.
    70  func BuildStatSummaryRequest(p StatsSummaryRequestParams) (*pb.StatSummaryRequest, error) {
    71  	window, err := ValidateTimeWindow(p.TimeWindow)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  
    76  	if p.AllNamespaces && p.ResourceName != "" {
    77  		return nil, errors.New("stats for a resource cannot be retrieved by name across all namespaces")
    78  	}
    79  
    80  	targetNamespace := p.Namespace
    81  	if p.AllNamespaces {
    82  		targetNamespace = ""
    83  	} else if p.Namespace == "" {
    84  		targetNamespace = corev1.NamespaceDefault
    85  	}
    86  
    87  	resourceType, err := k8s.CanonicalResourceNameFromFriendlyName(p.ResourceType)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	statRequest := &pb.StatSummaryRequest{
    93  		Selector: &pb.ResourceSelection{
    94  			Resource: &pb.Resource{
    95  				Namespace: targetNamespace,
    96  				Name:      p.ResourceName,
    97  				Type:      resourceType,
    98  			},
    99  			LabelSelector: p.LabelSelector,
   100  		},
   101  		TimeWindow: window,
   102  		SkipStats:  p.SkipStats,
   103  		TcpStats:   p.TCPStats,
   104  	}
   105  
   106  	if p.ToName != "" || p.ToType != "" || p.ToNamespace != "" {
   107  		if p.ToNamespace == "" {
   108  			p.ToNamespace = targetNamespace
   109  		}
   110  		if p.ToType == "" {
   111  			p.ToType = resourceType
   112  		}
   113  
   114  		toType, err := k8s.CanonicalResourceNameFromFriendlyName(p.ToType)
   115  		if err != nil {
   116  			return nil, err
   117  		}
   118  
   119  		toResource := pb.StatSummaryRequest_ToResource{
   120  			ToResource: &pb.Resource{
   121  				Namespace: p.ToNamespace,
   122  				Type:      toType,
   123  				Name:      p.ToName,
   124  			},
   125  		}
   126  		statRequest.Outbound = &toResource
   127  	}
   128  
   129  	if p.FromName != "" || p.FromType != "" || p.FromNamespace != "" {
   130  		if p.FromNamespace == "" {
   131  			p.FromNamespace = targetNamespace
   132  		}
   133  		if p.FromType == "" {
   134  			p.FromType = resourceType
   135  		}
   136  
   137  		fromType, err := validateFromResourceType(p.FromType)
   138  		if err != nil {
   139  			return nil, err
   140  		}
   141  
   142  		fromResource := pb.StatSummaryRequest_FromResource{
   143  			FromResource: &pb.Resource{
   144  				Namespace: p.FromNamespace,
   145  				Type:      fromType,
   146  				Name:      p.FromName,
   147  			},
   148  		}
   149  		statRequest.Outbound = &fromResource
   150  	}
   151  
   152  	return statRequest, nil
   153  }
   154  
   155  // BuildEdgesRequest builds a Public API EdgesRequest from a
   156  // EdgesRequestParams.
   157  func BuildEdgesRequest(p EdgesRequestParams) (*pb.EdgesRequest, error) {
   158  	namespace := p.Namespace
   159  
   160  	// If all namespaces was specified, ignore namespace value.
   161  	if p.AllNamespaces {
   162  		namespace = ""
   163  	}
   164  
   165  	resourceType, err := k8s.CanonicalResourceNameFromFriendlyName(p.ResourceType)
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  
   170  	edgesRequest := &pb.EdgesRequest{
   171  		Selector: &pb.ResourceSelection{
   172  			Resource: &pb.Resource{
   173  				Namespace: namespace,
   174  				Type:      resourceType,
   175  			},
   176  		},
   177  	}
   178  
   179  	return edgesRequest, nil
   180  }
   181  
   182  // BuildTopRoutesRequest builds a Public API TopRoutesRequest from a
   183  // TopRoutesRequestParams.
   184  func BuildTopRoutesRequest(p TopRoutesRequestParams) (*pb.TopRoutesRequest, error) {
   185  	window := defaultMetricTimeWindow
   186  	if p.TimeWindow != "" {
   187  		_, err := time.ParseDuration(p.TimeWindow)
   188  		if err != nil {
   189  			return nil, err
   190  		}
   191  		window = p.TimeWindow
   192  	}
   193  
   194  	if p.AllNamespaces && p.ResourceName != "" {
   195  		return nil, errors.New("routes for a resource cannot be retrieved by name across all namespaces")
   196  	}
   197  
   198  	targetNamespace := p.Namespace
   199  	if p.AllNamespaces {
   200  		targetNamespace = ""
   201  	} else if p.Namespace == "" {
   202  		targetNamespace = corev1.NamespaceDefault
   203  	}
   204  
   205  	resourceType, err := k8s.CanonicalResourceNameFromFriendlyName(p.ResourceType)
   206  	if err != nil {
   207  		return nil, err
   208  	}
   209  
   210  	topRoutesRequest := &pb.TopRoutesRequest{
   211  		Selector: &pb.ResourceSelection{
   212  			Resource: &pb.Resource{
   213  				Namespace: targetNamespace,
   214  				Name:      p.ResourceName,
   215  				Type:      resourceType,
   216  			},
   217  			LabelSelector: p.LabelSelector,
   218  		},
   219  		TimeWindow: window,
   220  	}
   221  
   222  	if p.ToName != "" || p.ToType != "" || p.ToNamespace != "" {
   223  		if p.ToNamespace == "" {
   224  			p.ToNamespace = targetNamespace
   225  		}
   226  		if p.ToType == "" {
   227  			p.ToType = resourceType
   228  		}
   229  
   230  		toType, err := k8s.CanonicalResourceNameFromFriendlyName(p.ToType)
   231  		if err != nil {
   232  			return nil, err
   233  		}
   234  
   235  		toResource := pb.TopRoutesRequest_ToResource{
   236  			ToResource: &pb.Resource{
   237  				Namespace: p.ToNamespace,
   238  				Type:      toType,
   239  				Name:      p.ToName,
   240  			},
   241  		}
   242  		topRoutesRequest.Outbound = &toResource
   243  	} else {
   244  		topRoutesRequest.Outbound = &pb.TopRoutesRequest_None{
   245  			None: &pb.Empty{},
   246  		}
   247  	}
   248  
   249  	return topRoutesRequest, nil
   250  }
   251  
   252  // ValidateTimeWindow validates the given duration as a time window. If an empty
   253  // string is provided, the default time window is returned, otherwise, if
   254  // validation is successful, the original time window is returned.
   255  func ValidateTimeWindow(window string) (string, error) {
   256  	if window == "" {
   257  		return defaultMetricTimeWindow, nil
   258  	}
   259  	w, err := time.ParseDuration(window)
   260  	if err != nil {
   261  		return "", err
   262  	}
   263  
   264  	if w < metricTimeWindowLowerBound {
   265  		return "", errors.New("metrics time window needs to be at least 15s")
   266  	}
   267  
   268  	return window, nil
   269  }
   270  
   271  // An authority can only receive traffic, not send it, so it can't be a --from
   272  func validateFromResourceType(resourceType string) (string, error) {
   273  	name, err := k8s.CanonicalResourceNameFromFriendlyName(resourceType)
   274  	if err != nil {
   275  		return "", err
   276  	}
   277  	if name == k8s.Authority {
   278  		return "", errors.New("cannot query traffic --from an authority")
   279  	}
   280  	return name, nil
   281  }
   282  
   283  // K8sPodToPublicPod converts a Kubernetes Pod to a Public API Pod
   284  func K8sPodToPublicPod(pod corev1.Pod, ownerKind string, ownerName string) *pb.Pod {
   285  	status := string(pod.Status.Phase)
   286  	if pod.DeletionTimestamp != nil {
   287  		status = "Terminating"
   288  	}
   289  
   290  	if pod.Status.Reason == "Evicted" {
   291  		status = "Evicted"
   292  	}
   293  
   294  	controllerComponent := pod.Labels[k8s.ControllerComponentLabel]
   295  	controllerNS := pod.Labels[k8s.ControllerNSLabel]
   296  
   297  	item := &pb.Pod{
   298  		Name:                pod.Namespace + "/" + pod.Name,
   299  		Status:              status,
   300  		PodIP:               pod.Status.PodIP,
   301  		ControllerNamespace: controllerNS,
   302  		ControlPlane:        controllerComponent != "",
   303  		ProxyReady:          k8s.GetProxyReady(pod),
   304  		ProxyVersion:        k8s.GetProxyVersion(pod),
   305  		ResourceVersion:     pod.ResourceVersion,
   306  	}
   307  
   308  	namespacedOwnerName := pod.Namespace + "/" + ownerName
   309  
   310  	switch ownerKind {
   311  	case k8s.Deployment:
   312  		item.Owner = &pb.Pod_Deployment{Deployment: namespacedOwnerName}
   313  	case k8s.DaemonSet:
   314  		item.Owner = &pb.Pod_DaemonSet{DaemonSet: namespacedOwnerName}
   315  	case k8s.Job:
   316  		item.Owner = &pb.Pod_Job{Job: namespacedOwnerName}
   317  	case k8s.ReplicaSet:
   318  		item.Owner = &pb.Pod_ReplicaSet{ReplicaSet: namespacedOwnerName}
   319  	case k8s.ReplicationController:
   320  		item.Owner = &pb.Pod_ReplicationController{ReplicationController: namespacedOwnerName}
   321  	case k8s.StatefulSet:
   322  		item.Owner = &pb.Pod_StatefulSet{StatefulSet: namespacedOwnerName}
   323  	}
   324  
   325  	return item
   326  }
   327  

View as plain text