k8s.io/kubernetes@v1.29.3/test/e2e/network/netpol/reachability.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 "strings" 22 23 v1 "k8s.io/api/core/v1" 24 "k8s.io/kubernetes/test/e2e/framework" 25 ) 26 27 // TestCase describes the data for a netpol test 28 type TestCase struct { 29 ToPort int 30 Protocol v1.Protocol 31 Reachability *Reachability 32 } 33 34 // PodString represents a namespace 'x' + pod 'a' as "x/a". 35 type PodString string 36 37 // NewPodString instantiates a PodString from the given namespace and name. 38 func NewPodString(namespace string, podName string) PodString { 39 return PodString(fmt.Sprintf("%s/%s", namespace, podName)) 40 } 41 42 // String converts back to a string 43 func (pod PodString) String() string { 44 return string(pod) 45 } 46 47 func (pod PodString) split() (string, string) { 48 pieces := strings.Split(string(pod), "/") 49 if len(pieces) != 2 { 50 framework.Failf("expected ns/pod, found %+v", pieces) 51 } 52 return pieces[0], pieces[1] 53 } 54 55 // Namespace extracts the namespace 56 func (pod PodString) Namespace() string { 57 ns, _ := pod.split() 58 return ns 59 } 60 61 // PodName extracts the pod name 62 func (pod PodString) PodName() string { 63 _, podName := pod.split() 64 return podName 65 } 66 67 // Peer is used for matching pods by either or both of the pod's namespace and name. 68 type Peer struct { 69 Namespace string 70 Pod string 71 } 72 73 // Matches checks whether the Peer matches the PodString: 74 // - an empty namespace means the namespace will always match 75 // - otherwise, the namespace must match the PodString's namespace 76 // - same goes for Pod: empty matches everything, otherwise must match exactly 77 func (p *Peer) Matches(pod PodString) bool { 78 return (p.Namespace == "" || p.Namespace == pod.Namespace()) && (p.Pod == "" || p.Pod == pod.PodName()) 79 } 80 81 // Reachability packages the data for a cluster-wide connectivity probe 82 type Reachability struct { 83 Expected *TruthTable 84 Observed *TruthTable 85 PodStrings []PodString 86 } 87 88 // NewReachability instantiates a reachability 89 func NewReachability(podStrings []PodString, defaultExpectation bool) *Reachability { 90 var podNames []string 91 for _, podString := range podStrings { 92 podNames = append(podNames, podString.String()) 93 } 94 r := &Reachability{ 95 Expected: NewTruthTableFromItems(podNames, &defaultExpectation), 96 Observed: NewTruthTableFromItems(podNames, nil), 97 PodStrings: podStrings, 98 } 99 return r 100 } 101 102 // AllowLoopback expects all communication from a pod to itself to be allowed. 103 // In general, call it after setting up any other rules since loopback logic follows no policy. 104 func (r *Reachability) AllowLoopback() { 105 for _, podString := range r.PodStrings { 106 podName := podString.String() 107 r.Expected.Set(podName, podName, true) 108 } 109 } 110 111 // Expect sets the expected value for a single observation 112 func (r *Reachability) Expect(from PodString, to PodString, isConnected bool) { 113 r.Expected.Set(string(from), string(to), isConnected) 114 } 115 116 // ExpectAllIngress defines that any traffic going into the pod will be allowed/denied (true/false) 117 func (r *Reachability) ExpectAllIngress(pod PodString, connected bool) { 118 r.Expected.SetAllTo(string(pod), connected) 119 if !connected { 120 framework.Logf("Denying all traffic *to* %s", pod) 121 } 122 } 123 124 // ExpectAllEgress defines that any traffic going out of the pod will be allowed/denied (true/false) 125 func (r *Reachability) ExpectAllEgress(pod PodString, connected bool) { 126 r.Expected.SetAllFrom(string(pod), connected) 127 if !connected { 128 framework.Logf("Denying all traffic *from* %s", pod) 129 } 130 } 131 132 // ExpectPeer sets expected values using Peer matchers 133 func (r *Reachability) ExpectPeer(from *Peer, to *Peer, connected bool) { 134 for _, fromPod := range r.PodStrings { 135 if from.Matches(fromPod) { 136 for _, toPod := range r.PodStrings { 137 if to.Matches(toPod) { 138 r.Expected.Set(fromPod.String(), toPod.String(), connected) 139 } 140 } 141 } 142 } 143 } 144 145 // Observe records a single connectivity observation 146 func (r *Reachability) Observe(fromPod PodString, toPod PodString, isConnected bool) { 147 r.Observed.Set(fromPod.String(), toPod.String(), isConnected) 148 } 149 150 // Summary produces a useful summary of expected and observed data 151 func (r *Reachability) Summary(ignoreLoopback bool) (trueObs int, falseObs int, ignoredObs int, comparison *TruthTable) { 152 comparison = r.Expected.Compare(r.Observed) 153 if !comparison.IsComplete() { 154 framework.Failf("observations not complete!") 155 } 156 falseObs, trueObs, ignoredObs = 0, 0, 0 157 for from, dict := range comparison.Values { 158 for to, val := range dict { 159 if ignoreLoopback && from == to { 160 // Never fail on loopback, because its not yet defined. 161 ignoredObs++ 162 } else if val { 163 trueObs++ 164 } else { 165 falseObs++ 166 } 167 } 168 } 169 return 170 } 171 172 // PrintSummary prints the summary 173 func (r *Reachability) PrintSummary(printExpected bool, printObserved bool, printComparison bool) { 174 right, wrong, ignored, comparison := r.Summary(ignoreLoopback) 175 if ignored > 0 { 176 framework.Logf("warning: this test doesn't take into consideration hairpin traffic, i.e. traffic whose source and destination is the same pod: %d cases ignored", ignored) 177 } 178 framework.Logf("reachability: correct:%v, incorrect:%v, result=%t\n\n", right, wrong, wrong == 0) 179 if printExpected { 180 framework.Logf("expected:\n\n%s\n\n\n", r.Expected.PrettyPrint("")) 181 } 182 if printObserved { 183 framework.Logf("observed:\n\n%s\n\n\n", r.Observed.PrettyPrint("")) 184 } 185 if printComparison { 186 framework.Logf("comparison:\n\n%s\n\n\n", comparison.PrettyPrint("")) 187 } 188 }