diff --git a/build/yamls/antrea-aks.yml b/build/yamls/antrea-aks.yml index ab28248434a..e3e30a94898 100644 --- a/build/yamls/antrea-aks.yml +++ b/build/yamls/antrea-aks.yml @@ -1239,6 +1239,11 @@ spec: name: Live-Traffic priority: 10 type: boolean + - description: Capture only the dropped packet. + jsonPath: .spec.droppedOnly + name: Dropped-Only + priority: 10 + type: boolean - description: Timeout in seconds. jsonPath: .spec.timeout name: Timeout @@ -1265,6 +1270,8 @@ spec: service: type: string type: object + droppedOnly: + type: boolean liveTraffic: type: boolean packet: diff --git a/build/yamls/antrea-eks.yml b/build/yamls/antrea-eks.yml index ba8ea0b439b..c03c714bce4 100644 --- a/build/yamls/antrea-eks.yml +++ b/build/yamls/antrea-eks.yml @@ -1239,6 +1239,11 @@ spec: name: Live-Traffic priority: 10 type: boolean + - description: Capture only the dropped packet. + jsonPath: .spec.droppedOnly + name: Dropped-Only + priority: 10 + type: boolean - description: Timeout in seconds. jsonPath: .spec.timeout name: Timeout @@ -1265,6 +1270,8 @@ spec: service: type: string type: object + droppedOnly: + type: boolean liveTraffic: type: boolean packet: diff --git a/build/yamls/antrea-gke.yml b/build/yamls/antrea-gke.yml index 3fcb516cc41..e3469606e4b 100644 --- a/build/yamls/antrea-gke.yml +++ b/build/yamls/antrea-gke.yml @@ -1239,6 +1239,11 @@ spec: name: Live-Traffic priority: 10 type: boolean + - description: Capture only the dropped packet. + jsonPath: .spec.droppedOnly + name: Dropped-Only + priority: 10 + type: boolean - description: Timeout in seconds. jsonPath: .spec.timeout name: Timeout @@ -1265,6 +1270,8 @@ spec: service: type: string type: object + droppedOnly: + type: boolean liveTraffic: type: boolean packet: diff --git a/build/yamls/antrea-ipsec.yml b/build/yamls/antrea-ipsec.yml index ccf2de6c5f0..2714c3eab34 100644 --- a/build/yamls/antrea-ipsec.yml +++ b/build/yamls/antrea-ipsec.yml @@ -1239,6 +1239,11 @@ spec: name: Live-Traffic priority: 10 type: boolean + - description: Capture only the dropped packet. + jsonPath: .spec.droppedOnly + name: Dropped-Only + priority: 10 + type: boolean - description: Timeout in seconds. jsonPath: .spec.timeout name: Timeout @@ -1265,6 +1270,8 @@ spec: service: type: string type: object + droppedOnly: + type: boolean liveTraffic: type: boolean packet: diff --git a/build/yamls/antrea.yml b/build/yamls/antrea.yml index 82663c0acbe..e5f40555c13 100644 --- a/build/yamls/antrea.yml +++ b/build/yamls/antrea.yml @@ -1239,6 +1239,11 @@ spec: name: Live-Traffic priority: 10 type: boolean + - description: Capture only the dropped packet. + jsonPath: .spec.droppedOnly + name: Dropped-Only + priority: 10 + type: boolean - description: Timeout in seconds. jsonPath: .spec.timeout name: Timeout @@ -1265,6 +1270,8 @@ spec: service: type: string type: object + droppedOnly: + type: boolean liveTraffic: type: boolean packet: diff --git a/build/yamls/base/crds.yml b/build/yamls/base/crds.yml index 11ef27d1b2d..006fc4c8a5e 100644 --- a/build/yamls/base/crds.yml +++ b/build/yamls/base/crds.yml @@ -78,6 +78,11 @@ spec: name: Live-Traffic type: boolean priority: 10 + - jsonPath: .spec.droppedOnly + description: Capture only the dropped packet. + name: Dropped-Only + type: boolean + priority: 10 - jsonPath: .spec.timeout description: Timeout in seconds. name: Timeout @@ -172,6 +177,8 @@ spec: type: integer liveTraffic: type: boolean + droppedOnly: + type: boolean timeout: type: integer status: diff --git a/pkg/agent/controller/traceflow/packetin.go b/pkg/agent/controller/traceflow/packetin.go index ae0cfcbbee2..2a286ef0dd7 100644 --- a/pkg/agent/controller/traceflow/packetin.go +++ b/pkg/agent/controller/traceflow/packetin.go @@ -108,7 +108,10 @@ func (c *Controller) parsePacketIn(pktIn *ofctrl.PacketIn) (*crdv1alpha1.Tracefl // Uninstall the OVS flows after receiving the first packet, to // avoid capturing too many matched packets. c.ofClient.UninstallTraceflowFlows(tag) - if tfState.isSender { + // Report the captured dropped packet, if the Traceflow is for + // the dropped packet only; otherwise only the sender reports + // the first captured packet. + if tfState.isSender || tfState.droppedOnly { capturedPacket = parseCapturedPacket(pktIn) } } diff --git a/pkg/agent/controller/traceflow/traceflow_controller.go b/pkg/agent/controller/traceflow/traceflow_controller.go index 6d542552294..423df3e54b2 100644 --- a/pkg/agent/controller/traceflow/traceflow_controller.go +++ b/pkg/agent/controller/traceflow/traceflow_controller.go @@ -74,6 +74,7 @@ type traceflowState struct { name string tag uint8 liveTraffic bool + droppedOnly bool isSender bool // Agent received the first Traceflow packet from OVS. receivedPacket bool @@ -308,6 +309,9 @@ func (c *Controller) startTraceflow(tf *crdv1alpha1.Traceflow) error { srcOFPort = uint32(podInterfaces[0].OFPort) // On the source Node, trace the first packet of the first // connection that matches the Traceflow spec. + // TODO: support specifying only the Destination Pod for + // live-traffic Traceflow, which will trace the matched traffic + // to the destination Pod from any source. if liveTraffic { matchPacket = packet } @@ -316,7 +320,10 @@ func (c *Controller) startTraceflow(tf *crdv1alpha1.Traceflow) error { // Store Traceflow to cache. c.runningTraceflowsMutex.Lock() - tfState := traceflowState{name: tf.Name, tag: tf.Status.DataplaneTag, liveTraffic: tf.Spec.LiveTraffic, isSender: isSender} + tfState := traceflowState{ + name: tf.Name, tag: tf.Status.DataplaneTag, + liveTraffic: liveTraffic, droppedOnly: tf.Spec.DroppedOnly && liveTraffic, + isSender: isSender} c.runningTraceflows[tfState.tag] = &tfState c.runningTraceflowsMutex.Unlock() @@ -326,7 +333,7 @@ func (c *Controller) startTraceflow(tf *crdv1alpha1.Traceflow) error { if timeout == 0 { timeout = crdv1alpha1.DefaultTraceflowTimeout } - err = c.ofClient.InstallTraceflowFlows(tfState.tag, liveTraffic, matchPacket, srcOFPort, timeout) + err = c.ofClient.InstallTraceflowFlows(tfState.tag, liveTraffic, tfState.droppedOnly, matchPacket, srcOFPort, timeout) if err != nil { return err } @@ -506,8 +513,8 @@ func (c *Controller) preparePacket(tf *crdv1alpha1.Traceflow, intf *interfacesto } } - if packet.IPProto == 0 || packet.IPProto == protocol.Type_ICMP || packet.IPProto == protocol.Type_IPv6ICMP { - // IPProto defaults to ICMP. + // Defaults to ICMP if not live-traffic Traceflow. + if packet.IPProto == 0 && !liveTraffic || packet.IPProto == protocol.Type_ICMP || packet.IPProto == protocol.Type_IPv6ICMP { isICMP = true } if isICMP { diff --git a/pkg/agent/openflow/client.go b/pkg/agent/openflow/client.go index 1d534387dc2..a07b6eb6b4d 100644 --- a/pkg/agent/openflow/client.go +++ b/pkg/agent/openflow/client.go @@ -226,7 +226,7 @@ type Client interface { SendTraceflowPacket(dataplaneTag uint8, packet *binding.Packet, inPort uint32, outPort int32) error // InstallTraceflowFlows installs flows for a Traceflow request. - InstallTraceflowFlows(dataplaneTag uint8, liveTraffic bool, packet *binding.Packet, srcOFPort uint32, timeoutSeconds uint16) error + InstallTraceflowFlows(dataplaneTag uint8, liveTraffic, droppedOnly bool, packet *binding.Packet, srcOFPort uint32, timeoutSeconds uint16) error // UninstallTraceflowFlows uninstalls flows for a Traceflow request. UninstallTraceflowFlows(dataplaneTag uint8) error @@ -859,11 +859,11 @@ func (c *client) SendTraceflowPacket(dataplaneTag uint8, packet *binding.Packet, return c.bridge.SendPacketOut(packetOutObj) } -func (c *client) InstallTraceflowFlows(dataplaneTag uint8, liveTraffic bool, packet *binding.Packet, srcOFPort uint32, timeoutSeconds uint16) error { +func (c *client) InstallTraceflowFlows(dataplaneTag uint8, liveTraffic, droppedOnly bool, packet *binding.Packet, srcOFPort uint32, timeoutSeconds uint16) error { cacheKey := fmt.Sprintf("%x", dataplaneTag) flows := []binding.Flow{} flows = append(flows, c.traceflowConnectionTrackFlows(dataplaneTag, packet, srcOFPort, timeoutSeconds, cookie.Default)...) - flows = append(flows, c.traceflowL2ForwardOutputFlows(dataplaneTag, liveTraffic, timeoutSeconds, cookie.Default)...) + flows = append(flows, c.traceflowL2ForwardOutputFlows(dataplaneTag, liveTraffic, droppedOnly, timeoutSeconds, cookie.Default)...) flows = append(flows, c.traceflowNetworkPolicyFlows(dataplaneTag, timeoutSeconds, cookie.Default)...) return c.addFlows(c.tfFlowCache, cacheKey, flows) } diff --git a/pkg/agent/openflow/client_test.go b/pkg/agent/openflow/client_test.go index a4bb9b4b293..d0aba4cc9f6 100644 --- a/pkg/agent/openflow/client_test.go +++ b/pkg/agent/openflow/client_test.go @@ -280,7 +280,7 @@ func Test_client_InstallTraceflowFlows(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() c := tt.prepareFunc(ctrl) - if err := c.InstallTraceflowFlows(tt.args.dataplaneTag, false, nil, 0, 300); (err != nil) != tt.wantErr { + if err := c.InstallTraceflowFlows(tt.args.dataplaneTag, false, false, nil, 0, 300); (err != nil) != tt.wantErr { t.Errorf("InstallTraceflowFlows() error = %v, wantErr %v", err, tt.wantErr) } }) diff --git a/pkg/agent/openflow/pipeline.go b/pkg/agent/openflow/pipeline.go index e5f57e5a3fc..936f0ee05eb 100644 --- a/pkg/agent/openflow/pipeline.go +++ b/pkg/agent/openflow/pipeline.go @@ -936,54 +936,59 @@ func (c *client) l2ForwardCalcFlow(dstMAC net.HardwareAddr, ofPort uint32, skipI // traceflowL2ForwardOutputFlows generates Traceflow specific flows that outputs traceflow packets // to OVS port and Antrea Agent after L2forwarding calculation. -func (c *client) traceflowL2ForwardOutputFlows(dataplaneTag uint8, liveTraffic bool, timeout uint16, category cookie.Category) []binding.Flow { +func (c *client) traceflowL2ForwardOutputFlows(dataplaneTag uint8, liveTraffic, droppedOnly bool, timeout uint16, category cookie.Category) []binding.Flow { flows := []binding.Flow{} l2FwdOutTable := c.pipeline[L2ForwardingOutTable] for _, ipProtocol := range []binding.Protocol{binding.ProtocolIP, binding.ProtocolIPv6} { if c.encapMode.SupportsEncap() { // SendToController and Output if output port is tunnel port. - flows = append(flows, l2FwdOutTable.BuildFlow(priorityNormal+3). + fb1 := l2FwdOutTable.BuildFlow(priorityNormal+3). MatchReg(int(PortCacheReg), config.DefaultTunOFPort). MatchIPDSCP(dataplaneTag). SetHardTimeout(timeout). MatchProtocol(ipProtocol). MatchRegRange(int(marksReg), portFoundMark, ofPortMarkRange). Action().OutputRegRange(int(PortCacheReg), ofPortRegRange). - Action().SendToController(uint8(PacketInReasonTF)). - Cookie(c.cookieAllocator.Request(category).Raw()). - Done()) + Cookie(c.cookieAllocator.Request(category).Raw()) // For injected packets, only SendToController if output port is local // gateway. In encapMode, a Traceflow packet going out of the gateway // port (i.e. exiting the overlay) essentially means that the Traceflow // request is complete. - flowBuilder := l2FwdOutTable.BuildFlow(priorityNormal+2). + fb2 := l2FwdOutTable.BuildFlow(priorityNormal+2). MatchReg(int(PortCacheReg), config.HostGatewayOFPort). MatchIPDSCP(dataplaneTag). SetHardTimeout(timeout). MatchProtocol(ipProtocol). MatchRegRange(int(marksReg), portFoundMark, ofPortMarkRange). - Action().SendToController(uint8(PacketInReasonTF)). Cookie(c.cookieAllocator.Request(category).Raw()) + + // Do not send to controller if captures only dropped packet. + if !droppedOnly { + fb1 = fb1.Action().SendToController(uint8(PacketInReasonTF)) + fb2 = fb2.Action().SendToController(uint8(PacketInReasonTF)) + } if liveTraffic { // Clear the loaded DSCP bits before output. - flowBuilder = flowBuilder.Action().LoadIPDSCP(0). + fb2 = fb2.Action().LoadIPDSCP(0). Action().OutputRegRange(int(PortCacheReg), ofPortRegRange) } - flows = append(flows, flowBuilder.Done()) + flows = append(flows, fb1.Done(), fb2.Done()) } else { // SendToController and Output if output port is local gateway. Unlike in // encapMode, inter-Node Pod-to-Pod traffic is expected to go out of the // gateway port on the way to its destination. - flows = append(flows, l2FwdOutTable.BuildFlow(priorityNormal+2). + fb1 := l2FwdOutTable.BuildFlow(priorityNormal+2). MatchReg(int(PortCacheReg), config.HostGatewayOFPort). MatchIPDSCP(dataplaneTag). SetHardTimeout(timeout). MatchProtocol(ipProtocol). MatchRegRange(int(marksReg), portFoundMark, ofPortMarkRange). Action().OutputRegRange(int(PortCacheReg), ofPortRegRange). - Action().SendToController(uint8(PacketInReasonTF)). - Cookie(c.cookieAllocator.Request(category).Raw()). - Done()) + Cookie(c.cookieAllocator.Request(category).Raw()) + if !droppedOnly { + fb1 = fb1.Action().SendToController(uint8(PacketInReasonTF)) + } + flows = append(flows, fb1.Done()) } // Only SendToController if output port is local gateway and destination IP is gateway. gatewayIP := c.nodeConfig.GatewayConfig.IPv4 @@ -991,34 +996,38 @@ func (c *client) traceflowL2ForwardOutputFlows(dataplaneTag uint8, liveTraffic b gatewayIP = c.nodeConfig.GatewayConfig.IPv6 } if gatewayIP != nil { - flowBuilder := l2FwdOutTable.BuildFlow(priorityNormal+3). + fb := l2FwdOutTable.BuildFlow(priorityNormal+3). MatchReg(int(PortCacheReg), config.HostGatewayOFPort). MatchDstIP(gatewayIP). MatchIPDSCP(dataplaneTag). SetHardTimeout(timeout). MatchProtocol(ipProtocol). MatchRegRange(int(marksReg), portFoundMark, ofPortMarkRange). - Action().SendToController(uint8(PacketInReasonTF)). Cookie(c.cookieAllocator.Request(category).Raw()) + if !droppedOnly { + fb = fb.Action().SendToController(uint8(PacketInReasonTF)) + } if liveTraffic { - flowBuilder = flowBuilder.Action().LoadIPDSCP(0). + fb = fb.Action().LoadIPDSCP(0). Action().OutputRegRange(int(PortCacheReg), ofPortRegRange) } - flows = append(flows, flowBuilder.Done()) + flows = append(flows, fb.Done()) } // Only SendToController if output port is Pod port. - flowBuilder := l2FwdOutTable.BuildFlow(priorityNormal+2). + fb := l2FwdOutTable.BuildFlow(priorityNormal+2). MatchIPDSCP(dataplaneTag). SetHardTimeout(timeout). MatchProtocol(ipProtocol). MatchRegRange(int(marksReg), portFoundMark, ofPortMarkRange). - Action().SendToController(uint8(PacketInReasonTF)). Cookie(c.cookieAllocator.Request(category).Raw()) + if !droppedOnly { + fb = fb.Action().SendToController(uint8(PacketInReasonTF)) + } if liveTraffic { - flowBuilder = flowBuilder.Action().LoadIPDSCP(0). + fb = fb.Action().LoadIPDSCP(0). Action().OutputRegRange(int(PortCacheReg), ofPortRegRange) } - flows = append(flows, flowBuilder.Done()) + flows = append(flows, fb.Done()) } return flows } diff --git a/pkg/agent/openflow/testing/mock_openflow.go b/pkg/agent/openflow/testing/mock_openflow.go index 5eedd724f02..01a605ddc61 100644 --- a/pkg/agent/openflow/testing/mock_openflow.go +++ b/pkg/agent/openflow/testing/mock_openflow.go @@ -448,17 +448,17 @@ func (mr *MockClientMockRecorder) InstallServiceGroup(arg0, arg1, arg2 interface } // InstallTraceflowFlows mocks base method -func (m *MockClient) InstallTraceflowFlows(arg0 byte, arg1 bool, arg2 *openflow.Packet, arg3 uint32, arg4 uint16) error { +func (m *MockClient) InstallTraceflowFlows(arg0 byte, arg1, arg2 bool, arg3 *openflow.Packet, arg4 uint32, arg5 uint16) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "InstallTraceflowFlows", arg0, arg1, arg2, arg3, arg4) + ret := m.ctrl.Call(m, "InstallTraceflowFlows", arg0, arg1, arg2, arg3, arg4, arg5) ret0, _ := ret[0].(error) return ret0 } // InstallTraceflowFlows indicates an expected call of InstallTraceflowFlows -func (mr *MockClientMockRecorder) InstallTraceflowFlows(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) InstallTraceflowFlows(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstallTraceflowFlows", reflect.TypeOf((*MockClient)(nil).InstallTraceflowFlows), arg0, arg1, arg2, arg3, arg4) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstallTraceflowFlows", reflect.TypeOf((*MockClient)(nil).InstallTraceflowFlows), arg0, arg1, arg2, arg3, arg4, arg5) } // IsConnected mocks base method diff --git a/pkg/antctl/raw/traceflow/command.go b/pkg/antctl/raw/traceflow/command.go index d9163009f64..1beda488a5d 100644 --- a/pkg/antctl/raw/traceflow/command.go +++ b/pkg/antctl/raw/traceflow/command.go @@ -49,8 +49,9 @@ var ( outputType string flow string liveTraffic bool - nowait bool + droppedOnly bool timeout time.Duration + nowait bool }{} ) @@ -85,18 +86,18 @@ func init() { Short: "Start a Traceflows", Long: "Start a Traceflows from one Pod to another Pod/Service/IP.", Aliases: []string{"tf", "traceflows"}, - Example: ` Start a Traceflow from busybox0 to busybox1, both Pods are in Namespace default - $antctl traceflow -S busybox0 -D busybox1 - Start a Traceflow from busybox0 to destination IP, source is in Namespace default - $antctl traceflow -S busybox0 -D 123.123.123.123 - Start a Traceflow from busybox0 to destination Service, source and destination are in Namespace default - $antctl traceflow -S busybox0 -D svc0 -f tcp,tcp_dst=80,tcp_flags=2 - Start a Traceflow from busybox0 in Namespace ns0 to busybox1 in Namespace ns1, output type is json - $antctl traceflow -S ns0/busybox0 -D ns1/busybox1 -o json - Start a Traceflow from busybox0 to busybox1, with a TCP packet to destination port 80 - $antctl traceflow -S busybox0 -D busybox1 -f tcp,tcp_dst=80 - Start a Traceflow for live TCP traffic from busybox0 to TCP port 80, with 1 minute timeout - $antctl traceflow -S busybox0 -f tcp,tcp_dst=80 --live-traffic -t 1m + Example: ` Start a Traceflow from pod1 to pod2, both Pods are in Namespace default + $antctl traceflow -S pod1 -D pod2 + Start a Traceflow from pod1 in Namepace ns1 to a destination IP + $antctl traceflow -S ns1/pod1 -D 123.123.123.123 + Start a Traceflow from pod1 to Service svc1 in Namespace ns1 + $antctl traceflow -S pod1 -D ns1/svc1 -f tcp,tcp_dst=80 + Start a Traceflow from pod1 to pod2, with a UDP packet to destination port 1234 + $antctl traceflow -S pod1 -D pod2 -f udp,udp_dst=1234 + Start a Traceflow for live TCP traffic from pod1 to svc1, with 1 minute timeout + $antctl traceflow -S pod1 -D svc1 -f tcp --live-traffic -t 1m + Start a Traceflow to capture the first dropped TCP packet from pod1 to port 80 within 10 minutes + $antctl traceflow -S pod1 -f tcp,tcp_dst=80 --live-traffic --dropped-only -t 10m `, RunE: runE, } @@ -106,6 +107,7 @@ func init() { Command.Flags().StringVarP(&option.outputType, "output", "o", "yaml", "output type: yaml (default), json") Command.Flags().StringVarP(&option.flow, "flow", "f", "", "specify the flow (packet headers) of the Traceflow packet, including tcp_src, tcp_dst, tcp_flags, udp_src, udp_dst, ipv6") Command.Flags().BoolVarP(&option.liveTraffic, "live-traffic", "L", false, "if set, the Traceflow will trace the first packet of the matched live traffic flow") + Command.Flags().BoolVarP(&option.droppedOnly, "dropped-only", "", false, "if set, capture only the dropped packet in a live-traffic Traceflow") Command.Flags().BoolVarP(&option.nowait, "nowait", "", false, "if set, command returns without retrieving results") } @@ -129,6 +131,11 @@ func runE(cmd *cobra.Command, _ []string) error { return nil } + if !option.liveTraffic && option.droppedOnly { + fmt.Println("--dropped-only works only with live-traffic Traceflow") + return nil + } + kubeconfigPath, err := cmd.Flags().GetString("kubeconfig") if err != nil { return err @@ -257,8 +264,9 @@ func newTraceflow(client kubernetes.Interface) (*v1alpha1.Traceflow, error) { Source: src, Destination: dst, Packet: *pkt, - Timeout: uint16(option.timeout.Seconds()), LiveTraffic: option.liveTraffic, + DroppedOnly: option.droppedOnly, + Timeout: uint16(option.timeout.Seconds()), }, } return tf, nil diff --git a/pkg/apis/crd/v1alpha1/types.go b/pkg/apis/crd/v1alpha1/types.go index 935a68880a5..db5d75e2b1b 100644 --- a/pkg/apis/crd/v1alpha1/types.go +++ b/pkg/apis/crd/v1alpha1/types.go @@ -115,6 +115,9 @@ type TraceflowSpec struct { // rather than an injected packet, when set to true. The first packet of // the first connection that matches the packet spec will be traced. LiveTraffic bool `json:"liveTraffic,omitempty"` + // DroppedOnly indicates only the dropped packet should be captured in a + // live-traffic Traceflow. + DroppedOnly bool `json:"droppedOnly,omitempty"` // Timeout specifies the timeout of the Traceflow in seconds. Defaults // to 20 seconds if not set. Timeout uint16 `json:"timeout,omitempty"` diff --git a/pkg/controller/traceflow/controller.go b/pkg/controller/traceflow/controller.go index 8aa5f140295..2e074d3b6bb 100644 --- a/pkg/controller/traceflow/controller.go +++ b/pkg/controller/traceflow/controller.go @@ -282,33 +282,45 @@ func (c *Controller) startTraceflow(tf *crdv1alpha1.Traceflow) error { } func (c *Controller) checkTraceflowStatus(tf *crdv1alpha1.Traceflow) error { - sender := false - receiver := false - for i, nodeResult := range tf.Status.Results { - for j, ob := range nodeResult.Observations { - if ob.Component == crdv1alpha1.ComponentSpoofGuard { - sender = true - } - if ob.Action == crdv1alpha1.ActionDelivered || ob.Action == crdv1alpha1.ActionDropped || ob.Action == crdv1alpha1.ActionForwardedOutOfOverlay { - receiver = true - } - if ob.TranslatedDstIP != "" { - // Add Pod ns/name to observation if TranslatedDstIP (a.k.a. Service Endpoint address) is Pod IP. - pods, err := c.podInformer.Informer().GetIndexer().ByIndex(podIPsIndex, ob.TranslatedDstIP) - if err != nil { - klog.Infof("Unable to find Pod from IP, error: %+v", err) - } else if len(pods) > 0 { - pod, ok := pods[0].(*corev1.Pod) - if !ok { - klog.Warningf("Invalid Pod obj in cache") - } else { - tf.Status.Results[i].Observations[j].Pod = fmt.Sprintf("%s/%s", pod.Namespace, pod.Name) + succeeded := false + if tf.Spec.LiveTraffic && tf.Spec.DroppedOnly { + // There should be only one reported NodeResult for droppedOnly + // Traceflow. + if len(tf.Status.Results) > 0 { + succeeded = true + } + } else { + sender := false + receiver := false + for i, nodeResult := range tf.Status.Results { + for j, ob := range nodeResult.Observations { + if ob.Component == crdv1alpha1.ComponentSpoofGuard { + sender = true + } + if ob.Action == crdv1alpha1.ActionDelivered || + ob.Action == crdv1alpha1.ActionDropped || + ob.Action == crdv1alpha1.ActionForwardedOutOfOverlay { + receiver = true + } + if ob.TranslatedDstIP != "" { + // Add Pod ns/name to observation if TranslatedDstIP (a.k.a. Service Endpoint address) is Pod IP. + pods, err := c.podInformer.Informer().GetIndexer().ByIndex(podIPsIndex, ob.TranslatedDstIP) + if err != nil { + klog.Infof("Unable to find Pod from IP, error: %+v", err) + } else if len(pods) > 0 { + pod, ok := pods[0].(*corev1.Pod) + if !ok { + klog.Warningf("Invalid Pod obj in cache") + } else { + tf.Status.Results[i].Observations[j].Pod = fmt.Sprintf("%s/%s", pod.Namespace, pod.Name) + } } } } } + succeeded = sender && receiver } - if sender && receiver { + if succeeded { c.deallocateTagForTF(tf) return c.updateTraceflowStatus(tf, crdv1alpha1.Succeeded, "", 0) }