From 17ae3e96278926078d5f18e272c098a132d7e515 Mon Sep 17 00:00:00 2001 From: Julien Pinsonneau Date: Thu, 26 Oct 2023 17:59:52 +0200 Subject: [PATCH] fontend config + UpdateIfOwned --- controllers/consoleplugin/config/config.go | 80 ++ .../config/static-frontend-config.yaml | 711 ++++++++++++++++++ .../consoleplugin/consoleplugin_objects.go | 228 +++--- .../consoleplugin/consoleplugin_reconciler.go | 13 +- .../consoleplugin/consoleplugin_test.go | 67 +- controllers/ebpf/agent_controller.go | 2 +- .../ebpf/internal/permissions/permissions.go | 4 +- .../flowcollector_controller_console_test.go | 49 +- .../flowlogspipeline/flp_ingest_reconciler.go | 4 +- .../flp_monolith_reconciler.go | 4 +- .../flp_transfo_reconciler.go | 6 +- controllers/reconcilers/common.go | 14 +- pkg/helper/client_helper.go | 12 + 13 files changed, 1003 insertions(+), 191 deletions(-) create mode 100644 controllers/consoleplugin/config/config.go create mode 100644 controllers/consoleplugin/config/static-frontend-config.yaml diff --git a/controllers/consoleplugin/config/config.go b/controllers/consoleplugin/config/config.go new file mode 100644 index 000000000..93098af8c --- /dev/null +++ b/controllers/consoleplugin/config/config.go @@ -0,0 +1,80 @@ +package config + +import ( + flowslatest "github.com/netobserv/network-observability-operator/api/v1beta2" +) + +type ServerConfig struct { + Port int `yaml:"port,omitempty" json:"port,omitempty"` + MetricsPort int `yaml:"metricsPort,omitempty" json:"metricsPort,omitempty"` + CertPath string `yaml:"certPath,omitempty" json:"certPath,omitempty"` + KeyPath string `yaml:"keyPath,omitempty" json:"keyPath,omitempty"` + CORSOrigin string `yaml:"corsOrigin,omitempty" json:"corsOrigin,omitempty"` + CORSMethods string `yaml:"corsMethods,omitempty" json:"corsMethods,omitempty"` + CORSHeaders string `yaml:"corsHeaders,omitempty" json:"corsHeaders,omitempty"` + CORSMaxAge string `yaml:"corsMaxAge,omitempty" json:"corsMaxAge,omitempty"` +} + +type LokiConfig struct { + URL string `yaml:"url" json:"url"` + Labels []string `yaml:"labels" json:"labels"` + + StatusURL string `yaml:"statusUrl,omitempty" json:"statusUrl,omitempty"` + Timeout string `yaml:"timeout,omitempty" json:"timeout,omitempty"` + TenantID string `yaml:"tenantID,omitempty" json:"tenantID,omitempty"` + TokenPath string `yaml:"tokenPath,omitempty" json:"tokenPath,omitempty"` + SkipTLS bool `yaml:"skipTls,omitempty" json:"skipTls,omitempty"` + CAPath string `yaml:"caPath,omitempty" json:"caPath,omitempty"` + StatusSkipTLS bool `yaml:"statusSkipTls,omitempty" json:"statusSkipTls,omitempty"` + StatusCAPath string `yaml:"statusCaPath,omitempty" json:"statusCaPath,omitempty"` + StatusUserCertPath string `yaml:"statusUserCertPath,omitempty" json:"statusUserCertPath,omitempty"` + StatusUserKeyPath string `yaml:"statusUserKeyPath,omitempty" json:"statusUserKeyPath,omitempty"` + UseMocks bool `yaml:"useMocks,omitempty" json:"useMocks,omitempty"` + ForwardUserToken bool `yaml:"forwardUserToken,omitempty" json:"forwardUserToken,omitempty"` + AuthCheck string `yaml:"authCheck,omitempty" json:"authCheck,omitempty"` +} + +type ColumnConfig struct { + ID string `yaml:"id" json:"id"` + Name string `yaml:"name" json:"name"` + + Group string `yaml:"group,omitempty" json:"group,omitempty"` + Field string `yaml:"field,omitempty" json:"field,omitempty"` + Calculated string `yaml:"calculated,omitempty" json:"calculated,omitempty"` + Tooltip string `yaml:"tooltip,omitempty" json:"tooltip,omitempty"` + DocURL string `yaml:"docURL,omitempty" json:"docURL,omitempty"` + Filter string `yaml:"filter,omitempty" json:"filter,omitempty"` + Default bool `yaml:"default,omitempty" json:"default,omitempty"` + Width int `yaml:"width,omitempty" json:"width,omitempty"` +} + +type FilterConfig struct { + ID string `yaml:"id" json:"id"` + Name string `yaml:"name" json:"name"` + Component string `yaml:"component" json:"component"` + + Category string `yaml:"category,omitempty" json:"category,omitempty"` + AutoCompleteAddsQuotes bool `yaml:"autoCompleteAddsQuotes,omitempty" json:"autoCompleteAddsQuotes,omitempty"` + Hint string `yaml:"hint,omitempty" json:"hint,omitempty"` + Examples string `yaml:"examples,omitempty" json:"examples,omitempty"` + DocURL string `yaml:"docUrl,omitempty" json:"docUrl,omitempty"` + Placeholder string `yaml:"placeholder,omitempty" json:"placeholder,omitempty"` +} + +type FrontendConfig struct { + RecordTypes []string `yaml:"recordTypes" json:"recordTypes"` + Columns []ColumnConfig `yaml:"columns" json:"columns"` + Sampling int `yaml:"sampling" json:"sampling"` + Features []string `yaml:"features" json:"features"` + + PortNaming flowslatest.ConsolePluginPortConfig `yaml:"portNaming,omitempty" json:"portNaming,omitempty"` + Filters []FilterConfig `yaml:"filters,omitempty" json:"filters,omitempty"` + QuickFilters []flowslatest.QuickFilter `yaml:"quickFilters,omitempty" json:"quickFilters,omitempty"` + AlertNamespaces []string `yaml:"alertNamespaces,omitempty" json:"alertNamespaces,omitempty"` +} + +type PluginConfig struct { + Server ServerConfig `yaml:"server" json:"server"` + Loki LokiConfig `yaml:"loki" json:"loki"` + Frontend FrontendConfig `yaml:"frontend" json:"frontend"` +} diff --git a/controllers/consoleplugin/config/static-frontend-config.yaml b/controllers/consoleplugin/config/static-frontend-config.yaml new file mode 100644 index 000000000..b47f9a840 --- /dev/null +++ b/controllers/consoleplugin/config/static-frontend-config.yaml @@ -0,0 +1,711 @@ +# these configurations are static and append to the console plugin configmap +# other fields such as recordTypes, quickFilters, alertNamespaces, sampling, features +# are taken from the CR +# see consoleplugin_objects.go -> configMap func +columns: + - id: StartTime + name: Start Time + tooltip: Time of the first packet observed. Unlike End Time, it is not used in queries + to select records in an interval. + field: TimeFlowStartMs + default: false + width: 15 + - id: EndTime + name: End Time + tooltip: Time of the last packet observed. This is what is used in queries to select + records in an interval. + field: TimeFlowEndMs + default: true + width: 15 + - id: RecordType + name: Event / Type + field: _RecordType + filter: type + default: true + width: 15 + - id: _HashId + name: Conversation Id + field: _HashId + filter: id + default: true + width: 15 + - id: SrcK8S_Name + group: Source + name: Name + tooltip: The source name of the related kubernetes resource. + docURL: http://kubernetes.io/docs/user-guide/identifiers#names + field: SrcK8S_Name + filter: src_name + default: true + width: 15 + - id: SrcK8S_Type + group: Source + name: Kind + tooltip: |- + The kind of the related kubernetes resource. Examples: + - Pod + - Service + - Node + field: SrcK8S_Type + filter: src_kind + default: false + width: 10 + - id: SrcK8S_OwnerName + group: Source + name: Owner + tooltip: The source owner name of the related kubernetes resource. + docURL: https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/ + field: SrcK8S_OwnerName + filter: src_owner_name + default: false + width: 15 + - id: SrcK8S_OwnerType + group: Source + name: Owner Kind + tooltip: |- + The owner kind of the related kubernetes resource. Examples: + - Deployment + - StatefulSet + - DaemonSet + - Job + - CronJob + field: SrcK8S_OwnerType + filter: src_kind + default: false + width: 10 + - id: SrcK8S_Namespace + group: Source + name: Namespace + tooltip: The source namespace of the related kubernetes resource. + docURL: http://kubernetes.io/docs/user-guide/identifiers#namespaces + field: SrcK8S_Namespace + filter: src_namespace + default: true + width: 15 + - id: SrcAddr + group: Source + name: IP + tooltip: The source IP address. Can be either in IPv4 or IPv6 format. + field: SrcAddr + filter: src_address + default: false + width: 10 + - id: SrcPort + group: Source + name: Port + tooltip: The source port number. + field: SrcPort + filter: src_port + default: true + width: 10 + - id: SrcMac + group: Source + name: MAC + tooltip: The source MAC address. + field: SrcMac + filter: src_mac + default: false + width: 10 + - id: SrcK8S_HostIP + group: Source + name: Node IP + tooltip: The source node IP address. Can be either in IPv4 or IPv6 format. + field: SrcK8S_HostIP + filter: src_host_address + default: false + width: 10 + - id: SrcK8S_HostName + group: Source + name: Node Name + tooltip: The source name of the node running the workload. + docURL: https://kubernetes.io/docs/concepts/architecture/nodes/ + field: SrcK8S_HostName + filter: src_host_name + default: false + width: 15 + - id: SrcK8S_Object + group: Source + name: Kubernetes Object + calculated: getConcatenatedValue(SrcAddr,SrcPort,SrcK8S_Type,SrcK8S_Namespace,SrcK8S_Name) + default: false + width: 15 + - id: SrcK8S_OwnerObject + group: Source + name: Owner Kubernetes Object + calculated: getConcatenatedValue(SrcAddr,SrcPort,SrcK8S_OwnerType,SrcK8S_Namespace,SrcK8S_OwnerName) + default: false + width: 15 + - id: SrcAddrPort + group: Source + name: IP & Port + calculated: getConcatenatedValue(SrcAddr,SrcPort) + default: false + width: 15 + - id: DstK8S_Name + group: Destination + name: Name + tooltip: The destination name of the related kubernetes resource. + docURL: http://kubernetes.io/docs/user-guide/identifiers#names + field: DstK8S_Name + filter: dst_name + default: true + width: 15 + - id: DstK8S_Type + group: Destination + name: Kind + tooltip: |- + The kind of the related kubernetes resource. Examples: + - Pod + - Service + - Node + field: DstK8S_Type + filter: dst_kind + default: false + width: 10 + - id: DstK8S_OwnerName + group: Destination + name: Owner + tooltip: The destination owner name of the related kubernetes resource. + docURL: https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/ + field: DstK8S_OwnerName + filter: dst_owner_name + default: false + width: 15 + - id: DstK8S_OwnerType + group: Destination + name: Owner Kind + tooltip: |- + The owner kind of the related kubernetes resource. Examples: + - Deployment + - StatefulSet + - DaemonSet + - Job + - CronJob + field: DstK8S_OwnerType + filter: dst_kind + default: false + width: 10 + - id: DstK8S_Namespace + group: Destination + name: Namespace + tooltip: The destination namespace of the related kubernetes resource. + docURL: http://kubernetes.io/docs/user-guide/identifiers#namespaces + field: DstK8S_Namespace + filter: dst_namespace + default: true + width: 15 + - id: DstAddr + group: Destination + name: IP + tooltip: The destination IP address. Can be either in IPv4 or IPv6 format. + field: DstAddr + filter: dst_address + default: false + width: 10 + - id: DstPort + group: Destination + name: Port + tooltip: The destination port number. + field: DstPort + filter: dst_port + default: true + width: 10 + - id: DstMac + group: Destination + name: MAC + tooltip: The destination MAC address. + field: DstMac + filter: dst_mac + default: false + width: 10 + - id: DstK8S_HostIP + group: Destination + name: Node IP + tooltip: The destination node IP address. Can be either in IPv4 or IPv6 format. + field: DstK8S_HostIP + filter: dst_host_address + default: false + width: 10 + - id: DstK8S_HostName + group: Destination + name: Node Name + tooltip: The destination name of the node running the workload. + docURL: https://kubernetes.io/docs/concepts/architecture/nodes/ + field: DstK8S_HostName + filter: dst_host_name + default: false + width: 15 + - id: DstK8S_Object + group: Destination + name: Kubernetes Object + calculated: getConcatenatedValue(DstAddr,DstPort,DstK8S_Type,DstK8S_Namespace,DstK8S_Name) + default: false + width: 15 + - id: DstK8S_OwnerObject + group: Destination + name: Owner Kubernetes Object + calculated: getConcatenatedValue(DstAddr,DstPort,DstK8S_OwnerType,DstK8S_Namespace,DstK8S_OwnerName) + default: false + width: 15 + - id: DstAddrPort + group: Destination + name: IP & Port + calculated: getConcatenatedValue(DstAddr,DstPort) + default: false + width: 15 + - id: K8S_Name + name: Names + calculated: getSrcOrDstValue(SrcK8S_Name,DstK8S_Name) + default: false + width: 15 + - id: K8S_Type + name: Kinds + calculated: getSrcOrDstValue(SrcK8S_Type,DstK8S_Type) + default: false + width: 10 + - id: K8S_OwnerName + name: Owners + calculated: getSrcOrDstValue(SrcK8S_OwnerName,DstK8S_OwnerName) + default: false + width: 15 + - id: K8S_OwnerType + name: Owner Kinds + calculated: getSrcOrDstValue(SrcK8S_OwnerType,DstK8S_OwnerType) + default: false + width: 10 + - id: K8S_Namespace + name: Namespaces + calculated: getSrcOrDstValue(SrcK8S_Namespace,DstK8S_Namespace) + default: false + width: 15 + - id: Addr + name: IP + calculated: getSrcOrDstValue(SrcAddr,DstAddr) + default: false + width: 10 + - id: Port + name: Ports + calculated: getSrcOrDstValue(SrcPort,DstPort) + default: false + width: 10 + - id: Mac + name: MAC + calculated: getSrcOrDstValue(SrcMac,DstMac) + default: false + width: 10 + - id: K8S_HostIP + name: Node IP + calculated: getSrcOrDstValue(SrcK8S_HostIP,DstK8S_HostIP) + default: false + width: 10 + - id: K8S_HostName + name: Node Name + calculated: getSrcOrDstValue(SrcK8S_HostName,DstK8S_HostName) + default: false + width: 15 + - id: K8S_Object + name: Kubernetes Objects + calculated: '[getConcatenatedValue(SrcAddr,SrcPort,SrcK8S_Type,SrcK8S_Namespace,SrcK8S_Name),getConcatenatedValue(DstAddr,DstPort,DstK8S_Type,DstK8S_Namespace,DstK8S_Name)]' + default: false + width: 15 + - id: K8S_OwnerObject + name: Owner Kubernetes Objects + calculated: '[getConcatenatedValue(SrcAddr,SrcPort,SrcK8S_OwnerType,SrcK8S_Namespace,SrcK8S_OwnerName),getConcatenatedValue(DstAddr,DstPort,DstK8S_OwnerType,DstK8S_Namespace,DstK8S_OwnerName)]' + default: false + width: 15 + - id: AddrPort + name: IPs & Ports + calculated: '[getConcatenatedValue(SrcAddr,SrcPort),getConcatenatedValue(DstAddr,DstPort)]' + default: false + width: 15 + - id: Proto + name: Protocol + tooltip: The value of the protocol number in the IP packet header + field: Proto + filter: protocol + default: false + width: 10 + - id: FlowDirection + name: Direction + tooltip: The direction of the Flow observed at the Node observation point. + field: FlowDirection + filter: direction + default: false + width: 10 + - id: Interface + name: Interface + tooltip: The network interface of the Flow. + field: Interface + filter: interface + default: false + width: 10 + - id: Bytes + name: Bytes + tooltip: The total aggregated number of bytes. + field: Bytes + default: true + width: 5 + - id: Packets + name: Packets + tooltip: The total aggregated number of packets. + field: Packets + filter: pkt_drop_cause + default: true + width: 5 + - id: FlowDuration + name: Duration + tooltip: Time elapsed between Start Time and End Time. + default: false + width: 5 + - id: TimeFlowRttMs + name: Flow RTT + tooltip: TCP handshake Round Trip Time + field: TimeFlowRttNs + filter: time_flow_rtt + default: false + width: 5 + - id: CollectionTime + name: Collection Time + tooltip: Reception time of the record by the collector. + field: TimeReceived + default: false + width: 15 + - id: CollectionLatency + name: Collection Latency + tooltip: Time elapsed between End Time and Collection Time. + default: false + width: 5 + - id: DNSId + group: DNS + name: DNS Id + tooltip: DNS request identifier. + field: DnsId + filter: dns_id + default: false + width: 5 + - id: DNSLatency + group: DNS + name: DNS Latency + tooltip: Time elapsed between DNS request and response. + default: false + width: 5 + - id: DNSResponseCode + group: DNS + name: DNS Response Code + tooltip: DNS RCODE name from response header. + field: DnsFlagsResponseCode + filter: dns_flag_response_code + default: false + width: 5 +filters: + - id: src_namespace + name: Namespace + component: autocomplete + autoCompleteAddsQuotes: true + category: source + placeholder: 'E.g: netobserv' + hint: Specify a single kubernetes name. + examples: |- + Specify a single kubernetes name following these rules: + - Containing any alphanumeric, hyphen, underscrore or dot character + - Partial text like cluster, cluster-image, image-registry + - Exact match using quotes like "cluster-image-registry" + - Case sensitive match using quotes like "Deployment" + - Starting text like cluster, "cluster-*" + - Ending text like "*-registry" + - Pattern like "cluster-*-registry", "c*-*-r*y", -i*e- + - id: dst_namespace + name: Namespace + component: autocomplete + autoCompleteAddsQuotes: true + category: destination + placeholder: 'E.g: netobserv' + hint: Specify a single kubernetes name. + examples: |- + Specify a single kubernetes name following these rules: + - Containing any alphanumeric, hyphen, underscrore or dot character + - Partial text like cluster, cluster-image, image-registry + - Exact match using quotes like "cluster-image-registry" + - Case sensitive match using quotes like "Deployment" + - Starting text like cluster, "cluster-*" + - Ending text like "*-registry" + - Pattern like "cluster-*-registry", "c*-*-r*y", -i*e- + - id: src_name + name: Name + component: text + category: source + placeholder: 'E.g: my-pod' + hint: Specify a single kubernetes name. + examples: |- + Specify a single kubernetes name following these rules: + - Containing any alphanumeric, hyphen, underscrore or dot character + - Partial text like cluster, cluster-image, image-registry + - Exact match using quotes like "cluster-image-registry" + - Case sensitive match using quotes like "Deployment" + - Starting text like cluster, "cluster-*" + - Ending text like "*-registry" + - Pattern like "cluster-*-registry", "c*-*-r*y", -i*e- + - id: dst_name + name: Name + component: text + category: destination + placeholder: 'E.g: my-pod' + hint: Specify a single kubernetes name. + examples: |- + Specify a single kubernetes name following these rules: + - Containing any alphanumeric, hyphen, underscrore or dot character + - Partial text like cluster, cluster-image, image-registry + - Exact match using quotes like "cluster-image-registry" + - Case sensitive match using quotes like "Deployment" + - Starting text like cluster, "cluster-*" + - Ending text like "*-registry" + - Pattern like "cluster-*-registry", "c*-*-r*y", -i*e- + - id: src_kind + name: Kind + component: autocomplete + autoCompleteAddsQuotes: true + category: source + placeholder: 'E.g: Pod, Service' + - id: dst_kind + name: Kind + component: autocomplete + autoCompleteAddsQuotes: true + category: destination + placeholder: 'E.g: Pod, Service' + - id: src_owner_name + name: Owner Name + component: text + category: source + placeholder: 'E.g: my-deployment' + hint: Specify a single kubernetes name. + examples: |- + Specify a single kubernetes name following these rules: + - Containing any alphanumeric, hyphen, underscrore or dot character + - Partial text like cluster, cluster-image, image-registry + - Exact match using quotes like "cluster-image-registry" + - Case sensitive match using quotes like "Deployment" + - Starting text like cluster, "cluster-*" + - Ending text like "*-registry" + - Pattern like "cluster-*-registry", "c*-*-r*y", -i*e- + - id: dst_owner_name + name: Owner Name + component: text + category: destination + placeholder: 'E.g: my-deployment' + hint: Specify a single kubernetes name. + examples: |- + Specify a single kubernetes name following these rules: + - Containing any alphanumeric, hyphen, underscrore or dot character + - Partial text like cluster, cluster-image, image-registry + - Exact match using quotes like "cluster-image-registry" + - Case sensitive match using quotes like "Deployment" + - Starting text like cluster, "cluster-*" + - Ending text like "*-registry" + - Pattern like "cluster-*-registry", "c*-*-r*y", -i*e- + - id: src_resource + name: Resource + component: autocomplete + category: source + placeholder: 'E.g: Pod.default.my-pod' + hint: Specify an existing resource from its kind, namespace and name. + examples: |- + Specify a kind, namespace and name from existing: + - Select kind first from suggestions + - Then Select namespace from suggestions + - Finally select name from suggestions + You can also directly specify a kind, namespace and name like pod.openshift.apiserver + - id: dst_resource + name: Resource + component: autocomplete + category: destination + placeholder: 'E.g: Pod.default.my-pod' + hint: Specify an existing resource from its kind, namespace and name. + examples: |- + Specify a kind, namespace and name from existing: + - Select kind first from suggestions + - Then Select namespace from suggestions + - Finally select name from suggestions + You can also directly specify a kind, namespace and name like pod.openshift.apiserver + - id: src_address + name: IP + component: text + category: source + hint: Specify a single IP or range. + placeholder: 'E.g: 192.0.2.0' + examples: |- + Specify IP following one of these rules: + - A single IPv4 or IPv6 address like 192.0.2.0, ::1 + - An IP address range like 192.168.0.1-192.189.10.12, 2001:db8::1-2001:db8::8 + - A CIDR specification like 192.51.100.0/24, 2001:db8::/32 + - Empty double quotes "" for an empty IP + - id: dst_address + name: IP + component: text + category: destination + hint: Specify a single IP or range. + placeholder: 'E.g: 192.0.2.0' + examples: |- + Specify IP following one of these rules: + - A single IPv4 or IPv6 address like 192.0.2.0, ::1 + - An IP address range like 192.168.0.1-192.189.10.12, 2001:db8::1-2001:db8::8 + - A CIDR specification like 192.51.100.0/24, 2001:db8::/32 + - Empty double quotes "" for an empty IP + - id: src_port + name: Port + component: autocomplete + category: source + hint: Specify a single port number or name. + placeholder: 'E.g: 80' + examples: |- + Specify a single port following one of these rules: + - A port number like 80, 21 + - A IANA name like HTTP, FTP + docUrl: https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml + - id: dst_port + name: Port + component: autocomplete + category: destination + hint: Specify a single port number or name. + placeholder: 'E.g: 80' + examples: |- + Specify a single port following one of these rules: + - A port number like 80, 21 + - A IANA name like HTTP, FTP + docUrl: https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml + - id: src_mac + name: MAC + component: text + category: source + placeholder: 'E.g: 42:01:0A:00:00:01' + hint: Specify a single MAC address. + - id: dst_mac + name: MAC + component: text + category: destination + placeholder: 'E.g: 42:01:0A:00:00:01' + hint: Specify a single MAC address. + - id: src_host_address + name: Node IP + component: text + category: source + placeholder: 'E.g: 10.0.0.1' + hint: Specify a single IP or range. + examples: |- + Specify IP following one of these rules: + - A single IPv4 or IPv6 address like 192.0.2.0, ::1 + - An IP address range like 192.168.0.1-192.189.10.12, 2001:db8::1-2001:db8::8 + - A CIDR specification like 192.51.100.0/24, 2001:db8::/32 + - Empty double quotes "" for an empty IP + - id: dst_host_address + name: Node IP + component: text + category: destination + placeholder: 'E.g: 10.0.0.1' + hint: Specify a single IP or range. + examples: |- + Specify IP following one of these rules: + - A single IPv4 or IPv6 address like 192.0.2.0, ::1 + - An IP address range like 192.168.0.1-192.189.10.12, 2001:db8::1-2001:db8::8 + - A CIDR specification like 192.51.100.0/24, 2001:db8::/32 + - Empty double quotes "" for an empty IP + - id: src_host_name + name: Node Name + component: text + category: source + placeholder: 'E.g: my-node' + hint: Specify a single kubernetes name. + examples: |- + Specify a single kubernetes name following these rules: + - Containing any alphanumeric, hyphen, underscrore or dot character + - Partial text like cluster, cluster-image, image-registry + - Exact match using quotes like "cluster-image-registry" + - Case sensitive match using quotes like "Deployment" + - Starting text like cluster, "cluster-*" + - Ending text like "*-registry" + - Pattern like "cluster-*-registry", "c*-*-r*y", -i*e- + - id: dst_host_name + name: Node Name + component: text + category: destination + placeholder: 'E.g: my-node' + hint: Specify a single kubernetes name. + examples: |- + Specify a single kubernetes name following these rules: + - Containing any alphanumeric, hyphen, underscrore or dot character + - Partial text like cluster, cluster-image, image-registry + - Exact match using quotes like "cluster-image-registry" + - Case sensitive match using quotes like "Deployment" + - Starting text like cluster, "cluster-*" + - Ending text like "*-registry" + - Pattern like "cluster-*-registry", "c*-*-r*y", -i*e- + - id: protocol + name: Protocol + component: autocomplete + placeholder: 'E.g: TCP, UDP' + hint: Specify a single protocol number or name. + examples: |- + Specify a single protocol following one of these rules: + - A protocol number like 6, 17 + - A IANA name like TCP, UDP + - Empty double quotes "" for undefined protocol + docUrl: https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml + - id: direction + name: Direction + component: autocomplete + placeholder: 'E.g: Ingress, Egress, Inner' + hint: Specify the direction of the Flow observed at the Node observation point. + - id: interface + name: Network interface + component: text + placeholder: 'E.g: br-ex, ovn-k8s-mp0' + hint: Specify a network interface. + - id: dscp + name: DSCP value + component: number + hint: Specify a Differentiated Services Code Point value as integer number. + - id: id + name: Conversation Id + component: text + hint: Specify a single conversation hash Id. + - id: pkt_drop_state + name: Packet drop TCP state + component: autocomplete + placeholder: 'E.g: ESTABLISHED, SYN_SENT, SYN_RECV' + hint: Specify a single TCP state. + examples: |- + Specify a single TCP state name like: + - A _LINUX_TCP_STATES_H number like 1, 2, 3 + - A _LINUX_TCP_STATES_H TCP name like ESTABLISHED, SYN_SENT, SYN_RECV + docUrl: https://github.com/torvalds/linux/blob/master/include/net/tcp_states.h + - id: pkt_drop_cause + name: Packet drop latest cause + component: autocomplete + placeholder: 'E.g: NO_SOCKET, PKT_TOO_SMALL' + hint: Specify a single packet drop cause. + examples: |- + Specify a single packet drop cause like: + - A _LINUX_DROPREASON_CORE_H number like 2, 3, 4 + - A _LINUX_DROPREASON_CORE_H SKB_DROP_REASON name like NOT_SPECIFIED, NO_SOCKET, PKT_TOO_SMALL + docUrl: https://github.com/torvalds/linux/blob/master/include/net/dropreason-core.h + - id: dns_id + name: DNS Id + component: number + hint: Specify a single DNS Id. + - id: dns_latency + name: DNS Latency + component: number + hint: Specify a DNS Latency in miliseconds. + - id: dns_flag_response_code + name: DNS Response Code + component: autocomplete + hint: Specify a single DNS RCODE name. + placeholder: 'E.g: NoError, NXDomain, NotAuth' + examples: |- + Specify a single DNS RCODE name like: + - A IANA RCODE number like 0, 3, 9 + - A IANA RCODE name like NoError, NXDomain, NotAuth + docUrl: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6 + - id: time_flow_rtt + name: Flow RTT + component: number + hint: Specify a TCP handshake Round Trip Time in nanoseconds. \ No newline at end of file diff --git a/controllers/consoleplugin/consoleplugin_objects.go b/controllers/consoleplugin/consoleplugin_objects.go index 7797eaeef..f5ac8d586 100644 --- a/controllers/consoleplugin/consoleplugin_objects.go +++ b/controllers/consoleplugin/consoleplugin_objects.go @@ -1,11 +1,11 @@ package consoleplugin import ( + _ "embed" "fmt" "hash/fnv" "path/filepath" "strconv" - "strings" osv1alpha1 "github.com/openshift/api/console/v1alpha1" monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" @@ -18,6 +18,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" flowslatest "github.com/netobserv/network-observability-operator/api/v1beta2" + config "github.com/netobserv/network-observability-operator/controllers/consoleplugin/config" "github.com/netobserv/network-observability-operator/controllers/constants" "github.com/netobserv/network-observability-operator/pkg/helper" "github.com/netobserv/network-observability-operator/pkg/volumes" @@ -160,101 +161,51 @@ func (b *builder) deployment(cmDigest string) *appsv1.Deployment { } } -func (b *builder) buildArgs(desired *flowslatest.FlowCollectorSpec) []string { - querierURL := helper.LokiQuerierURL(&desired.Loki) - statusURL := helper.LokiStatusURL(&desired.Loki) - - // check for connection traking to list indexes - indexFields := constants.LokiIndexFields - if desired.Processor.LogTypes != nil && *desired.Processor.LogTypes != flowslatest.LogTypeFlows { - indexFields = append(indexFields, constants.LokiConnectionIndexFields...) - } - - args := []string{ - "-cert", "/var/serving-cert/tls.crt", - "-key", "/var/serving-cert/tls.key", - "-loki", querierURL, - "-loki-labels", strings.Join(indexFields, ","), - "-loki-tenant-id", helper.LokiTenantID(&desired.Loki), - "-loglevel", desired.ConsolePlugin.LogLevel, - "-frontend-config", filepath.Join(configPath, configFile), - } - - if helper.LokiForwardUserToken(&desired.Loki) { - args = append(args, "-loki-forward-user-token") - } - - if querierURL != statusURL { - args = append(args, "-loki-status", statusURL) - } - - clientTLS := helper.LokiTLS(&desired.Loki) - if clientTLS.Enable { - if clientTLS.InsecureSkipVerify { - args = append(args, "-loki-skip-tls") - } else { - caPath := b.volumes.AddCACertificate(clientTLS, "loki-certs") - if caPath != "" { - args = append(args, "-loki-ca-path", caPath) - } - } - } - - statusTLS := helper.LokiStatusTLS(&desired.Loki) - if statusTLS.Enable { - if statusTLS.InsecureSkipVerify { - args = append(args, "-loki-status-skip-tls") - } else { - statusCaPath, userCertPath, userKeyPath := b.volumes.AddMutualTLSCertificates(statusTLS, "loki-status-certs") - if statusCaPath != "" { - args = append(args, "-loki-status-ca-path", statusCaPath) - } - if userCertPath != "" && userKeyPath != "" { - args = append(args, "-loki-status-user-cert-path", userCertPath) - args = append(args, "-loki-status-user-key-path", userKeyPath) - } - } - } - - if helper.LokiUseHostToken(&desired.Loki) { - tokenPath := b.volumes.AddToken(constants.PluginName) - args = append(args, "-loki-token-path", tokenPath) - } - return args -} - func (b *builder) podTemplate(cmDigest string) *corev1.PodTemplateSpec { - volumes := []corev1.Volume{{ - Name: secretName, - VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: secretName, + volumes := []corev1.Volume{ + { + Name: secretName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: secretName, + }, }, - }, - }, { - Name: configVolume, - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: configMapName, + }, { + Name: configVolume, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: configMapName, + }, }, }, }, - }, } - volumeMounts := []corev1.VolumeMount{{ - Name: secretName, - MountPath: "/var/serving-cert", - ReadOnly: true, - }, { - Name: configVolume, - MountPath: configPath, - ReadOnly: true, - }, + volumeMounts := []corev1.VolumeMount{ + { + Name: secretName, + MountPath: "/var/serving-cert", + ReadOnly: true, + }, { + Name: configVolume, + MountPath: configPath, + ReadOnly: true, + }, } - args := b.buildArgs(b.desired) + // ensure volumes are up to date + clientTLS := helper.LokiTLS(&b.desired.Loki) + if clientTLS.Enable && !clientTLS.InsecureSkipVerify { + b.volumes.AddCACertificate(clientTLS, "loki-certs") + } + statusTLS := helper.LokiStatusTLS(&b.desired.Loki) + if statusTLS.Enable && !statusTLS.InsecureSkipVerify { + b.volumes.AddMutualTLSCertificates(statusTLS, "loki-status-certs") + } + if helper.LokiUseHostToken(&b.desired.Loki) { + b.volumes.AddToken(constants.PluginName) + } return &corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ @@ -270,7 +221,11 @@ func (b *builder) podTemplate(cmDigest string) *corev1.PodTemplateSpec { ImagePullPolicy: corev1.PullPolicy(b.desired.ConsolePlugin.ImagePullPolicy), Resources: *b.desired.ConsolePlugin.Resources.DeepCopy(), VolumeMounts: b.volumes.AppendMounts(volumeMounts), - Args: args, + Args: []string{ + + "-loglevel", b.desired.ConsolePlugin.LogLevel, + "-config", filepath.Join(configPath, configFile), + }, }}, Volumes: b.volumes.AppendVolumes(volumes), ServiceAccountName: constants.PluginName, @@ -344,38 +299,99 @@ func (b *builder) metricsService() *corev1.Service { } } -// returns a configmap with a digest of its configuration contents, which will be used to -// detect any configuration change -func (b *builder) configMap() (*corev1.ConfigMap, string) { - outputRecordTypes := helper.GetRecordTypes(&b.desired.Processor) +func (b *builder) setLokiConfig(lconf *config.LokiConfig) { + lconf.URL = helper.LokiQuerierURL(&b.desired.Loki) + statusURL := helper.LokiStatusURL(&b.desired.Loki) + if lconf.URL != statusURL { + lconf.StatusURL = statusURL + } + // check for connection traking to list indexes + indexFields := constants.LokiIndexFields + if b.desired.Processor.LogTypes != nil && *b.desired.Processor.LogTypes != flowslatest.LogTypeFlows { + indexFields = append(indexFields, constants.LokiConnectionIndexFields...) + } + lconf.Labels = indexFields + lconf.TenantID = helper.LokiTenantID(&b.desired.Loki) + lconf.ForwardUserToken = helper.LokiForwardUserToken(&b.desired.Loki) + clientTLS := helper.LokiTLS(&b.desired.Loki) + if clientTLS.Enable { + if clientTLS.InsecureSkipVerify { + lconf.SkipTLS = true + } else { + caPath := b.volumes.AddCACertificate(clientTLS, "loki-certs") + if caPath != "" { + lconf.CAPath = caPath + } + } + } + statusTLS := helper.LokiStatusTLS(&b.desired.Loki) + if statusTLS.Enable { + if statusTLS.InsecureSkipVerify { + lconf.StatusSkipTLS = true + } else { + statusCaPath, userCertPath, userKeyPath := b.volumes.AddMutualTLSCertificates(statusTLS, "loki-status-certs") + if statusCaPath != "" { + lconf.StatusCAPath = statusCaPath + } + if userCertPath != "" && userKeyPath != "" { + lconf.StatusUserCertPath = userCertPath + lconf.StatusUserKeyPath = userKeyPath + } + } + } + if helper.LokiUseHostToken(&b.desired.Loki) { + lconf.TokenPath = b.volumes.AddToken(constants.PluginName) + } +} - var features []string +func (b *builder) setFrontendConfig(fconf *config.FrontendConfig) { if helper.UseEBPF(b.desired) { if helper.IsPktDropEnabled(&b.desired.Agent.EBPF) { - features = append(features, "pktDrop") + fconf.Features = append(fconf.Features, "pktDrop") } if helper.IsDNSTrackingEnabled(&b.desired.Agent.EBPF) { - features = append(features, "dnsTracking") + fconf.Features = append(fconf.Features, "dnsTracking") } if helper.IsFlowRTTEnabled(&b.desired.Agent.EBPF) { - features = append(features, "flowRTT") + fconf.Features = append(fconf.Features, "flowRTT") } } + fconf.RecordTypes = helper.GetRecordTypes(&b.desired.Processor) + fconf.PortNaming = b.desired.ConsolePlugin.PortNaming + fconf.QuickFilters = b.desired.ConsolePlugin.QuickFilters + fconf.AlertNamespaces = []string{b.namespace} + fconf.Sampling = helper.GetSampling(b.desired) +} + +//go:embed config/static-frontend-config.yaml +var staticFrontendConfig []byte - config := map[string]interface{}{ - "recordTypes": outputRecordTypes, - "portNaming": b.desired.ConsolePlugin.PortNaming, - "quickFilters": b.desired.ConsolePlugin.QuickFilters, - "alertNamespaces": []string{b.namespace}, - "sampling": helper.GetSampling(b.desired), - "features": features, +// returns a configmap with a digest of its configuration contents, which will be used to +// detect any configuration change +func (b *builder) configMap() (*corev1.ConfigMap, string, error) { + config := config.PluginConfig{} + // configure server + config.Server.CertPath = "/var/serving-cert/tls.crt" + config.Server.KeyPath = "/var/serving-cert/tls.key" + + // configure loki + b.setLokiConfig(&config.Loki) + + // configure frontend from embedded static file + err := yaml.Unmarshal(staticFrontendConfig, &config.Frontend) + if err != nil { + return nil, "", err } + b.setFrontendConfig(&config.Frontend) - configStr := "{}" - if bs, err := yaml.Marshal(config); err == nil { + var configStr string + bs, err := yaml.Marshal(config) + if err == nil { configStr = string(bs) + } else { + return nil, "", err } configMap := corev1.ConfigMap{ @@ -391,7 +407,7 @@ func (b *builder) configMap() (*corev1.ConfigMap, string) { hasher := fnv.New64a() _, _ = hasher.Write([]byte(configStr)) digest := strconv.FormatUint(hasher.Sum64(), 36) - return &configMap, digest + return &configMap, digest, nil } func (b *builder) serviceAccount() *corev1.ServiceAccount { diff --git a/controllers/consoleplugin/consoleplugin_reconciler.go b/controllers/consoleplugin/consoleplugin_reconciler.go index a2d8cddcd..2843fd4ec 100644 --- a/controllers/consoleplugin/consoleplugin_reconciler.go +++ b/controllers/consoleplugin/consoleplugin_reconciler.go @@ -183,7 +183,7 @@ func (r *CPReconciler) reconcilePlugin(ctx context.Context, builder *builder, de return err } } else if pluginNeedsUpdate(&oldPlg, &desired.ConsolePlugin) { - if err := r.UpdateOwned(ctx, &oldPlg, consolePlugin); err != nil { + if err := r.UpdateIfOwned(ctx, &oldPlg, consolePlugin); err != nil { return err } } @@ -191,13 +191,16 @@ func (r *CPReconciler) reconcilePlugin(ctx context.Context, builder *builder, de } func (r *CPReconciler) reconcileConfigMap(ctx context.Context, builder *builder) (string, error) { - newCM, configDigest := builder.configMap() + newCM, configDigest, err := builder.configMap() + if err != nil { + return "", err + } if !r.Managed.Exists(r.owned.configMap) { if err := r.CreateOwned(ctx, newCM); err != nil { return "", err } } else if !reflect.DeepEqual(newCM.Data, r.owned.configMap.Data) { - if err := r.UpdateOwned(ctx, r.owned.configMap, newCM); err != nil { + if err := r.UpdateIfOwned(ctx, r.owned.configMap, newCM); err != nil { return "", err } } @@ -214,7 +217,7 @@ func (r *CPReconciler) reconcileDeployment(ctx context.Context, builder *builder return err } } else if helper.DeploymentChanged(r.owned.deployment, newDepl, constants.PluginName, helper.HPADisabled(&desired.ConsolePlugin.Autoscaler), helper.PtrInt32(desired.ConsolePlugin.Replicas), &report) { - if err := r.UpdateOwned(ctx, r.owned.deployment, newDepl); err != nil { + if err := r.UpdateIfOwned(ctx, r.owned.deployment, newDepl); err != nil { return err } } else { @@ -256,7 +259,7 @@ func (r *CPReconciler) reconcileHPA(ctx context.Context, builder *builder, desir return err } } else if helper.AutoScalerChanged(r.owned.hpa, desired.ConsolePlugin.Autoscaler, &report) { - if err := r.UpdateOwned(ctx, r.owned.hpa, newASC); err != nil { + if err := r.UpdateIfOwned(ctx, r.owned.hpa, newASC); err != nil { return err } } diff --git a/controllers/consoleplugin/consoleplugin_test.go b/controllers/consoleplugin/consoleplugin_test.go index bc70cb266..372d6a8c5 100644 --- a/controllers/consoleplugin/consoleplugin_test.go +++ b/controllers/consoleplugin/consoleplugin_test.go @@ -187,6 +187,33 @@ func TestContainerUpdateCheck(t *testing.T) { report = helper.NewChangeReport("") assert.False(helper.PodChanged(&old.Spec.Template, &nEw.Spec.Template, constants.PluginName, &report)) assert.Contains(report.String(), "no change") +} + +func TestConfigMapUpdateCheck(t *testing.T) { + assert := assert.New(t) + + //equals specs + plugin := getPluginConfig() + loki := flowslatest.FlowCollectorLoki{Manual: flowslatest.LokiManualParams{IngesterURL: "http://loki:3100/", TenantID: "netobserv"}} + spec := flowslatest.FlowCollectorSpec{ConsolePlugin: plugin, Loki: loki} + builder := newBuilder(testNamespace, testImage, &spec) + old, _, _ := builder.configMap() + nEw, _, _ := builder.configMap() + assert.Equal(old.Data, nEw.Data) + + //update loki + loki = flowslatest.FlowCollectorLoki{Manual: flowslatest.LokiManualParams{IngesterURL: "http://loki:3100/", TenantID: "netobserv", TLS: flowslatest.ClientTLS{ + Enable: true, + CACert: flowslatest.CertificateReference{ + Type: "configmap", + Name: "cm-name", + CertFile: "ca.crt", + }, + }}} + spec = flowslatest.FlowCollectorSpec{ConsolePlugin: plugin, Loki: loki} + builder = newBuilder(testNamespace, testImage, &spec) + nEw, _, _ = builder.configMap() + assert.NotEqual(old.Data, nEw.Data) old = nEw //set status url and enable default tls @@ -195,10 +222,8 @@ func TestContainerUpdateCheck(t *testing.T) { spec = flowslatest.FlowCollectorSpec{ConsolePlugin: plugin, Loki: loki} builder = newBuilder(testNamespace, testImage, &spec) - nEw = builder.deployment("digest") - report = helper.NewChangeReport("") - assert.True(helper.PodChanged(&old.Spec.Template, &nEw.Spec.Template, constants.PluginName, &report)) - assert.Contains(report.String(), "Container changed") + nEw, _, _ = builder.configMap() + assert.NotEqual(old.Data, nEw.Data) old = nEw //update status ca cert @@ -210,10 +235,8 @@ func TestContainerUpdateCheck(t *testing.T) { spec = flowslatest.FlowCollectorSpec{ConsolePlugin: plugin, Loki: loki} builder = newBuilder(testNamespace, testImage, &spec) - nEw = builder.deployment("digest") - report = helper.NewChangeReport("") - assert.True(helper.PodChanged(&old.Spec.Template, &nEw.Spec.Template, constants.PluginName, &report)) - assert.Contains(report.String(), "Volumes changed") + nEw, _, _ = builder.configMap() + assert.NotEqual(old.Data, nEw.Data) old = nEw //update status user cert @@ -226,13 +249,11 @@ func TestContainerUpdateCheck(t *testing.T) { spec = flowslatest.FlowCollectorSpec{ConsolePlugin: plugin, Loki: loki} builder = newBuilder(testNamespace, testImage, &spec) - nEw = builder.deployment("digest") - report = helper.NewChangeReport("") - assert.True(helper.PodChanged(&old.Spec.Template, &nEw.Spec.Template, constants.PluginName, &report)) - assert.Contains(report.String(), "Volumes changed") + nEw, _, _ = builder.configMap() + assert.NotEqual(old.Data, nEw.Data) } -func TestContainerUpdateWithLokistackMode(t *testing.T) { +func TestConfigMapUpdateWithLokistackMode(t *testing.T) { assert := assert.New(t) //equals specs @@ -240,21 +261,17 @@ func TestContainerUpdateWithLokistackMode(t *testing.T) { loki := flowslatest.FlowCollectorLoki{Mode: "LOKISTACK", LokiStack: &flowslatest.LokiStack{Name: "lokistack", Namespace: "ls-namespace"}} spec := flowslatest.FlowCollectorSpec{ConsolePlugin: plugin, Loki: loki} builder := newBuilder(testNamespace, testImage, &spec) - old := builder.deployment("digest") - nEw := builder.deployment("digest") - report := helper.NewChangeReport("") - assert.False(helper.PodChanged(&old.Spec.Template, &nEw.Spec.Template, constants.PluginName, &report)) - assert.Contains(report.String(), "no change") + old, _, _ := builder.configMap() + nEw, _, _ := builder.configMap() + assert.Equal(old.Data, nEw.Data) //update lokistack name loki.LokiStack.Name = "lokistack-updated" spec = flowslatest.FlowCollectorSpec{ConsolePlugin: plugin, Loki: loki} builder = newBuilder(testNamespace, testImage, &spec) - nEw = builder.deployment("digest") - report = helper.NewChangeReport("") - assert.True(helper.PodChanged(&old.Spec.Template, &nEw.Spec.Template, constants.PluginName, &report)) - assert.Contains(report.String(), "Volumes changed") + nEw, _, _ = builder.configMap() + assert.NotEqual(old.Data, nEw.Data) old = nEw //update lokistack namespace @@ -262,10 +279,8 @@ func TestContainerUpdateWithLokistackMode(t *testing.T) { spec = flowslatest.FlowCollectorSpec{ConsolePlugin: plugin, Loki: loki} builder = newBuilder(testNamespace, testImage, &spec) - nEw = builder.deployment("digest") - report = helper.NewChangeReport("") - assert.True(helper.PodChanged(&old.Spec.Template, &nEw.Spec.Template, constants.PluginName, &report)) - assert.Contains(report.String(), "Container changed") + nEw, _, _ = builder.configMap() + assert.NotEqual(old.Data, nEw.Data) } func TestServiceUpdateCheck(t *testing.T) { diff --git a/controllers/ebpf/agent_controller.go b/controllers/ebpf/agent_controller.go index a18656054..2fbb72e14 100644 --- a/controllers/ebpf/agent_controller.go +++ b/controllers/ebpf/agent_controller.go @@ -139,7 +139,7 @@ func (c *AgentController) Reconcile( return c.CreateOwned(ctx, desired) case actionUpdate: rlog.Info("action: update agent") - return c.UpdateOwned(ctx, current, desired) + return c.UpdateIfOwned(ctx, current, desired) default: rlog.Info("action: nothing to do") c.CheckDaemonSetInProgress(current) diff --git a/controllers/ebpf/internal/permissions/permissions.go b/controllers/ebpf/internal/permissions/permissions.go index f9903ccd0..4b41a8c31 100644 --- a/controllers/ebpf/internal/permissions/permissions.go +++ b/controllers/ebpf/internal/permissions/permissions.go @@ -88,7 +88,7 @@ func (c *Reconciler) reconcileNamespace(ctx context.Context) error { "pod-security.kubernetes.io/enforce": "privileged", }) { rlog.Info("updating namespace") - return c.UpdateOwned(ctx, actual, desired) + return c.UpdateIfOwned(ctx, actual, desired) } } rlog.Info("namespace is already reconciled. Doing nothing") @@ -176,7 +176,7 @@ func (c *Reconciler) reconcileOpenshiftPermissions( !equality.Semantic.DeepDerivative(&scc.AllowedCapabilities, &actual.AllowedCapabilities) { rlog.Info("updating SecurityContextConstraints") - return c.UpdateOwned(ctx, actual, scc) + return c.UpdateIfOwned(ctx, actual, scc) } rlog.Info("SecurityContextConstraints already reconciled. Doing nothing") return nil diff --git a/controllers/flowcollector_controller_console_test.go b/controllers/flowcollector_controller_console_test.go index 30f37b1c2..3ac5f7267 100644 --- a/controllers/flowcollector_controller_console_test.go +++ b/controllers/flowcollector_controller_console_test.go @@ -1,8 +1,6 @@ package controllers import ( - "fmt" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" operatorsv1 "github.com/openshift/api/operator/v1" @@ -137,13 +135,8 @@ func flowCollectorConsolePluginSpecs() { }, timeout, interval).Should(Equal(int32(9001))) By("Creating the console plugin configmap") - Eventually(func() interface{} { - ofc := v1.ConfigMap{} - if err := k8sClient.Get(ctx, configKey, &ofc); err != nil { - return err - } - return ofc.Data["config.yaml"] - }, timeout, interval).Should(ContainSubstring("portNaming:\n enable: true\n portNames:\n \"3100\": loki\nquickFilters:\n- name: Applications")) + Eventually(getConfigMapData(configKey), + timeout, interval).Should(ContainSubstring("url: http://loki:3100/")) }) It("Should update successfully", func() { @@ -210,22 +203,22 @@ func flowCollectorConsolePluginSpecs() { Context("Configuring the Loki URL", func() { It("Should be initially configured with default Loki URL", func() { - Eventually(getContainerArgumentAfter("netobserv-plugin", "-loki", cpKey), - timeout, interval).Should(Equal("http://loki:3100/")) + Eventually(getConfigMapData(configKey), + timeout, interval).Should(ContainSubstring("url: http://loki:3100/")) }) It("Should update the Loki URL in the Console Plugin if it changes in the Spec", func() { UpdateCR(crKey, func(fc *flowslatest.FlowCollector) { fc.Spec.Loki.Manual.IngesterURL = "http://loki.namespace:8888" }) - Eventually(getContainerArgumentAfter("netobserv-plugin", "-loki", cpKey), - timeout, interval).Should(Equal("http://loki.namespace:8888")) + Eventually(getConfigMapData(configKey), + timeout, interval).Should(ContainSubstring("url: http://loki.namespace:8888")) }) It("Should use the Loki Querier URL instead of the Loki URL, if the first is defined", func() { UpdateCR(crKey, func(fc *flowslatest.FlowCollector) { fc.Spec.Loki.Manual.QuerierURL = "http://loki-querier:6789" }) - Eventually(getContainerArgumentAfter("netobserv-plugin", "-loki", cpKey), - timeout, interval).Should(Equal("http://loki-querier:6789")) + Eventually(getConfigMapData(configKey), + timeout, interval).Should(ContainSubstring("url: http://loki-querier:6789")) }) }) @@ -361,30 +354,12 @@ func flowCollectorConsolePluginSpecs() { }) } -func getContainerArgumentAfter(containerName, argName string, dpKey types.NamespacedName) func() interface{} { +func getConfigMapData(configKey types.NamespacedName) func() interface{} { return func() interface{} { - deployment := appsv1.Deployment{} - if err := k8sClient.Get(ctx, dpKey, &deployment); err != nil { + ofc := v1.ConfigMap{} + if err := k8sClient.Get(ctx, configKey, &ofc); err != nil { return err } - for i := range deployment.Spec.Template.Spec.Containers { - cnt := &deployment.Spec.Template.Spec.Containers[i] - if cnt.Name == containerName { - args := cnt.Args - for len(args) > 0 { - if args[0] == argName { - if len(args) < 2 { - return fmt.Errorf("container %q: arg %v has no value. Actual args: %v", - containerName, argName, cnt.Args) - } - return args[1] - } - args = args[1:] - } - return fmt.Errorf("container %q: arg %v not found. Actual args: %v", - containerName, argName, cnt.Args) - } - } - return fmt.Errorf("container not found: %v", containerName) + return ofc.Data["config.yaml"] } } diff --git a/controllers/flowlogspipeline/flp_ingest_reconciler.go b/controllers/flowlogspipeline/flp_ingest_reconciler.go index 11476981c..195dd465b 100644 --- a/controllers/flowlogspipeline/flp_ingest_reconciler.go +++ b/controllers/flowlogspipeline/flp_ingest_reconciler.go @@ -100,7 +100,7 @@ func (r *flpIngesterReconciler) reconcile(ctx context.Context, desired *flowslat return err } } else if !equality.Semantic.DeepDerivative(newCM.Data, r.owned.configMap.Data) { - if err := r.UpdateOwned(ctx, r.owned.configMap, newCM); err != nil { + if err := r.UpdateIfOwned(ctx, r.owned.configMap, newCM); err != nil { return err } } @@ -156,7 +156,7 @@ func (r *flpIngesterReconciler) reconcileDaemonSet(ctx context.Context, desiredD if !r.Managed.Exists(r.owned.daemonSet) { return r.CreateOwned(ctx, desiredDS) } else if helper.PodChanged(&r.owned.daemonSet.Spec.Template, &desiredDS.Spec.Template, constants.FLPName, &report) { - return r.UpdateOwned(ctx, r.owned.daemonSet, desiredDS) + return r.UpdateIfOwned(ctx, r.owned.daemonSet, desiredDS) } else { // DaemonSet up to date, check if it's ready r.CheckDaemonSetInProgress(r.owned.daemonSet) diff --git a/controllers/flowlogspipeline/flp_monolith_reconciler.go b/controllers/flowlogspipeline/flp_monolith_reconciler.go index 28cc75972..bda650104 100644 --- a/controllers/flowlogspipeline/flp_monolith_reconciler.go +++ b/controllers/flowlogspipeline/flp_monolith_reconciler.go @@ -103,7 +103,7 @@ func (r *flpMonolithReconciler) reconcile(ctx context.Context, desired *flowslat return err } } else if !equality.Semantic.DeepDerivative(newCM.Data, r.owned.configMap.Data) { - if err := r.UpdateOwned(ctx, r.owned.configMap, newCM); err != nil { + if err := r.UpdateIfOwned(ctx, r.owned.configMap, newCM); err != nil { return err } } @@ -165,7 +165,7 @@ func (r *flpMonolithReconciler) reconcileDaemonSet(ctx context.Context, desiredD if !r.Managed.Exists(r.owned.daemonSet) { return r.CreateOwned(ctx, desiredDS) } else if helper.PodChanged(&r.owned.daemonSet.Spec.Template, &desiredDS.Spec.Template, constants.FLPName, &report) { - return r.UpdateOwned(ctx, r.owned.daemonSet, desiredDS) + return r.UpdateIfOwned(ctx, r.owned.daemonSet, desiredDS) } else { // DaemonSet up to date, check if it's ready r.CheckDaemonSetInProgress(r.owned.daemonSet) diff --git a/controllers/flowlogspipeline/flp_transfo_reconciler.go b/controllers/flowlogspipeline/flp_transfo_reconciler.go index fed144b96..c49e41883 100644 --- a/controllers/flowlogspipeline/flp_transfo_reconciler.go +++ b/controllers/flowlogspipeline/flp_transfo_reconciler.go @@ -104,7 +104,7 @@ func (r *flpTransformerReconciler) reconcile(ctx context.Context, desired *flows return err } } else if !equality.Semantic.DeepDerivative(newCM.Data, r.owned.configMap.Data) { - if err := r.UpdateOwned(ctx, r.owned.configMap, newCM); err != nil { + if err := r.UpdateIfOwned(ctx, r.owned.configMap, newCM); err != nil { return err } } @@ -151,7 +151,7 @@ func (r *flpTransformerReconciler) reconcileDeployment(ctx context.Context, desi return err } } else if helper.DeploymentChanged(r.owned.deployment, newDep, constants.FLPName, helper.HPADisabled(&desiredFLP.KafkaConsumerAutoscaler), helper.PtrInt32(desiredFLP.KafkaConsumerReplicas), &report) { - if err := r.UpdateOwned(ctx, r.owned.deployment, newDep); err != nil { + if err := r.UpdateIfOwned(ctx, r.owned.deployment, newDep); err != nil { return err } } else { @@ -169,7 +169,7 @@ func (r *flpTransformerReconciler) reconcileDeployment(ctx context.Context, desi return err } } else if helper.AutoScalerChanged(r.owned.hpa, desiredFLP.KafkaConsumerAutoscaler, &report) { - if err := r.UpdateOwned(ctx, r.owned.hpa, newASC); err != nil { + if err := r.UpdateIfOwned(ctx, r.owned.hpa, newASC); err != nil { return err } } diff --git a/controllers/reconcilers/common.go b/controllers/reconcilers/common.go index ce22b8cee..ece86e7a3 100644 --- a/controllers/reconcilers/common.go +++ b/controllers/reconcilers/common.go @@ -73,7 +73,7 @@ func (c *Common) ReconcileClusterRoleBinding(ctx context.Context, desired *rbacv // cluster role binding already reconciled. Exiting return nil } - return c.UpdateOwned(ctx, &actual, desired) + return c.UpdateIfOwned(ctx, &actual, desired) } func (c *Common) ReconcileRoleBinding(ctx context.Context, desired *rbacv1.RoleBinding) error { @@ -100,7 +100,7 @@ func (c *Common) ReconcileRoleBinding(ctx context.Context, desired *rbacv1.RoleB // role binding already reconciled. Exiting return nil } - return c.UpdateOwned(ctx, &actual, desired) + return c.UpdateIfOwned(ctx, &actual, desired) } func (c *Common) ReconcileClusterRole(ctx context.Context, desired *rbacv1.ClusterRole) error { @@ -118,7 +118,7 @@ func (c *Common) ReconcileClusterRole(ctx context.Context, desired *rbacv1.Clust return nil } - return c.UpdateOwned(ctx, &actual, desired) + return c.UpdateIfOwned(ctx, &actual, desired) } func (c *Common) ReconcileRole(ctx context.Context, desired *rbacv1.Role) error { @@ -136,7 +136,7 @@ func (c *Common) ReconcileRole(ctx context.Context, desired *rbacv1.Role) error return nil } - return c.UpdateOwned(ctx, &actual, desired) + return c.UpdateIfOwned(ctx, &actual, desired) } func (c *Common) ReconcileConfigMap(ctx context.Context, desired *corev1.ConfigMap, delete bool) error { @@ -161,7 +161,7 @@ func (c *Common) ReconcileConfigMap(ctx context.Context, desired *corev1.ConfigM return nil } - return c.UpdateOwned(ctx, &actual, desired) + return c.UpdateIfOwned(ctx, &actual, desired) } func (i *Instance) ReconcileService(ctx context.Context, old, new *corev1.Service, report *helper.ChangeReport) error { @@ -174,7 +174,7 @@ func (i *Instance) ReconcileService(ctx context.Context, old, new *corev1.Servic newSVC := old.DeepCopy() newSVC.Spec.Ports = new.Spec.Ports newSVC.ObjectMeta.Annotations = new.ObjectMeta.Annotations - if err := i.UpdateOwned(ctx, old, newSVC); err != nil { + if err := i.UpdateIfOwned(ctx, old, newSVC); err != nil { return err } } @@ -186,7 +186,7 @@ func GenericReconcile[K client.Object](ctx context.Context, m *NamespacedObjectM return cl.CreateOwned(ctx, new) } if changeFunc(old, new, report) { - return cl.UpdateOwned(ctx, old, new) + return cl.UpdateIfOwned(ctx, old, new) } return nil } diff --git a/pkg/helper/client_helper.go b/pkg/helper/client_helper.go index 5464cad19..ee2a0c923 100644 --- a/pkg/helper/client_helper.go +++ b/pkg/helper/client_helper.go @@ -76,6 +76,18 @@ func (c *Client) UpdateOwned(ctx context.Context, old, obj client.Object) error return nil } +// UpdateIfOwned is an helper function that updates an object if currently owned by the operator +func (c *Client) UpdateIfOwned(ctx context.Context, old, obj client.Object) error { + log := log.FromContext(ctx) + + if old != nil && !IsOwned(old) { + kind := reflect.TypeOf(obj).String() + log.Info("SKIP "+kind+" update since not owned", "Namespace", obj.GetNamespace(), "Name", obj.GetName()) + return nil + } + return c.UpdateOwned(ctx, old, obj) +} + func (c *Client) CheckDeploymentInProgress(d *appsv1.Deployment) { if d.Status.UpdatedReplicas < d.Status.Replicas { c.SetInProgress(true)