From 30ec43ef391880b3f8cc87450ccad285b9f4250d Mon Sep 17 00:00:00 2001 From: Zhecheng Li Date: Wed, 8 Jul 2020 17:04:23 +0800 Subject: [PATCH] antctl support traceflow Fixed #923 antctl traceflow is on remote mode, inside controller mode and inside agent mode. It supports yaml and json output. It can return without retrieving results. e.g. ``` $ antctl traceflow -S busybox0 -D busybox1 name: default-busybox0-to-default-busybox1-fpllngzi phase: Succeeded source: default/busybox0 destination: default/busybox1 results: - node: antrea-linux-testbed7-1 timestamp: 1596435607 observations: - component: SpoofGuard action: Forwarded - component: Forwarding componentInfo: Output action: Delivered ``` --- build/yamls/antrea-aks.yml | 4 + build/yamls/antrea-eks.yml | 4 + build/yamls/antrea-gke.yml | 4 + build/yamls/antrea-ipsec.yml | 4 + build/yamls/antrea.yml | 4 + build/yamls/base/agent-rbac.yml | 2 + build/yamls/base/controller-rbac.yml | 2 + build/yamls/base/controller.yml | 1 + docs/antctl.md | 32 ++ pkg/antctl/antctl.go | 6 + pkg/antctl/raw/traceflow/command.go | 397 +++++++++++++++++++++++ pkg/antctl/raw/traceflow/command_test.go | 141 ++++++++ pkg/apis/ops/v1alpha1/types.go | 28 +- pkg/apiserver/apiserver.go | 30 +- 14 files changed, 633 insertions(+), 26 deletions(-) create mode 100644 pkg/antctl/raw/traceflow/command.go create mode 100644 pkg/antctl/raw/traceflow/command_test.go diff --git a/build/yamls/antrea-aks.yml b/build/yamls/antrea-aks.yml index 96970aa386c..56ba84194fd 100644 --- a/build/yamls/antrea-aks.yml +++ b/build/yamls/antrea-aks.yml @@ -505,6 +505,8 @@ rules: - list - update - patch + - create + - delete --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole @@ -607,6 +609,8 @@ rules: - list - update - patch + - create + - delete --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/build/yamls/antrea-eks.yml b/build/yamls/antrea-eks.yml index 5bfa6d6c095..92fb816a922 100644 --- a/build/yamls/antrea-eks.yml +++ b/build/yamls/antrea-eks.yml @@ -505,6 +505,8 @@ rules: - list - update - patch + - create + - delete --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole @@ -607,6 +609,8 @@ rules: - list - update - patch + - create + - delete --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/build/yamls/antrea-gke.yml b/build/yamls/antrea-gke.yml index f5811d69005..b84a2d06bcb 100644 --- a/build/yamls/antrea-gke.yml +++ b/build/yamls/antrea-gke.yml @@ -505,6 +505,8 @@ rules: - list - update - patch + - create + - delete --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole @@ -607,6 +609,8 @@ rules: - list - update - patch + - create + - delete --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/build/yamls/antrea-ipsec.yml b/build/yamls/antrea-ipsec.yml index 620d213f140..cc87e60687a 100644 --- a/build/yamls/antrea-ipsec.yml +++ b/build/yamls/antrea-ipsec.yml @@ -505,6 +505,8 @@ rules: - list - update - patch + - create + - delete --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole @@ -607,6 +609,8 @@ rules: - list - update - patch + - create + - delete --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/build/yamls/antrea.yml b/build/yamls/antrea.yml index 56c51162176..91c0a241f81 100644 --- a/build/yamls/antrea.yml +++ b/build/yamls/antrea.yml @@ -505,6 +505,8 @@ rules: - list - update - patch + - create + - delete --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole @@ -607,6 +609,8 @@ rules: - list - update - patch + - create + - delete --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/build/yamls/base/agent-rbac.yml b/build/yamls/base/agent-rbac.yml index f036be9a669..268c3e217c9 100644 --- a/build/yamls/base/agent-rbac.yml +++ b/build/yamls/base/agent-rbac.yml @@ -96,6 +96,8 @@ rules: - list - update - patch + - create + - delete --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1beta1 diff --git a/build/yamls/base/controller-rbac.yml b/build/yamls/base/controller-rbac.yml index a2865ec9c23..4efa087685e 100644 --- a/build/yamls/base/controller-rbac.yml +++ b/build/yamls/base/controller-rbac.yml @@ -110,6 +110,8 @@ rules: - list - update - patch + - create + - delete --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1beta1 diff --git a/build/yamls/base/controller.yml b/build/yamls/base/controller.yml index 71294daa9a7..45d538a06c4 100644 --- a/build/yamls/base/controller.yml +++ b/build/yamls/base/controller.yml @@ -132,3 +132,4 @@ spec: hostPath: path: /var/log/antrea type: DirectoryOrCreate + diff --git a/docs/antctl.md b/docs/antctl.md index 39add9eefc2..87e019445d9 100644 --- a/docs/antctl.md +++ b/docs/antctl.md @@ -20,6 +20,7 @@ running in two different modes: - [Dumping Pod network interface information](#dumping-pod-network-interface-information) - [Dumping OVS flows](#dumping-ovs-flows) - [OVS packet tracing](#ovs-packet-tracing) + - [Traceflow](#traceflow) ## Installation @@ -313,3 +314,34 @@ result: | Megaflow: recirc_id=0x54,eth,ip,in_port=1,nw_frag=no Datapath actions: 3 ``` + +### Traceflow + +`antctl traceflow` command is used to start a traceflow and retrieve its result. After the +result is collected, the traceflow will be deleted. Users can also create a traceflow with +`kubectl`, but `antctl traceflow` offers a simpler approach. + +The required options for this command +are `source` and `destination`, which consist of namespace and pod, service or IP. The command supports +yaml and json output. If users want a non blocking operation, an option: `--wait=false` can +be added to start the traceflow without waiting for result. Then, the deletion operation +will not be conducted. Besides, users can specify header protocol (ICMP, TCP and UDP) and +source/destination ports. + +e.g. +```bash +$ antctl traceflow -S busybox0 -D busybox1 +name: default-busybox0-to-default-busybox1-fpllngzi +phase: Succeeded +source: default/busybox0 +destination: default/busybox1 +results: +- node: antrea-linux-testbed7-1 + timestamp: 1596435607 + observations: + - component: SpoofGuard + action: Forwarded + - component: Forwarding + componentInfo: Output + action: Delivered +``` diff --git a/pkg/antctl/antctl.go b/pkg/antctl/antctl.go index e4f7ca19b7d..3bf4f1c7930 100644 --- a/pkg/antctl/antctl.go +++ b/pkg/antctl/antctl.go @@ -24,6 +24,7 @@ import ( "github.com/vmware-tanzu/antrea/pkg/agent/apiserver/handlers/podinterface" "github.com/vmware-tanzu/antrea/pkg/agent/openflow" "github.com/vmware-tanzu/antrea/pkg/antctl/raw/supportbundle" + "github.com/vmware-tanzu/antrea/pkg/antctl/raw/traceflow" "github.com/vmware-tanzu/antrea/pkg/antctl/transform/addressgroup" "github.com/vmware-tanzu/antrea/pkg/antctl/transform/appliedtogroup" "github.com/vmware-tanzu/antrea/pkg/antctl/transform/controllerinfo" @@ -324,6 +325,11 @@ var CommandList = &commandList{ supportAgent: true, supportController: true, }, + { + cobraCommand: traceflow.Command, + supportAgent: true, + supportController: true, + }, }, codec: scheme.Codecs, } diff --git a/pkg/antctl/raw/traceflow/command.go b/pkg/antctl/raw/traceflow/command.go new file mode 100644 index 00000000000..2640eb01738 --- /dev/null +++ b/pkg/antctl/raw/traceflow/command.go @@ -0,0 +1,397 @@ +// Copyright 2020 Antrea Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package traceflow + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "math/rand" + "net" + "regexp" + "strconv" + "strings" + "time" + + "github.com/spf13/cobra" + "gopkg.in/yaml.v2" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/klog" + + "github.com/vmware-tanzu/antrea/pkg/antctl/runtime" + "github.com/vmware-tanzu/antrea/pkg/apis/ops/v1alpha1" + clientset "github.com/vmware-tanzu/antrea/pkg/client/clientset/versioned" +) + +var ( + Command *cobra.Command + option = &struct { + source string + destination string + outputType string + flow string + waiting bool + }{} +) + +const ( + ICMPProtocol int32 = 1 + TCPProtocol int32 = 6 + UDPProtocol int32 = 17 +) + +// Response is the response of antctl traceflow. +type Response struct { + Name string `json:"name" yaml:"name"` // Traceflow name + Phase v1alpha1.TraceflowPhase `json:"phase,omitempty" yaml:"phase,omitempty"` // Traceflow phase + Source string `json:"source,omitempty" yaml:"source,omitempty"` // Traceflow source, e.g. "default/pod0" + Destination string `json:"destination,omitempty" yaml:"destination,omitempty"` // Traceflow destination, e.g. "default/pod1" + NodeResults []v1alpha1.NodeResult `json:"results,omitempty" yaml:"results,omitempty"` // Traceflow node results +} + +func init() { + Command = &cobra.Command{ + Use: "traceflow", + 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 + 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 TCP header and 80 as destination port + $antctl traceflow -S busybox0 -D busybox1 -f tcp,tcp_dst=80 +`, + RunE: runE, + } + + Command.Flags().StringVarP(&option.source, "source", "S", "", "source of the traceflow: namespace/pod or pod") + Command.Flags().StringVarP(&option.destination, "destination", "D", "", "destination of the traceflow: namespace/pod, pod, namespace/service, service or IP") + Command.Flags().StringVarP(&option.outputType, "output", "o", "yaml", "output type: yaml (default), json") + Command.Flags().BoolVarP(&option.waiting, "wait", "", true, "if false, command returns without retrieving results") + Command.Flags().StringVarP(&option.flow, "flow", "f", "", "specify the flow (packet headers) of the traceflow packet") +} + +func runE(cmd *cobra.Command, _ []string) error { + if len(option.source) == 0 || len(option.destination) == 0 { + fmt.Println("Please provide source and destination.") + return nil + } + + kubeconfigPath, err := cmd.Flags().GetString("kubeconfig") + if err != nil { + return err + } + kubeconfig, err := runtime.ResolveKubeconfig(kubeconfigPath) + if err != nil { + return err + } + setupKubeconfig(kubeconfig, &v1alpha1.SchemeGroupVersion) + + k8sclient, err := kubernetes.NewForConfig(kubeconfig) + if err != nil { + return fmt.Errorf("error when creating kubernetes clientset: %w", err) + } + client, err := clientset.NewForConfig(kubeconfig) + if err != nil { + return fmt.Errorf("error when creating clientset: %w", err) + } + + tf, err := newTraceflow(k8sclient) + if err != nil { + return fmt.Errorf("error when filling up traceflow config: %w", err) + } + + ctx, _ := context.WithTimeout(context.Background(), 5*time.Second) + if _, err = client.OpsV1alpha1().Traceflows().Create(ctx, tf, metav1.CreateOptions{}); err != nil { + return fmt.Errorf("error when creating traceflow, is traceflow feature gate enabled? %w", err) + } + defer func() { + if option.waiting { + if err = client.OpsV1alpha1().Traceflows().Delete(context.TODO(), tf.Name, metav1.DeleteOptions{}); err != nil { + klog.Errorf("error when deleting traceflow: %+v", err) + } + } + }() + + if !option.waiting { + return nil + } + + if err := wait.Poll(1*time.Second, 15*time.Second, func() (bool, error) { + tf, err := client.OpsV1alpha1().Traceflows().Get(context.TODO(), tf.Name, metav1.GetOptions{}) + if err != nil { + return false, err + } + if tf.Status.Phase != v1alpha1.Succeeded { + return false, nil + } + if err := output(tf); err != nil { + return false, fmt.Errorf("error when outputing result: %w", err) + } + return true, nil + }); err != nil { + return fmt.Errorf("error when retrieving traceflow: %w", err) + } + + return nil +} + +func setupKubeconfig(kubeconfig *rest.Config, groupVersion *schema.GroupVersion) { + kubeconfig.APIPath = "/apis" + kubeconfig.GroupVersion = groupVersion + kubeconfig.NegotiatedSerializer = scheme.Codecs.WithoutConversion() + kubeconfig.Insecure = true + kubeconfig.CAFile = "" + kubeconfig.CAData = nil +} + +func newTraceflow(client kubernetes.Interface) (*v1alpha1.Traceflow, error) { + var name string + var src v1alpha1.Source + split := strings.Split(option.source, "/") + if len(split) == 1 { + src.Namespace = "default" + src.Pod = split[0] + } else if len(split) == 2 && len(split[0]) != 0 && len(split[1]) != 0 { + src.Namespace = split[0] + src.Pod = split[1] + } else { + return nil, fmt.Errorf("source should be in the format of namespace/pod or pod") + } + + var dst v1alpha1.Destination + dstIP := net.ParseIP(option.destination) + if dstIP != nil { + dst.IP = dstIP.String() + name = getTFName(fmt.Sprintf("%s-%s-to-%s", src.Namespace, src.Pod, dst.IP)) + } else { + var isPod bool + var err error + split = strings.Split(option.destination, "/") + if len(split) == 1 { + dst.Namespace = "default" + isPod, err = dstIsPod(client, "default", split[0]) + if err != nil { + return nil, fmt.Errorf("failed to check if destination is pod or service: %w", err) + } + if isPod { + dst.Pod = split[0] + } else { + dst.Service = split[0] + } + } else if len(split) == 2 && len(split[0]) != 0 && len(split[1]) != 0 { + dst.Namespace = split[0] + isPod, err = dstIsPod(client, split[0], split[1]) + if err != nil { + return nil, fmt.Errorf("failed to check if destination is pod or service: %w", err) + } + if isPod { + dst.Pod = split[1] + } else { + dst.Service = split[1] + } + } else { + return nil, fmt.Errorf("destination should be in the format of namespace/pod, pod, namespace/service or service") + } + if isPod { + name = getTFName(fmt.Sprintf("%s-%s-to-%s-%s", src.Namespace, src.Pod, dst.Namespace, dst.Pod)) + } else { + name = getTFName(fmt.Sprintf("%s-%s-to-%s-%s", src.Namespace, src.Pod, dst.Namespace, dst.Service)) + } + } + + pkt, err := parseFlow() + if err != nil { + return nil, fmt.Errorf("failed to parse flow: %w", err) + } + + tf := &v1alpha1.Traceflow{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: v1alpha1.TraceflowSpec{ + Source: src, + Destination: dst, + Packet: *pkt, + }, + } + + return tf, nil +} + +func dstIsPod(client kubernetes.Interface, ns string, name string) (bool, error) { + ctx, _ := context.WithTimeout(context.Background(), 2*time.Second) + _, err := client.CoreV1().Pods(ns).Get(ctx, name, metav1.GetOptions{}) + if err != nil { + if errors.IsNotFound(err) { + return false, nil + } + return false, fmt.Errorf("failed to get Pod from Kubernetes API: %w", err) + } + return true, nil +} + +func parseFlow() (*v1alpha1.Packet, error) { + cleanFlow := strings.ReplaceAll(option.flow, " ", "") + pkt := new(v1alpha1.Packet) + protocols := map[string]int32{ + "icmp": ICMPProtocol, + "tcp": TCPProtocol, + "udp": UDPProtocol, + } + for _, v := range strings.Split(cleanFlow, ",") { + n, ok := protocols[v] + if ok { + (*pkt).IPHeader.Protocol = n + break + } + } + + r, ok, err := getFieldPortValue(cleanFlow, "tcp_src") + if err != nil { + return nil, fmt.Errorf("error when get tcp_src value: %w", err) + } + if ok { + if (*pkt).TransportHeader.TCP == nil { + (*pkt).TransportHeader.TCP = new(v1alpha1.TCPHeader) + } + (*pkt).TransportHeader.TCP.SrcPort = int32(r) + } + r, ok, err = getFieldPortValue(cleanFlow, "tcp_dst") + if err != nil { + return nil, fmt.Errorf("error when get tcp_dst value: %w", err) + } + if ok { + if (*pkt).TransportHeader.TCP == nil { + (*pkt).TransportHeader.TCP = new(v1alpha1.TCPHeader) + } + (*pkt).TransportHeader.TCP.DstPort = int32(r) + } + r, ok, err = getFieldPortValue(cleanFlow, "udp_src") + if err != nil { + return nil, fmt.Errorf("error when get udp_src value: %w", err) + } + if ok { + if (*pkt).TransportHeader.UDP == nil { + (*pkt).TransportHeader.UDP = new(v1alpha1.UDPHeader) + } + (*pkt).TransportHeader.UDP.SrcPort = int32(r) + } + r, ok, err = getFieldPortValue(cleanFlow, "udp_dst") + if err != nil { + return nil, fmt.Errorf("error when get udp_dst value: %w", err) + } + if ok { + if (*pkt).TransportHeader.UDP == nil { + (*pkt).TransportHeader.UDP = new(v1alpha1.UDPHeader) + } + (*pkt).TransportHeader.UDP.DstPort = int32(r) + } + + return pkt, nil +} + +func getFieldPortValue(cleanFlow string, f string) (int, bool, error) { + for _, v := range strings.Split(cleanFlow, ",") { + m, err := regexp.MatchString(fmt.Sprintf("%s=[0-9]+", f), v) + if err != nil { + return 0, false, err + } + if m { + r, err := strconv.Atoi(v[len(f)+1:]) + if err != nil { + return 0, false, err + } + return r, true, nil + } + } + return 0, false, nil +} + +func output(tf *v1alpha1.Traceflow) error { + r := Response{ + Name: tf.Name, + Phase: tf.Status.Phase, + Source: fmt.Sprintf("%s/%s", tf.Spec.Source.Namespace, tf.Spec.Source.Pod), + Destination: tf.Spec.Destination.IP, + NodeResults: tf.Status.Results, + } + if len(tf.Spec.Destination.IP) == 0 { + if len(tf.Spec.Destination.Service) != 0 { + r.Destination = fmt.Sprintf("%s/%s", tf.Spec.Destination.Namespace, tf.Spec.Destination.Service) + } else { + r.Destination = fmt.Sprintf("%s/%s", tf.Spec.Destination.Namespace, tf.Spec.Destination.Pod) + } + } + if option.outputType == "json" { + if err := jsonOutput(&r); err != nil { + return fmt.Errorf("error when converting output to json: %w", err) + } + } else if option.outputType == "yaml" { + if err := yamlOutput(&r); err != nil { + return fmt.Errorf("error when converting output to yaml: %w", err) + } + } else { + return fmt.Errorf("output types are yaml and json") + } + return nil +} + +func yamlOutput(r *Response) error { + o, err := yaml.Marshal(&r) + if err != nil { + return err + } + fmt.Println(string(o)) + return nil +} + +func jsonOutput(r *Response) error { + o, err := json.Marshal(r) + if err != nil { + return err + } + var b bytes.Buffer + if err = json.Indent(&b, o, "", " "); err != nil { + return err + } + fmt.Println(string(b.Bytes())) + return nil +} + +func getTFName(prefix string) string { + if !option.waiting { + return prefix + } + var lettersAndDigits = []rune("abcdefghijklmnopqrstuvwxyz0123456789") + b := make([]rune, 8) + for i := range b { + randIdx := rand.Intn(len(lettersAndDigits)) + b[i] = lettersAndDigits[randIdx] + } + return fmt.Sprintf("%s-%s", prefix, string(b)) +} diff --git a/pkg/antctl/raw/traceflow/command_test.go b/pkg/antctl/raw/traceflow/command_test.go new file mode 100644 index 00000000000..3fd087dfe00 --- /dev/null +++ b/pkg/antctl/raw/traceflow/command_test.go @@ -0,0 +1,141 @@ +// Copyright 2020 Antrea Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package traceflow + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/vmware-tanzu/antrea/pkg/apis/ops/v1alpha1" +) + +// TestParseFlow tests if a flow can be parsed correctly. +func TestParseFlow(t *testing.T) { + type testcase struct { + flow string + success bool + expected *v1alpha1.Traceflow + } + + tcs := []testcase{ + { + flow: "udp,udp_src=1234,udp_dst=4321", + success: true, + expected: &v1alpha1.Traceflow{ + Spec: v1alpha1.TraceflowSpec{ + Packet: v1alpha1.Packet{ + IPHeader: v1alpha1.IPHeader{ + Protocol: 17, + }, + TransportHeader: v1alpha1.TransportHeader{ + UDP: &v1alpha1.UDPHeader{ + SrcPort: 1234, + DstPort: 4321, + }, + }, + }, + }, + }, + }, + { + flow: " icmp,", + success: true, + expected: &v1alpha1.Traceflow{ + Spec: v1alpha1.TraceflowSpec{ + Packet: v1alpha1.Packet{ + IPHeader: v1alpha1.IPHeader{ + Protocol: 1, + }, + }, + }, + }, + }, + { + flow: "tcp,tcp_dst=4321", + success: true, + expected: &v1alpha1.Traceflow{ + Spec: v1alpha1.TraceflowSpec{ + Packet: v1alpha1.Packet{ + IPHeader: v1alpha1.IPHeader{ + Protocol: 6, + }, + TransportHeader: v1alpha1.TransportHeader{ + TCP: &v1alpha1.TCPHeader{ + DstPort: 4321, + }, + }, + }, + }, + }, + }, + } + + for _, tc := range tcs { + option.flow = tc.flow + pkt, err := parseFlow() + if err != nil { + if tc.success { + t.Errorf("error when running parseFlow(): %w", err) + } + } else { + assert.Equal(t, *pkt, tc.expected.Spec.Packet) + } + } +} + +// TestGetFieldPortValue tests if corresponding value can be got of a key in a string. +func TestGetFieldPortValue(t *testing.T) { + type testcase struct { + flow string + field string + expected int + success bool + } + + tcs := []testcase{ + { + flow: "ab=1,abc=2", + field: "abc", + expected: 2, + success: true, + }, + { + flow: "ab=1,abc=2", + field: "ab", + expected: 1, + success: true, + }, + { + flow: "ab=1,abc=2", + field: "a", + expected: 0, + success: false, + }, + } + + for _, tc := range tcs { + n, ok, err := getFieldPortValue(tc.flow, tc.field) + if err != nil { + t.Errorf("testcase error: %v", tc) + } + if tc.success { + assert.Equal(t, ok, true) + assert.Equal(t, tc.expected, n) + } else { + assert.Equal(t, ok, false) + } + } +} diff --git a/pkg/apis/ops/v1alpha1/types.go b/pkg/apis/ops/v1alpha1/types.go index c61967f7f20..cefd5c9edd4 100644 --- a/pkg/apis/ops/v1alpha1/types.go +++ b/pkg/apis/ops/v1alpha1/types.go @@ -149,37 +149,37 @@ type TraceflowStatus struct { type NodeResult struct { // Node is the node of the observation. - Node string `json:"node,omitempty"` + Node string `json:"node,omitempty" yaml:"node,omitempty"` // Role of the node like sender, receiver, etc. - Role string `json:"role,omitempty"` + Role string `json:"role,omitempty" yaml:"role,omitempty"` // Timestamp is the timestamp of the observations on the node. - Timestamp int64 `json:"timestamp,omitempty"` + Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` // Observations includes all observations from sender nodes, receiver ones, etc. - Observations []Observation `json:"observations,omitempty"` + Observations []Observation `json:"observations,omitempty" yaml:"observations,omitempty"` } // Observation describes those from sender nodes or receiver nodes. type Observation struct { // Component is the observation component. - Component TraceflowComponent `json:"component,omitempty"` + Component TraceflowComponent `json:"component,omitempty" yaml:"component,omitempty"` // ComponentInfo is the extension of Component field. - ComponentInfo string `json:"componentInfo,omitempty"` + ComponentInfo string `json:"componentInfo,omitempty" yaml:"componentInfo,omitempty"` // Action is the action to the observation. - Action TraceflowAction `json:"action,omitempty"` + Action TraceflowAction `json:"action,omitempty" yaml:"action,omitempty"` // Pod is the combination of Pod name and Pod Namespace. - Pod string `json:"pod,omitempty"` + Pod string `json:"pod,omitempty" yaml:"pod,omitempty"` // DstMAC is the destination MAC. - DstMAC string `json:"dstMAC,omitempty"` + DstMAC string `json:"dstMAC,omitempty" yaml:"dstMAC,omitempty"` // NetworkPolicy is the combination of Namespace and NetworkPolicyName. - NetworkPolicy string `json:"networkPolicy,omitempty"` + NetworkPolicy string `json:"networkPolicy,omitempty" yaml:"networkPolicy,omitempty"` // TTL is the observation TTL. - TTL int32 `json:"ttl,omitempty"` + TTL int32 `json:"ttl,omitempty" yaml:"ttl,omitempty"` // TranslatedSrcIP is the translated source IP. - TranslatedSrcIP string `json:"translatedSrcIP,omitempty"` + TranslatedSrcIP string `json:"translatedSrcIP,omitempty" yaml:"translatedSrcIP,omitempty"` // TranslatedSrcIP is the translated destination IP. - TranslatedDstIP string `json:"translatedDstIP,omitempty"` + TranslatedDstIP string `json:"translatedDstIP,omitempty" yaml:"translatedDstIP,omitempty"` // TunnelDstIP is the tunnel destination IP. - TunnelDstIP string `json:"tunnelDstIP,omitempty"` + TunnelDstIP string `json:"tunnelDstIP,omitempty" yaml:"tunnelDstIP,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index b9f4739a93b..fd1852f039f 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -112,17 +112,7 @@ func (c *Config) Complete(informers informers.SharedInformerFactory) completedCo return completedConfig{c.genericConfig.Complete(informers), &c.extraConfig} } -func (c completedConfig) New() (*APIServer, error) { - genericServer, err := c.genericConfig.New("antrea-apiserver", genericapiserver.NewEmptyDelegate()) - if err != nil { - return nil, err - } - - s := &APIServer{ - GenericAPIServer: genericServer, - caCertController: c.extraConfig.caCertController, - } - +func installAPIGroup(s *APIServer, c completedConfig) error { networkingGroup := genericapiserver.NewDefaultAPIGroupInfo(networking.GroupName, Scheme, metav1.ParameterCodec, Codecs) networkingStorage := map[string]rest.Storage{} networkingStorage["addressgroups"] = addressgroup.NewREST(c.extraConfig.addressGroupStore) @@ -141,9 +131,25 @@ func (c completedConfig) New() (*APIServer, error) { groups := []*genericapiserver.APIGroupInfo{&networkingGroup, &systemGroup} for _, apiGroupInfo := range groups { if err := s.GenericAPIServer.InstallAPIGroup(apiGroupInfo); err != nil { - return nil, err + return err } } + return nil +} +func (c completedConfig) New() (*APIServer, error) { + genericServer, err := c.genericConfig.New("antrea-apiserver", genericapiserver.NewEmptyDelegate()) + if err != nil { + return nil, err + } + + s := &APIServer{ + GenericAPIServer: genericServer, + caCertController: c.extraConfig.caCertController, + } + + if err := installAPIGroup(s, c); err != nil { + return nil, err + } return s, nil }