github.com/fafucoder/cilium@v1.6.11/test/helpers/policygen/models.go (about) 1 // Copyright 2017-2019 Authors of Cilium 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package policygen 16 17 import ( 18 "bytes" 19 "encoding/json" 20 "fmt" 21 "html/template" 22 "io/ioutil" 23 "net" 24 "os" 25 "path/filepath" 26 "strconv" 27 "strings" 28 "time" 29 30 cnpv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" 31 "github.com/cilium/cilium/pkg/policy/api" 32 "github.com/cilium/cilium/test/helpers" 33 "github.com/cilium/cilium/test/helpers/constants" 34 35 "github.com/onsi/ginkgo" 36 "github.com/onsi/gomega" 37 log "github.com/sirupsen/logrus" 38 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 39 ) 40 41 var timeout = 10 * time.Minute 42 43 // ConnTestSpec Connectivity Test Specification. This structs contains the 44 // mapping of all protocols tested and the expected result based on the context 45 // of each test case 46 type ConnTestSpec struct { 47 HTTP ResultType 48 HTTPPrivate ResultType 49 Ping ResultType 50 UDP ResultType 51 } 52 53 // GetField method to retrieve the value of any type of the struct. 54 // It is used by `TestSpec` to created expected results 55 func (conn *ConnTestSpec) GetField(field string) ResultType { 56 switch field { 57 case HTTP: 58 return conn.HTTP 59 case HTTPPrivate: 60 return conn.HTTPPrivate 61 case Ping: 62 return conn.Ping 63 case UDP: 64 return conn.UDP 65 } 66 return ResultType{} 67 } 68 69 // PolicyTestKind is utilized to describe a new TestCase 70 // It needs a described name, the kind of the test (Egrees or Ingress) and the 71 // expected result of `ConnTestSpec` 72 // Template field is used to render the cilium network policy. 73 type PolicyTestKind struct { 74 name string 75 kind string //Egress/ingress 76 tests ConnTestSpec 77 template map[string]string 78 exclude []string 79 } 80 81 // SetTemplate renders the template field from the PolicyTest struct using go 82 // templates. The result will be stored in the result parameter. The spec 83 // parameters is needed to retrieve the source and destination pods and pass 84 // the information to the go template. 85 func (pol *PolicyTestKind) SetTemplate(result *map[string]interface{}, spec *TestSpec) error { 86 getTemplate := func(tmpl string) (*bytes.Buffer, error) { 87 t, err := template.New("").Parse(tmpl) 88 if err != nil { 89 return nil, err 90 } 91 content := new(bytes.Buffer) 92 err = t.Execute(content, spec) 93 if err != nil { 94 return nil, err 95 } 96 return content, nil 97 } 98 99 for k, v := range pol.template { 100 // If any key was already set we do not need to overwrite it. 101 // This is in use on L7 when a port is always needed 102 if _, ok := (*result)[k]; ok { 103 continue 104 } 105 tmpl, err := getTemplate(v) 106 if err != nil { 107 return err 108 } 109 var data interface{} 110 err = json.Unmarshal(tmpl.Bytes(), &data) 111 if err != nil { 112 return err 113 } 114 (*result)[k] = data 115 } 116 return nil 117 } 118 119 // ResultType defines the expected result for a connectivity test. 120 type ResultType struct { 121 kind string // Timeout, reply 122 success bool // If the cmd exec is valid or not. 123 } 124 125 // String returns the ResultType in humman readable format 126 func (res ResultType) String() string { 127 return fmt.Sprintf("kind: %s success: %t", res.kind, res.success) 128 } 129 130 // PolicyTestSuite groups together L3, L4, and L7 policy-related tests. 131 type PolicyTestSuite struct { 132 l3Checks []PolicyTestKind 133 l4Checks []PolicyTestKind 134 l7Checks []PolicyTestKind 135 } 136 137 // Target defines the destination for traffic when running tests 138 type Target struct { 139 Kind string // serviceL3, serviceL4, NodePort, Direct 140 PortNumber int 141 } 142 143 // SetPortNumber returns an unused port on the host to use in a Kubernetes 144 // NodePort service 145 func (t *Target) SetPortNumber() int { 146 NodePortStart++ 147 t.PortNumber = NodePortStart 148 return t.PortNumber 149 } 150 151 // GetTarget returns a `TargetDetails` with the IP and Port to run the tests 152 // in spec. It needs the `TestSpec` parameter to be able to retrieve the 153 // service name. It'll return an error if the service is not defined or cannot 154 // be retrieved. This function only returns the first port mapped in the 155 // service; It'll not work with multiple ports. 156 func (t *Target) GetTarget(spec *TestSpec) (*TargetDetails, error) { 157 158 switch t.Kind { 159 case nodePort, service: 160 host, port, err := spec.Kub.GetServiceHostPort(helpers.DefaultNamespace, t.GetServiceName(spec)) 161 if err != nil { 162 return nil, err 163 } 164 return &TargetDetails{ 165 Port: port, 166 IP: []byte(host), 167 }, nil 168 case direct: 169 filter := `{.status.podIP}{"="}{.spec.containers[0].ports[0].containerPort}` 170 res, err := spec.Kub.Get(helpers.DefaultNamespace, fmt.Sprintf("pod %s", spec.DestPod)).Filter(filter) 171 if err != nil { 172 return nil, fmt.Errorf("cannot get pod '%s' info: %s", spec.DestPod, err) 173 } 174 vals := strings.Split(res.String(), "=") 175 port, err := strconv.Atoi(vals[1]) 176 if err != nil { 177 return nil, fmt.Errorf("cannot get pod '%s' port: %s", spec.DestPod, err) 178 } 179 return &TargetDetails{ 180 Port: port, 181 IP: []byte(vals[0]), 182 }, nil 183 } 184 return nil, fmt.Errorf("%s not Implemented yet", t.Kind) 185 } 186 187 // GetServiceName returns the prefix of spec prefixed with the kind of the the 188 // target 189 func (t *Target) GetServiceName(spec *TestSpec) string { 190 return fmt.Sprintf("%s-%s", strings.ToLower(t.Kind), spec.Prefix) 191 } 192 193 // GetManifestName returns the manifest filename for the target using the spec 194 // parameter 195 func (t *Target) GetManifestName(spec *TestSpec) string { 196 return fmt.Sprintf("%s_%s_manifest.json", spec.Prefix, strings.ToLower(t.Kind)) 197 } 198 199 // GetManifestPath returns the manifest path for the target using the spec 200 // parameter 201 func (t *Target) GetManifestPath(spec *TestSpec) string { 202 return fmt.Sprintf("%s/%s", helpers.BasePath, t.GetManifestName(spec)) 203 } 204 205 // CreateApplyManifest creates the manifest for the type of the target and 206 // applies it in kubernetes. It will fail if the service manifest cannot be 207 // created correctly or applied to Kubernetes 208 func (t *Target) CreateApplyManifest(spec *TestSpec) error { 209 manifestPath := t.GetManifestPath(spec) 210 getTemplate := func(tmpl string) (*bytes.Buffer, error) { 211 metadata := map[string]interface{}{ 212 "spec": spec, 213 "target": t, 214 "targetName": t.GetServiceName(spec), 215 } 216 t, err := template.New("").Parse(tmpl) 217 if err != nil { 218 return nil, err 219 } 220 content := new(bytes.Buffer) 221 err = t.Execute(content, metadata) 222 if err != nil { 223 return nil, err 224 } 225 return content, nil 226 } 227 228 switch t.Kind { 229 case service: 230 // As default services are listen on port 80. 231 t.PortNumber = 80 232 service := `{ 233 "apiVersion": "v1", 234 "kind": "Service", 235 "metadata": { 236 "name": "{{ .targetName }}", 237 "labels": { 238 "test": "policygen" 239 } 240 }, 241 "spec": { 242 "ports": [ 243 { "port": {{ .target.PortNumber }} } 244 ], 245 "selector": { 246 "id": "{{ .spec.DestPod }}" 247 } 248 }}` 249 data, err := getTemplate(service) 250 if err != nil { 251 return fmt.Errorf("cannot render template: %s", err) 252 } 253 err = helpers.RenderTemplateToFile(t.GetManifestName(spec), data.String(), os.ModePerm) 254 if err != nil { 255 return err 256 } 257 case nodePort: 258 t.SetPortNumber() 259 nodePort := ` 260 { 261 "apiVersion": "v1", 262 "kind": "Service", 263 "metadata": { 264 "name": "{{ .targetName }}", 265 "labels": { 266 "test": "policygen" 267 } 268 }, 269 "spec": { 270 "type": "NodePort", 271 "ports": [ 272 { 273 "targetPort": 80, 274 "port": {{ .target.PortNumber }}, 275 "protocol": "TCP" 276 } 277 ], 278 "selector": { 279 "id": "{{ .spec.DestPod }}" 280 } 281 } 282 }` 283 284 data, err := getTemplate(nodePort) 285 if err != nil { 286 return fmt.Errorf("cannot render template: %s", err) 287 } 288 289 err = helpers.RenderTemplateToFile(t.GetManifestName(spec), data.String(), os.ModePerm) 290 if err != nil { 291 return err 292 } 293 case direct: 294 t.PortNumber = 80 295 return nil 296 } 297 res := spec.Kub.ApplyDefault(manifestPath) 298 if !res.WasSuccessful() { 299 return fmt.Errorf("%s", res.CombineOutput()) 300 } 301 return nil 302 } 303 304 // TargetDetails represents the address of a TCP end point. 305 type TargetDetails net.TCPAddr 306 307 // String combines host and port into a network address of the 308 // form "host:port" or, if host contains a colon or a percent sign, 309 // "[host]:port". 310 func (target TargetDetails) String() string { 311 return net.JoinHostPort(string(target.IP), fmt.Sprintf("%d", target.Port)) 312 } 313 314 // TestSpec defined a new test specification. It contains three different rules 315 // (l3, l4, l7) and a destination and source pod in which test will run. Each 316 // testSpec has a prefix, which is a label used to group all resources created 317 // by the TestSpec. Each test is executed using a type of Destination which is 318 // defined under Target struct. This struct needs a `*helpers.Kubectl` to run 319 // the needed commands 320 type TestSpec struct { 321 l3 PolicyTestKind 322 l4 PolicyTestKind 323 l7 PolicyTestKind 324 SrcPod string 325 DestPod string 326 Prefix string 327 Destination Target 328 Kub *helpers.Kubectl 329 } 330 331 // String return the testSpec definition on human-readable format 332 func (t TestSpec) String() string { 333 return fmt.Sprintf("L3:%s L4:%s L7:%s Destination:%s", 334 t.l3.name, t.l4.name, t.l7.name, t.Destination.Kind) 335 } 336 337 // RunTest runs all the `TestSpec` methods and makes the needed assertions for 338 // Ginkgo tests. This method will create pods, wait for pods to be ready, apply 339 // a new CiliumNetworkPolicy and create a new Destination (Service, NodePort) 340 // if needed. Then it will execute `connectivityTest` and compare the results 341 // with the expected results within the test specification 342 func (t *TestSpec) RunTest(kub *helpers.Kubectl) { 343 defer func() { go t.Destroy(destroyDelay) }() 344 345 t.Kub = kub 346 err := t.CreateManifests() 347 gomega.Expect(err).To(gomega.BeNil(), "cannot create pods manifest for %s", t.Prefix) 348 349 manifest, err := t.ApplyManifest() 350 gomega.Expect(err).To(gomega.BeNil(), "cannot apply pods manifest for %s", t.Prefix) 351 log.WithField("prefix", t.Prefix).Infof("Manifest '%s' is created correctly", manifest) 352 353 err = t.Destination.CreateApplyManifest(t) 354 gomega.Expect(err).To(gomega.BeNil(), "cannot apply destination for %s", t.Prefix) 355 356 if t.IsPolicyInvalid() { 357 // Some policies cannot be applied correctly because of different 358 // rules. This code makes sure that the status of the policy has a error 359 // in the status. 360 cnp, err := t.InvalidNetworkPolicyApply() 361 kub.Exec(fmt.Sprintf("%s delete cnp %s", helpers.KubectlCmd, t.Prefix)) 362 gomega.Expect(err).To(gomega.BeNil(), "Cannot apply network policy") 363 gomega.Expect(cnp).NotTo(gomega.BeNil(), "CNP is not a valid struct") 364 gomega.Expect(cnp.Status.Nodes).NotTo(gomega.BeEmpty(), "CNP Status is empty") 365 366 for node, status := range cnp.Status.Nodes { 367 gomega.Expect(status.Error).NotTo(gomega.BeEmpty(), 368 "Node %q applied invalid policy and do not raise an error", node) 369 } 370 return 371 } 372 373 err = t.NetworkPolicyApply() 374 gomega.Expect(err).To(gomega.BeNil(), "cannot apply network policy for %s", t.Prefix) 375 376 err = kub.CiliumEndpointWaitReady() 377 gomega.Expect(err).To(gomega.BeNil(), "Endpoints are not ready after timeout") 378 379 err = t.ExecTest() 380 gomega.Expect(err).To(gomega.BeNil(), "cannot execute test for %s", t.Prefix) 381 } 382 383 // IsPolicyInvalid validates that the policy combination does not match with 384 // testSpec.exclude information. That means that if a policy cannot be 385 // installed we know that the combination is invalid. 386 func (t *TestSpec) IsPolicyInvalid() bool { 387 var exclude []string 388 exclude = append(t.l3.exclude, t.l4.exclude...) 389 exclude = append(exclude, t.l7.exclude...) 390 391 for _, value := range exclude { 392 if strings.Contains(t.String(), value) { 393 return true 394 } 395 } 396 return false 397 } 398 399 // Destroy deletes the pods, CiliumNetworkPolicies and Destinations created by 400 // `TestSpec` after specified delay. The delay parameter is used to have the 401 // pod running for a while and keep Cilium and Kubernetes with a consider load. 402 func (t *TestSpec) Destroy(delay time.Duration) error { 403 manifestToDestroy := []string{ 404 t.GetManifestsPath(), 405 fmt.Sprintf("%s/%s", helpers.BasePath, t.NetworkPolicyName()), 406 fmt.Sprintf("%s", t.Destination.GetManifestPath(t)), 407 } 408 409 done := time.After(delay) 410 411 for { 412 select { 413 case <-done: 414 for _, manifest := range manifestToDestroy { 415 t.Kub.Delete(manifest) 416 } 417 } 418 } 419 } 420 421 // GetManifestName returns a string with the `TestSpec` manifest name 422 func (t *TestSpec) GetManifestName() string { 423 return fmt.Sprintf("%s_manifest.yaml", t.Prefix) 424 } 425 426 // GetManifestsPath returns the `TestSpec` manifest path 427 func (t *TestSpec) GetManifestsPath() string { 428 return fmt.Sprintf("%s/%s", helpers.BasePath, t.GetManifestName()) 429 } 430 431 // CreateManifests creates a new pod manifest. It sets a random prefix for the 432 // `TestCase` and creates two new pods (srcPod and DestPod). Returns an error 433 // if the manifest cannot be created 434 func (t *TestSpec) CreateManifests() error { 435 t.Prefix = helpers.MakeUID() 436 t.SrcPod = fmt.Sprintf("%s-%s", t.Prefix, helpers.MakeUID()) 437 t.DestPod = fmt.Sprintf("%s-%s", t.Prefix, helpers.MakeUID()) 438 439 manifest := ` 440 --- 441 apiVersion: v1 442 kind: Pod 443 metadata: 444 name: "%[2]s" 445 labels: 446 id: "%[2]s" 447 zgroup: "%[1]s" 448 test: "policygen" 449 spec: 450 terminationGracePeriodSeconds: 0 451 containers: 452 - name: app-frontend 453 image: %[4]s 454 imagePullPolicy: IfNotPresent 455 command: [ "sleep" ] 456 args: 457 - "1000h" 458 --- 459 apiVersion: v1 460 kind: Pod 461 metadata: 462 name: "%[3]s" 463 labels: 464 id: "%[3]s" 465 zgroup: "%[1]s" 466 test: "policygen" 467 spec: 468 terminationGracePeriodSeconds: 0 469 containers: 470 - name: web 471 image: %[5]s 472 imagePullPolicy: IfNotPresent 473 ports: 474 - containerPort: 80` 475 476 err := helpers.RenderTemplateToFile( 477 t.GetManifestName(), 478 fmt.Sprintf(manifest, t.Prefix, t.SrcPod, t.DestPod, constants.AlpineCurlImage, constants.HttpdImage), 479 os.ModePerm) 480 if err != nil { 481 return err 482 } 483 return nil 484 } 485 486 // ApplyManifest applies a new deployment manifest into the Kubernetes cluster. 487 // Returns an error if the manifest cannot be applied correctly 488 func (t *TestSpec) ApplyManifest() (string, error) { 489 err := t.CreateManifests() 490 if err != nil { 491 return "", err 492 } 493 res := t.Kub.ApplyDefault(t.GetManifestsPath()) 494 if !res.WasSuccessful() { 495 return "", fmt.Errorf("%s", res.CombineOutput()) 496 } 497 err = t.Kub.WaitforPods( 498 helpers.DefaultNamespace, 499 fmt.Sprintf("-l zgroup=%s", t.Prefix), 500 timeout) 501 if err != nil { 502 return "", err 503 } 504 return t.GetManifestName(), nil 505 } 506 507 // GetPodMetadata returns a map with the pod name and the IP for the pods used 508 // by the `TestSpec`. Returns an error in case that the pod info cannot 509 // be retrieved correctly. 510 func (t *TestSpec) GetPodMetadata() (map[string]string, error) { 511 result := make(map[string]string) 512 filter := `{range .items[*]}{@.metadata.name}{"="}{@.status.podIP}{"\n"}{end}` 513 514 res := t.Kub.Get(helpers.DefaultNamespace, fmt.Sprintf("pods -l zgroup=%s", t.Prefix)) 515 data, err := res.Filter(filter) 516 if err != nil { 517 return nil, err 518 } 519 520 for _, line := range strings.Split(data.String(), "\n") { 521 vals := strings.Split(line, "=") 522 if len(vals) == 2 { 523 result[vals[0]] = vals[1] 524 } 525 } 526 return result, nil 527 } 528 529 // CreateCiliumNetworkPolicy returns a CiliumNetworkPolicy based on the 530 // `TestSpec` l3, l4 and l7 rules. Returns an error if any of the `PolicyTest` 531 // set Template fails or if spec cannot be dump as string 532 func (t *TestSpec) CreateCiliumNetworkPolicy() (string, error) { 533 534 type rule map[string]interface{} 535 536 specs := []api.Rule{} 537 var err error 538 539 ingressMap := map[string]interface{}{} 540 l4ingress := map[string]interface{}{} 541 egressMap := map[string]interface{}{} 542 l4egress := map[string]interface{}{} 543 544 metadata := []byte(` 545 { 546 "apiVersion": "cilium.io/v2", 547 "kind": "CiliumNetworkPolicy", 548 "metadata": { 549 "name": "%[1]s", 550 "labels": { 551 "test": "policygen" 552 } 553 }, 554 "specs": %[2]s}`) 555 556 //Create template 557 switch kind := t.l3.kind; kind { 558 case ingress: 559 err = t.l3.SetTemplate(&ingressMap, t) 560 case egress: 561 err = t.l3.SetTemplate(&egressMap, t) 562 } 563 564 if err != nil { 565 return "", err 566 } 567 568 switch kind := t.l4.kind; kind { 569 case ingress: 570 err = t.l4.SetTemplate(&l4ingress, t) 571 case egress: 572 err = t.l4.SetTemplate(&l4egress, t) 573 } 574 575 if err != nil { 576 return "", err 577 } 578 579 switch kind := t.l7.kind; kind { 580 case ingress: 581 err = t.l7.SetTemplate(&l4ingress, t) 582 case egress: 583 err = t.l7.SetTemplate(&l4egress, t) 584 } 585 586 if err != nil { 587 return "", err 588 } 589 590 if len(l4ingress) > 0 { 591 ingressMap[toPorts] = []rule{l4ingress} 592 } 593 594 if len(l4egress) > 0 { 595 egressMap[toPorts] = []rule{l4egress} 596 } 597 598 if len(ingressMap) > 0 { 599 var ingressVal api.IngressRule 600 jsonOut, err := json.Marshal(ingressMap) 601 if err != nil { 602 return "", err 603 } 604 err = json.Unmarshal(jsonOut, &ingressVal) 605 if err != nil { 606 return "", err 607 } 608 specs = append(specs, api.Rule{ 609 EndpointSelector: api.EndpointSelector{ 610 LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{ 611 "id": t.DestPod, 612 }}, 613 }, 614 Ingress: []api.IngressRule{ingressVal}, 615 Egress: []api.EgressRule{}, 616 }) 617 } 618 619 if len(egressMap) > 0 { 620 var egressVal api.EgressRule 621 jsonOut, err := json.Marshal(egressMap) 622 if err != nil { 623 return "", err 624 } 625 err = json.Unmarshal(jsonOut, &egressVal) 626 if err != nil { 627 return "", err 628 } 629 630 specs = append(specs, api.Rule{ 631 EndpointSelector: api.EndpointSelector{ 632 LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{ 633 "id": t.SrcPod, 634 }}, 635 }, 636 Ingress: []api.IngressRule{}, 637 Egress: []api.EgressRule{egressVal}, 638 }) 639 } 640 641 if len(specs) == 0 { 642 return "", nil 643 } 644 645 jsonOutput, err := json.Marshal(specs) 646 if err != nil { 647 return "", err 648 } 649 return fmt.Sprintf(string(metadata), t.Prefix, jsonOutput), nil 650 } 651 652 // NetworkPolicyName returns the name of the NetworkPolicy 653 func (t *TestSpec) NetworkPolicyName() string { 654 return fmt.Sprintf("%s_policy.json", t.Prefix) 655 } 656 657 // NetworkPolicyApply applies the CiliumNetworkPolicy in Kubernetes and wait 658 // until the statuses of all pods have been updated. Returns an error if the 659 // status of the pods did not update or if the policy was unable to be applied 660 func (t *TestSpec) NetworkPolicyApply() error { 661 policy, err := t.CreateCiliumNetworkPolicy() 662 if err != nil { 663 return fmt.Errorf("Network policy cannot be created prefix=%s: %s", t.Prefix, err) 664 } 665 666 if policy == "" { 667 //This only happens on L3:No Policy L4:No Policy L7:No Policy 668 log.Info("No policy so do not import it") 669 return nil 670 } 671 672 err = ioutil.WriteFile(t.NetworkPolicyName(), []byte(policy), os.ModePerm) 673 if err != nil { 674 return fmt.Errorf("Network policy cannot be written prefix=%s: %s", t.Prefix, err) 675 } 676 677 _, err = t.Kub.CiliumPolicyAction( 678 helpers.DefaultNamespace, 679 fmt.Sprintf("%s/%s", helpers.BasePath, t.NetworkPolicyName()), 680 helpers.KubectlApply, 681 helpers.HelperTimeout) 682 683 if err != nil { 684 return fmt.Errorf("Network policy cannot be imported prefix=%s: %s", t.Prefix, err) 685 } 686 return nil 687 } 688 689 // InvalidNetworkPolicyApply it writes the policy and applies to Kubernetes, 690 // but instead of apply the policy, this return the CNP status for the TestSpec 691 // policy. This function is only used when a invalid combination of policies 692 // are created, where we need to test that the error is present. 693 func (t *TestSpec) InvalidNetworkPolicyApply() (*cnpv2.CiliumNetworkPolicy, error) { 694 policy, err := t.CreateCiliumNetworkPolicy() 695 if err != nil { 696 return nil, fmt.Errorf("Network policy cannot be created prefix=%s: %s", t.Prefix, err) 697 } 698 699 err = ioutil.WriteFile(t.NetworkPolicyName(), []byte(policy), os.ModePerm) 700 if err != nil { 701 return nil, fmt.Errorf("Network policy cannot be written prefix=%s: %s", t.Prefix, err) 702 } 703 704 res := t.Kub.ApplyDefault(filepath.Join(helpers.BasePath, t.NetworkPolicyName())) 705 if !res.WasSuccessful() { 706 return nil, fmt.Errorf("%s", res.CombineOutput()) 707 } 708 body := func() bool { 709 cnp := t.Kub.GetCNP(helpers.DefaultNamespace, t.Prefix) 710 if cnp != nil && len(cnp.Status.Nodes) > 0 { 711 return true 712 } 713 return false 714 } 715 err = helpers.WithTimeout( 716 body, 717 fmt.Sprintf("CNP %q is not ready after timeout", t.Prefix), 718 &helpers.TimeoutConfig{Timeout: 100 * time.Second}) 719 if err != nil { 720 return nil, err 721 } 722 cnp := t.Kub.GetCNP(helpers.DefaultNamespace, t.Prefix) 723 if cnp == nil { 724 return nil, fmt.Errorf("Cannot get cnp '%s'", t.Prefix) 725 } 726 727 return cnp, nil 728 } 729 730 type connTestResultType struct { 731 kind string 732 result ResultType 733 } 734 735 // getConnectivityTest returns an array with the expected results of the given 736 // connectivity test kind 737 func (t *TestSpec) getConnectivityTest(kind string) []connTestResultType { 738 return []connTestResultType{ 739 {t.l3.kind, t.l3.tests.GetField(kind)}, 740 {t.l4.kind, t.l4.tests.GetField(kind)}, 741 {t.l7.kind, t.l7.tests.GetField(kind)}} 742 } 743 744 // GetTestExpects returns a map with the connTestType and the expected result 745 // based on the `testExpect` 746 func (t *TestSpec) GetTestExpects() map[string]ResultType { 747 expectedTestResult := func(testType string) ResultType { 748 connTest := t.getConnectivityTest(testType) 749 750 //First check the egress rules that are the first rules that match 751 for _, kind := range ConnTestsFailedResults { 752 for _, test := range connTest { 753 if test.kind == egress { 754 if test.result == kind { 755 return kind 756 } 757 } 758 } 759 } 760 // If no ResultType for egress, we need to check if any specific 761 // ResultType on ingress 762 for _, kind := range ConnTestsFailedResults { 763 for _, test := range connTest { 764 if test.kind == ingress { 765 if test.result == kind { 766 return kind 767 } 768 } 769 } 770 } 771 return ResultOK 772 } 773 774 result := map[string]ResultType{} 775 for _, connTestType := range ConnTests { 776 result[connTestType] = expectedTestResult(connTestType) 777 } 778 779 return result 780 } 781 782 // ExecTest runs the connectivityTest for the expected `PolicyTest`. It will 783 // assert using gomega. 784 func (t *TestSpec) ExecTest() error { 785 testFailMessage := func(kind string) string { 786 return fmt.Sprintf("Type %s from %s to %s did not work", kind, t.SrcPod, t.DestPod) 787 } 788 for connType, expectResult := range t.GetTestExpects() { 789 if connType == Ping && (t.Destination.Kind == service || t.Destination.Kind == nodePort) { 790 continue 791 } 792 ginkgo.By(fmt.Sprintf("Checking %s", connType)) 793 fn := ConnTestsActions[connType] 794 target, err := t.Destination.GetTarget(t) 795 if err != nil { 796 return fmt.Errorf("cannot get target in '%s': %s", t.Prefix, err) 797 } 798 result := fn(t.SrcPod, *target, t.Kub) 799 gomega.Expect(result).To(gomega.Equal(expectResult), testFailMessage(connType)) 800 } 801 return nil 802 } 803 804 // TestSpecsGroup is a group of different TestSpec 805 type TestSpecsGroup []*TestSpec 806 807 // CreateAndApplyManifests creates all of the pods manifests and applies those 808 // manifest to the given kubernetes instance. 809 func (tg TestSpecsGroup) CreateAndApplyManifests(kub *helpers.Kubectl) { 810 completeManifest := "/tmp/data.yaml" 811 manifests := []string{} 812 for _, test := range tg { 813 test.CreateManifests() 814 manifests = append(manifests, test.GetManifestsPath()) 815 test.Kub = kub 816 err := test.Destination.CreateApplyManifest(test) 817 gomega.ExpectWithOffset(1, err).To(gomega.BeNil(), "cannot apply destination for %s", test.Prefix) 818 } 819 820 res := kub.Exec(fmt.Sprintf("cat %s > %s", strings.Join(manifests, " "), completeManifest)) 821 res.ExpectSuccess() 822 823 res = kub.Exec(fmt.Sprintf("%s apply -f %s", helpers.KubectlCmd, completeManifest)) 824 res.ExpectSuccess() 825 } 826 827 // CreateAndApplyCNP creates all Cilium Network Policies and it applies those 828 // manifests to the given Kubernetes instance. 829 func (tg TestSpecsGroup) CreateAndApplyCNP(kub *helpers.Kubectl) { 830 for _, test := range tg { 831 // TODO: Should be any better way to do this 832 test.Kub = kub 833 err := test.NetworkPolicyApply() 834 gomega.ExpectWithOffset(1, err).To(gomega.BeNil()) 835 } 836 } 837 838 // ConnectivityTest runs the Connectivity test per each TestSpec defined into 839 // the TestSpecsGroup 840 func (tg TestSpecsGroup) ConnectivityTest() { 841 for _, test := range tg { 842 err := test.ExecTest() 843 gomega.ExpectWithOffset(1, err).To(gomega.BeNil(), "cannot execute test for %s", test.Prefix) 844 } 845 }