github.com/kiali/kiali@v1.84.0/graph/options.go (about) 1 package graph 2 3 // Options.go holds the option settings for a single graph request. 4 5 import ( 6 "context" 7 "fmt" 8 net_http "net/http" 9 "net/url" 10 "strconv" 11 "strings" 12 "time" 13 14 "github.com/gorilla/mux" 15 "github.com/prometheus/common/model" 16 "k8s.io/client-go/tools/clientcmd/api" 17 18 "github.com/kiali/kiali/business" 19 "github.com/kiali/kiali/business/authentication" 20 "github.com/kiali/kiali/config" 21 "github.com/kiali/kiali/log" 22 ) 23 24 // The supported vendors 25 const ( 26 VendorCytoscape string = "cytoscape" 27 VendorIstio string = "istio" 28 defaultConfigVendor string = VendorCytoscape 29 defaultTelemetryVendor string = VendorIstio 30 ) 31 32 const ( 33 BoxByApp string = "app" 34 BoxByCluster string = "cluster" 35 BoxByNamespace string = "namespace" 36 BoxByNone string = "none" 37 RateNone string = "none" 38 RateReceived string = "received" // tcp bytes received, grpc response messages, etc 39 RateRequests string = "requests" // request count 40 RateSent string = "sent" // tcp bytes sent, grpc request messages, etc 41 RateTotal string = "total" // Sent+Received 42 defaultBoxBy string = BoxByNone 43 defaultDuration string = "10m" 44 defaultGraphType string = GraphTypeWorkload 45 defaultIncludeIdleEdges bool = false 46 defaultInjectServiceNodes bool = false 47 defaultRateGrpc string = RateRequests 48 defaultRateHttp string = RateRequests 49 defaultRateTcp string = RateSent 50 ) 51 52 const ( 53 graphKindNamespace string = "namespace" 54 graphKindNode string = "node" 55 ) 56 57 // NodeOptions are those that apply only to node-detail graphs 58 type NodeOptions struct { 59 Aggregate string 60 AggregateValue string 61 App string 62 Cluster string 63 Namespace string 64 Service string 65 Version string 66 Workload string 67 } 68 69 // CommonOptions are those supplied to Telemetry and Config Vendors 70 type CommonOptions struct { 71 Duration time.Duration 72 GraphType string 73 Params url.Values // make available the raw query params for vendor-specific handling 74 QueryTime int64 // unix time in seconds 75 } 76 77 // ConfigOptions are those supplied to Config Vendors 78 type ConfigOptions struct { 79 BoxBy string 80 CommonOptions 81 } 82 83 type RequestedAppenders struct { 84 All bool 85 AppenderNames []string 86 } 87 88 type RequestedRates struct { 89 Grpc string 90 Http string 91 Tcp string 92 } 93 94 // ClusterSensitiveKey is the recommended [string] type for maps keying on a cluster-sensitive name 95 type ClusterSensitiveKey = string 96 97 // GetClusterSensitiveKey returns a valid key for maps using a ClusterSensitiveKey 98 func GetClusterSensitiveKey(cluster, name string) ClusterSensitiveKey { 99 return fmt.Sprintf("%s:%s", cluster, name) 100 } 101 102 type AccessibleNamespace struct { 103 Cluster string 104 CreationTimestamp time.Time 105 Name string 106 } 107 108 // AccessibleNamepaces is a map with Key: ClusterSensitive namespace Key, Value: *AccessibleNamespace 109 type AccessibleNamespaces map[ClusterSensitiveKey]*AccessibleNamespace 110 111 // TelemetryOptions are those supplied to Telemetry Vendors 112 type TelemetryOptions struct { 113 AccessibleNamespaces AccessibleNamespaces 114 Appenders RequestedAppenders // requested appenders, nil if param not supplied 115 IncludeIdleEdges bool // include edges with request rates of 0 116 InjectServiceNodes bool // inject destination service nodes between source and destination nodes. 117 Namespaces NamespaceInfoMap 118 Rates RequestedRates 119 CommonOptions 120 NodeOptions 121 } 122 123 // Options comprises all available options 124 type Options struct { 125 ConfigVendor string 126 TelemetryVendor string 127 ConfigOptions 128 TelemetryOptions 129 } 130 131 func NewOptions(r *net_http.Request) Options { 132 // path variables (0 or more will be set) 133 vars := mux.Vars(r) 134 aggregate := vars["aggregate"] 135 aggregateValue := vars["aggregateValue"] 136 app := vars["app"] 137 namespace := vars["namespace"] 138 service := vars["service"] 139 version := vars["version"] 140 workload := vars["workload"] 141 142 // query params 143 params := r.URL.Query() 144 var duration model.Duration 145 var includeIdleEdges bool 146 var injectServiceNodes bool 147 var queryTime int64 148 appenders := RequestedAppenders{All: true} 149 boxBy := params.Get("boxBy") 150 // @TODO requires refactoring to use clusterNameFromQuery 151 cluster := params.Get("clusterName") 152 configVendor := params.Get("configVendor") 153 durationString := params.Get("duration") 154 graphType := params.Get("graphType") 155 includeIdleEdgesString := params.Get("includeIdleEdges") 156 injectServiceNodesString := params.Get("injectServiceNodes") 157 namespaces := params.Get("namespaces") // csl of namespaces 158 queryTimeString := params.Get("queryTime") 159 rateGrpc := params.Get("rateGrpc") 160 rateHttp := params.Get("rateHttp") 161 rateTcp := params.Get("rateTcp") 162 telemetryVendor := params.Get("telemetryVendor") 163 164 if _, ok := params["appenders"]; ok { 165 appenderNames := strings.Split(params.Get("appenders"), ",") 166 for i, appenderName := range appenderNames { 167 appenderNames[i] = strings.TrimSpace(appenderName) 168 } 169 appenders = RequestedAppenders{All: false, AppenderNames: appenderNames} 170 } 171 if cluster == "" { 172 cluster = Unknown 173 } 174 if configVendor == "" { 175 configVendor = defaultConfigVendor 176 } else if configVendor != VendorCytoscape { 177 BadRequest(fmt.Sprintf("Invalid configVendor [%s]", configVendor)) 178 } 179 if durationString == "" { 180 duration, _ = model.ParseDuration(defaultDuration) 181 } else { 182 var durationErr error 183 duration, durationErr = model.ParseDuration(durationString) 184 if durationErr != nil { 185 BadRequest(fmt.Sprintf("Invalid duration [%s]", durationString)) 186 } 187 } 188 189 if graphType == "" { 190 graphType = defaultGraphType 191 } else if graphType != GraphTypeApp && graphType != GraphTypeService && graphType != GraphTypeVersionedApp && graphType != GraphTypeWorkload { 192 BadRequest(fmt.Sprintf("Invalid graphType [%s]", graphType)) 193 } 194 // service graphs do not inject service nodes 195 if graphType == GraphTypeService { 196 injectServiceNodesString = "false" 197 } 198 // app node graphs require an app graph type 199 if app != "" && graphType != GraphTypeApp && graphType != GraphTypeVersionedApp { 200 BadRequest(fmt.Sprintf("Invalid graphType [%s]. This node detail graph supports only graphType app or versionedApp.", graphType)) 201 } 202 if boxBy == "" { 203 boxBy = defaultBoxBy 204 } else { 205 for _, box := range strings.Split(boxBy, ",") { 206 switch strings.TrimSpace(box) { 207 case BoxByApp: 208 continue 209 case BoxByCluster: 210 continue 211 case BoxByNamespace: 212 continue 213 default: 214 BadRequest(fmt.Sprintf("Invalid boxBy [%s]", boxBy)) 215 } 216 } 217 } 218 if includeIdleEdgesString == "" { 219 includeIdleEdges = defaultIncludeIdleEdges 220 } else { 221 var includeIdleEdgesErr error 222 includeIdleEdges, includeIdleEdgesErr = strconv.ParseBool(includeIdleEdgesString) 223 if includeIdleEdgesErr != nil { 224 BadRequest(fmt.Sprintf("Invalid includeIdleEdges [%s]", includeIdleEdgesString)) 225 } 226 } 227 if injectServiceNodesString == "" { 228 injectServiceNodes = defaultInjectServiceNodes 229 } else { 230 var injectServiceNodesErr error 231 injectServiceNodes, injectServiceNodesErr = strconv.ParseBool(injectServiceNodesString) 232 if injectServiceNodesErr != nil { 233 BadRequest(fmt.Sprintf("Invalid injectServiceNodes [%s]", injectServiceNodesString)) 234 } 235 } 236 if queryTimeString == "" { 237 queryTime = time.Now().Unix() 238 } else { 239 var queryTimeErr error 240 queryTime, queryTimeErr = strconv.ParseInt(queryTimeString, 10, 64) 241 if queryTimeErr != nil { 242 BadRequest(fmt.Sprintf("Invalid queryTime [%s]", queryTimeString)) 243 } 244 } 245 if telemetryVendor == "" { 246 telemetryVendor = defaultTelemetryVendor 247 } else if telemetryVendor != VendorIstio { 248 BadRequest(fmt.Sprintf("Invalid telemetryVendor [%s]", telemetryVendor)) 249 } 250 251 // Process namespaces options: 252 namespaceMap := NewNamespaceInfoMap() 253 254 authInfoContext := authentication.GetAuthInfoContext(r.Context()) 255 256 var authInfo *api.AuthInfo 257 if authInfoContext != nil { 258 if authInfoCheck, ok := authInfoContext.(*api.AuthInfo); !ok { 259 Error("authInfo is not of type *api.AuthInfo") 260 } else { 261 authInfo = authInfoCheck 262 } 263 } else { 264 Error("token missing in request context") 265 } 266 267 accessibleNamespaces := getAccessibleNamespaces(authInfo) 268 269 // If path variable is set then it is the only relevant namespace (it's a node graph) 270 // Else if namespaces query param is set it specifies the relevant namespaces 271 // Else error, at least one namespace is required. 272 if namespace != "" { 273 namespaces = namespace 274 } 275 276 if namespaces == "" { 277 BadRequest("At least one namespace must be specified via the namespaces query parameter.") 278 } 279 280 for _, namespaceName := range strings.Split(namespaces, ",") { 281 namespaceName = strings.TrimSpace(namespaceName) 282 var earliestCreationTimestamp *time.Time 283 for _, an := range accessibleNamespaces { 284 if namespaceName == an.Name { 285 if nil == earliestCreationTimestamp || earliestCreationTimestamp.After(an.CreationTimestamp) { 286 earliestCreationTimestamp = &an.CreationTimestamp 287 } 288 } 289 } 290 if nil == earliestCreationTimestamp { 291 Forbidden(fmt.Sprintf("Requested namespace [%s] is not accessible.", namespaceName)) 292 } else { 293 namespaceMap[namespaceName] = NamespaceInfo{ 294 Name: namespaceName, 295 Duration: getSafeNamespaceDuration(namespaceName, *earliestCreationTimestamp, time.Duration(duration), queryTime), 296 IsIstio: config.IsIstioNamespace(namespaceName), 297 } 298 } 299 } 300 301 // Process Rate Options 302 303 rates := RequestedRates{ 304 Grpc: defaultRateGrpc, 305 Http: defaultRateHttp, 306 Tcp: defaultRateTcp, 307 } 308 309 if rateGrpc != "" { 310 switch rateGrpc { 311 case RateNone: 312 rates.Grpc = RateNone 313 case RateReceived: 314 rates.Grpc = RateReceived 315 case RateRequests: 316 rates.Grpc = RateRequests 317 case RateSent: 318 rates.Grpc = RateSent 319 case RateTotal: 320 rates.Grpc = RateTotal 321 default: 322 BadRequest(fmt.Sprintf("Invalid gRPC Rate [%s]", rateGrpc)) 323 } 324 } 325 326 if rateHttp != "" { 327 switch rateHttp { 328 case RateNone: 329 rates.Http = RateNone 330 case RateRequests: 331 rates.Http = RateRequests 332 default: 333 BadRequest(fmt.Sprintf("Invalid HTTP Rate [%s]", rateHttp)) 334 } 335 } 336 337 if rateTcp != "" { 338 switch rateTcp { 339 case RateNone: 340 rates.Tcp = RateNone 341 case RateReceived: 342 rates.Tcp = RateReceived 343 case RateSent: 344 rates.Tcp = RateSent 345 case RateTotal: 346 rates.Tcp = RateTotal 347 default: 348 BadRequest(fmt.Sprintf("Invalid TCP Rate [%s]", rateTcp)) 349 } 350 } 351 352 // Service graphs require service injection 353 if graphType == GraphTypeService { 354 injectServiceNodes = true 355 } 356 357 options := Options{ 358 ConfigVendor: configVendor, 359 TelemetryVendor: telemetryVendor, 360 ConfigOptions: ConfigOptions{ 361 BoxBy: boxBy, 362 CommonOptions: CommonOptions{ 363 Duration: time.Duration(duration), 364 GraphType: graphType, 365 Params: params, 366 QueryTime: queryTime, 367 }, 368 }, 369 TelemetryOptions: TelemetryOptions{ 370 AccessibleNamespaces: accessibleNamespaces, 371 Appenders: appenders, 372 IncludeIdleEdges: includeIdleEdges, 373 InjectServiceNodes: injectServiceNodes, 374 Namespaces: namespaceMap, 375 Rates: rates, 376 CommonOptions: CommonOptions{ 377 Duration: time.Duration(duration), 378 GraphType: graphType, 379 Params: params, 380 QueryTime: queryTime, 381 }, 382 NodeOptions: NodeOptions{ 383 Aggregate: aggregate, 384 AggregateValue: aggregateValue, 385 App: app, 386 Cluster: cluster, 387 Namespace: namespace, 388 Service: service, 389 Version: version, 390 Workload: workload, 391 }, 392 }, 393 } 394 395 return options 396 } 397 398 // GetGraphKind will return the kind of graph represented by the options. 399 func (o *TelemetryOptions) GetGraphKind() string { 400 if o.NodeOptions.App != "" || 401 o.NodeOptions.Version != "" || 402 o.NodeOptions.Workload != "" || 403 o.NodeOptions.Service != "" { 404 return graphKindNode 405 } 406 return graphKindNamespace 407 } 408 409 // getAccessibleNamespaces returns a Set of all namespaces accessible to the user. 410 // The Set is implemented using the map convention. Each map entry is set to the 411 // creation timestamp of the namespace, to be used to ensure valid time ranges for 412 // queries against the namespace. 413 func getAccessibleNamespaces(authInfo *api.AuthInfo) AccessibleNamespaces { 414 // Get the namespaces 415 business, err := business.Get(authInfo) 416 CheckError(err) 417 418 namespaces, err := business.Namespace.GetNamespaces(context.TODO()) 419 CheckError(err) 420 421 // Create a map to store the namespaces 422 accessibleNamespaces := make(AccessibleNamespaces) 423 for _, namespace := range namespaces { 424 accessibleNamespaces[GetClusterSensitiveKey(namespace.Cluster, namespace.Name)] = &AccessibleNamespace{ 425 Cluster: namespace.Cluster, 426 CreationTimestamp: namespace.CreationTimestamp, 427 Name: namespace.Name, 428 } 429 } 430 431 return accessibleNamespaces 432 } 433 434 // getSafeNamespaceDuration returns a safe duration for the query. If queryTime-requestedDuration > namespace 435 // creation time just return the requestedDuration. Otherwise reduce the duration as needed to ensure the 436 // namespace existed for the entire time range. An error is generated if no safe duration exists (i.e. the 437 // queryTime precedes the namespace). 438 func getSafeNamespaceDuration(ns string, nsCreationTime time.Time, requestedDuration time.Duration, queryTime int64) time.Duration { 439 var endTime time.Time 440 safeDuration := requestedDuration 441 442 if !nsCreationTime.IsZero() { 443 if queryTime != 0 { 444 endTime = time.Unix(queryTime, 0) 445 } else { 446 endTime = time.Now() 447 } 448 449 nsLifetime := endTime.Sub(nsCreationTime) 450 if nsLifetime <= 0 { 451 BadRequest(fmt.Sprintf("Namespace [%s] did not exist at requested queryTime [%v]", ns, endTime)) 452 } 453 454 if nsLifetime < safeDuration { 455 safeDuration = nsLifetime 456 log.Debugf("Reducing requestedDuration [%v] to safeDuration [%v]", requestedDuration, safeDuration) 457 } 458 } 459 460 return safeDuration 461 }