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  }