k8s.io/kubernetes@v1.29.3/test/e2e/network/netpol/probe.go (about) 1 /* 2 Copyright 2020 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package netpol 18 19 import ( 20 "fmt" 21 22 "github.com/onsi/ginkgo/v2" 23 v1 "k8s.io/api/core/v1" 24 "k8s.io/kubernetes/test/e2e/framework" 25 netutils "k8s.io/utils/net" 26 ) 27 28 // probeConnectivityArgs is set of arguments for a probeConnectivity 29 type probeConnectivityArgs struct { 30 nsFrom string 31 podFrom string 32 containerFrom string 33 addrTo string 34 protocol v1.Protocol 35 toPort int 36 expectConnectivity bool 37 timeoutSeconds int 38 pollIntervalSeconds int 39 pollTimeoutSeconds int 40 } 41 42 // decouple us from k8smanager.go 43 type Prober interface { 44 probeConnectivity(args *probeConnectivityArgs) (bool, string, error) 45 } 46 47 // ProbeJob packages the data for the input of a pod->pod connectivity probe 48 type ProbeJob struct { 49 PodFrom TestPod 50 PodTo TestPod 51 PodToServiceIP string 52 ToPort int 53 ToPodDNSDomain string 54 Protocol v1.Protocol 55 ExpectConnectivity bool 56 } 57 58 // ProbeJobResults packages the data for the results of a pod->pod connectivity probe 59 type ProbeJobResults struct { 60 Job *ProbeJob 61 IsConnected bool 62 Err error 63 Command string 64 } 65 66 // ProbePodToPodConnectivity runs a series of probes in kube, and records the results in `testCase.Reachability` 67 func ProbePodToPodConnectivity(prober Prober, allPods []TestPod, dnsDomain string, testCase *TestCase) { 68 size := len(allPods) * len(allPods) 69 jobs := make(chan *ProbeJob, size) 70 results := make(chan *ProbeJobResults, size) 71 for i := 0; i < getWorkers(); i++ { 72 go probeWorker(prober, jobs, results) 73 } 74 for _, podFrom := range allPods { 75 for _, podTo := range allPods { 76 // set connectivity expectation for the probe job, this allows to retry probe when observed value 77 // don't match expected value. 78 expectConnectivity := testCase.Reachability.Expected.Get(podFrom.PodString().String(), podTo.PodString().String()) 79 80 jobs <- &ProbeJob{ 81 PodFrom: podFrom, 82 PodTo: podTo, 83 ToPort: testCase.ToPort, 84 ToPodDNSDomain: dnsDomain, 85 Protocol: testCase.Protocol, 86 ExpectConnectivity: expectConnectivity, 87 } 88 } 89 } 90 close(jobs) 91 92 for i := 0; i < size; i++ { 93 result := <-results 94 job := result.Job 95 if result.Err != nil { 96 framework.Logf("unable to perform probe %s -> %s: %v", job.PodFrom.PodString(), job.PodTo.PodString(), result.Err) 97 } 98 testCase.Reachability.Observe(job.PodFrom.PodString(), job.PodTo.PodString(), result.IsConnected) 99 expected := testCase.Reachability.Expected.Get(job.PodFrom.PodString().String(), job.PodTo.PodString().String()) 100 if result.IsConnected != expected { 101 framework.Logf("Validation of %s -> %s FAILED !!!", job.PodFrom.PodString(), job.PodTo.PodString()) 102 framework.Logf("error %v ", result.Err) 103 if expected { 104 framework.Logf("Expected allowed pod connection was instead BLOCKED --- run '%v'", result.Command) 105 } else { 106 framework.Logf("Expected blocked pod connection was instead ALLOWED --- run '%v'", result.Command) 107 } 108 } 109 } 110 } 111 112 // probeWorker continues polling a pod connectivity status, until the incoming "jobs" channel is closed, and writes results back out to the "results" channel. 113 // it only writes pass/fail status to a channel and has no failure side effects, this is by design since we do not want to fail inside a goroutine. 114 func probeWorker(prober Prober, jobs <-chan *ProbeJob, results chan<- *ProbeJobResults) { 115 defer ginkgo.GinkgoRecover() 116 for job := range jobs { 117 podFrom := job.PodFrom 118 // defensive programming: this should not be possible as we already check in initializeClusterFromModel 119 if netutils.ParseIPSloppy(job.PodTo.ServiceIP) == nil { 120 results <- &ProbeJobResults{ 121 Job: job, 122 IsConnected: false, 123 Err: fmt.Errorf("empty service ip"), 124 } 125 } 126 // note that we can probe a dnsName instead of ServiceIP by using dnsName like so: 127 // we stopped doing this because we wanted to support netpol testing in non dns enabled 128 // clusters, but might re-enable it later. 129 // dnsName := job.PodTo.QualifiedServiceAddress(job.ToPodDNSDomain) 130 131 // TODO make this work on dual-stack clusters... 132 connected, command, err := prober.probeConnectivity(&probeConnectivityArgs{ 133 nsFrom: podFrom.Namespace, 134 podFrom: podFrom.Name, 135 containerFrom: podFrom.ContainerName, 136 addrTo: job.PodTo.ServiceIP, 137 protocol: job.Protocol, 138 toPort: job.ToPort, 139 expectConnectivity: job.ExpectConnectivity, 140 timeoutSeconds: getProbeTimeoutSeconds(), 141 pollIntervalSeconds: getPollIntervalSeconds(), 142 pollTimeoutSeconds: getPollTimeoutSeconds(), 143 }) 144 result := &ProbeJobResults{ 145 Job: job, 146 IsConnected: connected, 147 Err: err, 148 Command: command, 149 } 150 results <- result 151 } 152 }