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
15 )
16
17
18
19 type StatsBaseRequestParams struct {
20 TimeWindow string
21 Namespace string
22 ResourceType string
23 ResourceName string
24 AllNamespaces bool
25 }
26
27
28
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
43
44 type EdgesRequestParams struct {
45 Namespace string
46 ResourceType string
47 AllNamespaces bool
48 }
49
50
51
52 type TopRoutesRequestParams struct {
53 StatsBaseRequestParams
54 ToNamespace string
55 ToType string
56 ToName string
57 LabelSelector string
58 }
59
60
61
62 type GatewayRequestParams struct {
63 RemoteClusterName string
64 GatewayNamespace string
65 TimeWindow string
66 }
67
68
69
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
156
157 func BuildEdgesRequest(p EdgesRequestParams) (*pb.EdgesRequest, error) {
158 namespace := p.Namespace
159
160
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
183
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
253
254
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
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
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