github.phpd.cn/cilium/cilium@v1.6.12/test/helpers/cilium.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 helpers
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"io/ioutil"
    22  	"net"
    23  	"os"
    24  	"path/filepath"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/cilium/cilium/api/v1/models"
    29  	"github.com/cilium/cilium/pkg/logging"
    30  	"github.com/cilium/cilium/pkg/logging/logfields"
    31  	"github.com/cilium/cilium/test/config"
    32  	"github.com/cilium/cilium/test/ginkgo-ext"
    33  	"github.com/cilium/cilium/test/helpers/logutils"
    34  
    35  	"github.com/sirupsen/logrus"
    36  )
    37  
    38  var log = logging.DefaultLogger
    39  
    40  const (
    41  	// MaxRetries is the number of times that a loop should iterate until a
    42  	// specified condition is not met
    43  	MaxRetries = 30
    44  )
    45  
    46  // BpfLBList returns the output of `cilium bpf lb list -o json` as a map
    47  // Key will be the frontend address and the value is an array with all backend
    48  // addresses
    49  func (s *SSHMeta) BpfLBList(noDuplicates bool) (map[string][]string, error) {
    50  	var (
    51  		result map[string][]string
    52  		res    *CmdRes
    53  	)
    54  
    55  	res = s.ExecCilium("bpf lb list -o json")
    56  
    57  	if !res.WasSuccessful() {
    58  		return nil, fmt.Errorf("cannot get bpf lb list: %s", res.CombineOutput())
    59  	}
    60  	err := res.Unmarshal(&result)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  
    65  	if noDuplicates {
    66  		for svc, entries := range result {
    67  			unique := make(map[string]struct{})
    68  			for _, e := range entries {
    69  				unique[e] = struct{}{}
    70  			}
    71  			result[svc] = make([]string, 0, len(unique))
    72  			for e := range unique {
    73  				result[svc] = append(result[svc], e)
    74  			}
    75  		}
    76  	}
    77  
    78  	return result, nil
    79  }
    80  
    81  // ExecCilium runs a Cilium CLI command and returns the resultant cmdRes.
    82  func (s *SSHMeta) ExecCilium(cmd string) *CmdRes {
    83  	command := fmt.Sprintf("cilium %s", cmd)
    84  	return s.ExecWithSudo(command)
    85  }
    86  
    87  // EndpointGet returns the output of `cilium endpoint get` for the provided
    88  // endpoint ID.
    89  func (s *SSHMeta) EndpointGet(id string) *models.Endpoint {
    90  	if id == "" {
    91  		return nil
    92  	}
    93  	var data []models.Endpoint
    94  	endpointGetCmd := fmt.Sprintf("endpoint get %s -o json", id)
    95  	res := s.ExecCilium(endpointGetCmd)
    96  	err := res.Unmarshal(&data)
    97  	if err != nil {
    98  		s.logger.WithError(err).Errorf("EndpointGet fail %s", id)
    99  		return nil
   100  	}
   101  	if len(data) > 0 {
   102  		return &data[0]
   103  	}
   104  	return nil
   105  }
   106  
   107  // GetEndpointMutableConfigurationOption returns the value of the mutable
   108  // configuration option optionName for the endpoint with ID endpointID, or an
   109  // error if optionName's corresponding value cannot be retrieved for the
   110  // endpoint.
   111  func (s *SSHMeta) GetEndpointMutableConfigurationOption(endpointID, optionName string) (string, error) {
   112  	cmd := fmt.Sprintf("endpoint config %s -o json | jq -r '.realized.options.%s'", endpointID, optionName)
   113  	res := s.ExecCilium(cmd)
   114  	if !res.WasSuccessful() {
   115  		return "", fmt.Errorf("Unable to execute %q: %s", cmd, res.CombineOutput())
   116  	}
   117  
   118  	return res.SingleOut(), nil
   119  }
   120  
   121  // SetAndWaitForEndpointConfiguration waits for the endpoint configuration to become a certain value
   122  func (s *SSHMeta) SetAndWaitForEndpointConfiguration(endpointID, optionName, expectedValue string) error {
   123  	logger := s.logger.WithFields(logrus.Fields{
   124  		logfields.EndpointID: endpointID,
   125  		"option":             optionName,
   126  		"value":              expectedValue})
   127  	body := func() bool {
   128  		logger.Infof("Setting endpoint configuration")
   129  		status := s.EndpointSetConfig(endpointID, optionName, expectedValue)
   130  		if !status {
   131  			logger.Error("Cannot set endpoint configuration")
   132  			return status
   133  		}
   134  
   135  		value, err := s.GetEndpointMutableConfigurationOption(endpointID, optionName)
   136  		if err != nil {
   137  			log.WithError(err).Error("cannot get endpoint configuration")
   138  			return false
   139  		}
   140  
   141  		if value == expectedValue {
   142  			return true
   143  		}
   144  		logger.Debugf("Expected configuration option to have value %s, but got %s",
   145  			expectedValue, value)
   146  		return false
   147  	}
   148  
   149  	err := WithTimeout(
   150  		body,
   151  		fmt.Sprintf("cannot set endpoint config for endpoint %q", endpointID),
   152  		&TimeoutConfig{Timeout: HelperTimeout})
   153  	return err
   154  }
   155  
   156  // WaitEndpointsDeleted waits up until timeout reached for all endpoints to be
   157  // deleted. Returns true if all endpoints have been deleted before HelperTimeout
   158  // is exceeded, false otherwise.
   159  func (s *SSHMeta) WaitEndpointsDeleted() bool {
   160  	logger := s.logger.WithFields(logrus.Fields{"functionName": "WaitEndpointsDeleted"})
   161  	// cilium-health endpoint is always running.
   162  	desiredState := "1"
   163  	body := func() bool {
   164  		cmd := fmt.Sprintf(`cilium endpoint list -o json | jq '. | length'`)
   165  		res := s.Exec(cmd)
   166  		numEndpointsRunning := strings.TrimSpace(res.GetStdOut())
   167  		if numEndpointsRunning == desiredState {
   168  			return true
   169  		}
   170  
   171  		logger.Infof("%s endpoints are still running, want %s", numEndpointsRunning, desiredState)
   172  		return false
   173  	}
   174  	err := WithTimeout(body, "Endpoints are not deleted after timeout", &TimeoutConfig{Timeout: HelperTimeout})
   175  	if err != nil {
   176  		logger.WithError(err).Warn("Endpoints are not deleted after timeout")
   177  		s.Exec("cilium endpoint list") // This function is only for debugging.
   178  		return false
   179  	}
   180  	return true
   181  
   182  }
   183  
   184  // WaitEndpointsReady waits up until timeout reached for all endpoints to not be
   185  // in any regenerating or waiting-for-identity state. Returns true if all
   186  // endpoints regenerate before HelperTimeout is exceeded, false otherwise.
   187  func (s *SSHMeta) WaitEndpointsReady() bool {
   188  	logger := s.logger.WithFields(logrus.Fields{"functionName": "WaitEndpointsReady"})
   189  	desiredState := string(models.EndpointStateReady)
   190  	body := func() bool {
   191  		filter := `{range [*]}{@.status.external-identifiers.container-name}{"="}{@.status.state},{@.status.identity.id}{"\n"}{end}`
   192  		cmd := fmt.Sprintf(`cilium endpoint list -o jsonpath='%s'`, filter)
   193  
   194  		res := s.Exec(cmd)
   195  		if !res.WasSuccessful() {
   196  			logger.Infof("Cannot get endpoint list: %s", res.CombineOutput())
   197  			return false
   198  		}
   199  		values := res.KVOutput()
   200  		total := len(values)
   201  
   202  		result := map[string]int{}
   203  		for _, status := range values {
   204  			fields := strings.Split(status, ",")
   205  			state := fields[0]
   206  			secID := fields[1]
   207  			// Consider an endpoint with reserved identity 5 (reserved:init) as not ready.
   208  			if secID == "5" {
   209  				state = state + "+init"
   210  			}
   211  			result[state]++
   212  		}
   213  
   214  		logger.WithField("status", result).Infof(
   215  			"'%d' containers are in a '%s' state of a total of '%d' containers.",
   216  			result[desiredState], desiredState, total)
   217  
   218  		if result[desiredState] == total {
   219  			return true
   220  		}
   221  
   222  		return false
   223  	}
   224  
   225  	err := WithTimeout(body, "Endpoints are not ready after timeout", &TimeoutConfig{Timeout: HelperTimeout})
   226  	if err != nil {
   227  		logger.WithError(err).Warn("Endpoints are not ready after timeout")
   228  		s.Exec("cilium endpoint list") // This function is only for debugging into log.
   229  		return false
   230  	}
   231  	return true
   232  }
   233  
   234  // EndpointSetConfig sets the provided configuration option to the provided
   235  // value for the endpoint with the endpoint ID id. It returns true if the
   236  // configuration update command returned successfully.
   237  func (s *SSHMeta) EndpointSetConfig(id, option, value string) bool {
   238  	logger := s.logger.WithFields(logrus.Fields{"endpointID": id})
   239  	res := s.ExecCilium(fmt.Sprintf(
   240  		"endpoint config %s -o json | jq -r '.realized.options.%s'", id, option))
   241  
   242  	if res.SingleOut() == value {
   243  		logger.Debugf("no need to update %s=%s; value already set", option, value)
   244  		return res.WasSuccessful()
   245  	}
   246  
   247  	before := s.EndpointGet(id)
   248  	if before == nil {
   249  		return false
   250  	}
   251  
   252  	configCmd := fmt.Sprintf("endpoint config %s %s=%s", id, option, value)
   253  	data := s.ExecCilium(configCmd)
   254  	if !data.WasSuccessful() {
   255  		logger.Errorf("cannot set endpoint configuration %s=%s", option, value)
   256  		return false
   257  	}
   258  
   259  	return true
   260  }
   261  
   262  // ListEndpoints returns the CmdRes resulting from executing
   263  // `cilium endpoint list -o json`.
   264  func (s *SSHMeta) ListEndpoints() *CmdRes {
   265  	return s.ExecCilium("endpoint list -o json")
   266  }
   267  
   268  // GetEndpointsIDMap returns a mapping of an endpoint ID to Docker container
   269  // name, and an error if the list of endpoints cannot be retrieved via the
   270  // Cilium CLI.
   271  func (s *SSHMeta) GetEndpointsIDMap() (map[string]string, error) {
   272  	filter := `{range [*]}{@.id}{"="}{@.status.external-identifiers.container-name}{"\n"}{end}`
   273  	cmd := fmt.Sprintf("endpoint list -o jsonpath='%s'", filter)
   274  	endpoints := s.ExecCilium(cmd)
   275  	if !endpoints.WasSuccessful() {
   276  		return nil, fmt.Errorf("%q failed: %s", cmd, endpoints.CombineOutput())
   277  	}
   278  	return endpoints.KVOutput(), nil
   279  }
   280  
   281  // GetAllEndpointsIds returns a mapping of all Docker container name to to its
   282  // corresponding endpoint ID, and an error if the list of endpoints cannot be
   283  // retrieved via the Cilium CLI.
   284  func (s *SSHMeta) GetAllEndpointsIds() (map[string]string, error) {
   285  	filter := `{range [*]}{@.status.external-identifiers.container-name}{"="}{@.id}{"\n"}{end}`
   286  	cmd := fmt.Sprintf("endpoint list -o jsonpath='%s'", filter)
   287  	endpoints := s.ExecCilium(cmd)
   288  	if !endpoints.WasSuccessful() {
   289  		return nil, fmt.Errorf("%q failed: %s", cmd, endpoints.CombineOutput())
   290  	}
   291  	return endpoints.KVOutput(), nil
   292  }
   293  
   294  // GetEndpointsIds returns a mapping of a Docker container name to to its
   295  // corresponding endpoint ID, and an error if the list of endpoints cannot be
   296  // retrieved via the Cilium CLI.
   297  func (s *SSHMeta) GetEndpointsIds() (map[string]string, error) {
   298  	// cilium endpoint list -o jsonpath='{range [?(@.status.labels.security-relevant[0]!='reserved:health')]}{@.status.external-identifiers.container-name}{"="}{@.id}{"\n"}{end}'
   299  	filter := `{range [?(@.status.labels.security-relevant[0]!="reserved:health")]}{@.status.external-identifiers.container-name}{"="}{@.id}{"\n"}{end}`
   300  	cmd := fmt.Sprintf("endpoint list -o jsonpath='%s'", filter)
   301  	endpoints := s.ExecCilium(cmd)
   302  	if !endpoints.WasSuccessful() {
   303  		return nil, fmt.Errorf("%q failed: %s", cmd, endpoints.CombineOutput())
   304  	}
   305  	return endpoints.KVOutput(), nil
   306  }
   307  
   308  // GetEndpointsIdentityIds returns a mapping of a Docker container name to it's
   309  // corresponding endpoint's security identity, it will return an error if the list
   310  // of endpoints cannot be retrieved via the Cilium CLI.
   311  func (s *SSHMeta) GetEndpointsIdentityIds() (map[string]string, error) {
   312  	filter := `{range [*]}{@.status.external-identifiers.container-name}{"="}{@.status.identity.id}{"\n"}{end}`
   313  	endpoints := s.ExecCilium(fmt.Sprintf("endpoint list -o jsonpath='%s'", filter))
   314  	if !endpoints.WasSuccessful() {
   315  		return nil, fmt.Errorf("cannot get endpoint list: %s", endpoints.CombineOutput())
   316  	}
   317  	return endpoints.KVOutput(), nil
   318  }
   319  
   320  // GetEndpointsNames returns the container-name field of each Cilium endpoint.
   321  func (s *SSHMeta) GetEndpointsNames() ([]string, error) {
   322  	data := s.ListEndpoints()
   323  	if data.WasSuccessful() == false {
   324  		return nil, fmt.Errorf("`cilium endpoint list` was not successful")
   325  	}
   326  
   327  	result, err := data.Filter("{ [?(@.status.labels.security-relevant[0]!='reserved:health')].status.external-identifiers.container-name }")
   328  	if err != nil {
   329  		return nil, err
   330  	}
   331  
   332  	return strings.Split(result.String(), " "), nil
   333  }
   334  
   335  // ManifestsPath returns the path of the directory where manifests (YAMLs
   336  // containing policies, DaemonSets, etc.) are stored for the runtime tests.
   337  // TODO: this can just be a constant; there's no need to have a function.
   338  func (s *SSHMeta) ManifestsPath() string {
   339  	return fmt.Sprintf("%s/runtime/manifests/", BasePath)
   340  }
   341  
   342  // MonitorStart starts the  monitor command in background and returns a callback
   343  // function wich stops the monitor when the user needs. When the callback is
   344  // called the command will stop and monitor's output is saved on
   345  // `monitorLogFileName` file.
   346  func (s *SSHMeta) MonitorStart() func() error {
   347  	cmd := "cilium monitor -v | ts '[%Y-%m-%d %H:%M:%S]'"
   348  	ctx, cancel := context.WithCancel(context.Background())
   349  	res := s.ExecInBackground(ctx, cmd, ExecOptions{SkipLog: true})
   350  
   351  	cb := func() error {
   352  		cancel()
   353  		testPath, err := CreateReportDirectory()
   354  		if err != nil {
   355  			s.logger.WithError(err).Errorf(
   356  				"cannot create test results path '%s'", testPath)
   357  			return err
   358  		}
   359  
   360  		err = ioutil.WriteFile(
   361  			filepath.Join(testPath, MonitorLogFileName),
   362  			res.CombineOutput().Bytes(),
   363  			LogPerm)
   364  		if err != nil {
   365  			log.WithError(err).Errorf("cannot create monitor log file")
   366  		}
   367  		return nil
   368  	}
   369  	return cb
   370  }
   371  
   372  // GetFullPath returns the path of file name prepended with the absolute path
   373  // where manifests (YAMLs containing policies, DaemonSets, etc.) are stored.
   374  func (s *SSHMeta) GetFullPath(name string) string {
   375  	return fmt.Sprintf("%s%s", s.ManifestsPath(), name)
   376  }
   377  
   378  // PolicyEndpointsSummary returns the count of whether policy enforcement is
   379  // enabled, disabled, and the total number of endpoints, and an error if the
   380  // Cilium endpoint metadata cannot be retrieved via the API.
   381  func (s *SSHMeta) PolicyEndpointsSummary() (map[string]int, error) {
   382  	result := map[string]int{
   383  		Enabled:  0,
   384  		Disabled: 0,
   385  		Total:    0,
   386  	}
   387  
   388  	res := s.ListEndpoints()
   389  	if !res.WasSuccessful() {
   390  		return nil, fmt.Errorf("was not able to list endpoints: %s", res.CombineOutput().String())
   391  	}
   392  
   393  	endpoints, err := res.Filter("{ [?(@.status.labels.security-relevant[0]!='reserved:health')].status.policy.realized.policy-enabled }")
   394  
   395  	if err != nil {
   396  		return result, fmt.Errorf(`cannot filter for "policy-enabled" from output of "cilium endpoint list"`)
   397  	}
   398  	status := strings.Split(endpoints.String(), " ")
   399  	for _, kind := range status {
   400  		switch models.EndpointPolicyEnabled(kind) {
   401  		case models.EndpointPolicyEnabledBoth, models.EndpointPolicyEnabledEgress,
   402  			models.EndpointPolicyEnabledIngress:
   403  			result[Enabled]++
   404  		case OptionNone:
   405  			result[Disabled]++
   406  		}
   407  		result[Total]++
   408  	}
   409  	return result, nil
   410  }
   411  
   412  // SetPolicyEnforcement sets the PolicyEnforcement configuration value for the
   413  // Cilium agent to the provided status.
   414  func (s *SSHMeta) SetPolicyEnforcement(status string) *CmdRes {
   415  	// We check before setting PolicyEnforcement; if we do not, EndpointWait
   416  	// will fail due to the status of the endpoints not changing.
   417  	log.Infof("setting %s=%s", PolicyEnforcement, status)
   418  	res := s.ExecCilium(fmt.Sprintf("config -o json | jq -r '.status.realized[\"policy-enforcement\"]'"))
   419  	if res.SingleOut() == status {
   420  		return res
   421  	}
   422  	return s.ExecCilium(fmt.Sprintf("config %s=%s", PolicyEnforcement, status))
   423  }
   424  
   425  // SetPolicyEnforcementAndWait and wait sets the PolicyEnforcement configuration
   426  // value for the Cilium agent to the provided status, and then waits for all endpoints
   427  // running in s to be ready. Returns whether setting of the configuration value
   428  // was unsuccessful / if the endpoints go into ready state.
   429  func (s *SSHMeta) SetPolicyEnforcementAndWait(status string) bool {
   430  	res := s.SetPolicyEnforcement(status)
   431  	if !res.WasSuccessful() {
   432  		return false
   433  	}
   434  
   435  	return s.WaitEndpointsReady()
   436  }
   437  
   438  // PolicyDelAll deletes all policy rules currently imported into Cilium.
   439  func (s *SSHMeta) PolicyDelAll() *CmdRes {
   440  	log.Info("Deleting all policy in agent")
   441  	return s.PolicyDel("--all")
   442  }
   443  
   444  // PolicyDel deletes the policy with the given ID from Cilium.
   445  func (s *SSHMeta) PolicyDel(id string) *CmdRes {
   446  	res := s.ExecCilium(fmt.Sprintf(
   447  		"policy delete %s -o json | jq '.revision'", id))
   448  	if !res.WasSuccessful() {
   449  		return res
   450  	}
   451  	policyID, _ := res.IntOutput()
   452  	return s.PolicyWait(policyID)
   453  }
   454  
   455  // PolicyGet runs `cilium policy get <id>`, where id is the name of a specific
   456  // policy imported into Cilium. It returns the resultant CmdRes from running
   457  // the aforementioned command.
   458  func (s *SSHMeta) PolicyGet(id string) *CmdRes {
   459  	return s.ExecCilium(fmt.Sprintf("policy get %s", id))
   460  }
   461  
   462  // PolicyGetAll gets all policies that are imported in the Cilium agent.
   463  func (s *SSHMeta) PolicyGetAll() *CmdRes {
   464  	return s.ExecCilium("policy get")
   465  
   466  }
   467  
   468  // PolicyGetRevision retrieves the current policy revision number in the Cilium
   469  // agent.
   470  func (s *SSHMeta) PolicyGetRevision() (int, error) {
   471  	rev := s.ExecCilium("policy get -o json | jq '.revision'")
   472  	return rev.IntOutput()
   473  }
   474  
   475  // PolicyImportAndWait validates and imports a new policy into Cilium and waits
   476  // until the policy revision number increments. Returns an error if the policy
   477  // is invalid or could not be imported.
   478  func (s *SSHMeta) PolicyImportAndWait(path string, timeout time.Duration) (int, error) {
   479  	ginkgoext.By(fmt.Sprintf("Setting up policy: %s", path))
   480  
   481  	revision, err := s.PolicyGetRevision()
   482  	if err != nil {
   483  		return -1, fmt.Errorf("cannot get policy revision: %s", err)
   484  	}
   485  	s.logger.WithFields(logrus.Fields{
   486  		logfields.Path:           path,
   487  		logfields.PolicyRevision: revision}).Info("before importing policy")
   488  
   489  	s.logger.WithFields(logrus.Fields{
   490  		logfields.Path: path}).Info("validating policy before importing")
   491  
   492  	res := s.ExecCilium(fmt.Sprintf("policy validate %s", path))
   493  	if res.WasSuccessful() == false {
   494  		s.logger.WithFields(logrus.Fields{
   495  			logfields.Path: path,
   496  		}).Errorf("could not validate policy %s: %s", path, res.CombineOutput())
   497  		return -1, fmt.Errorf("could not validate policy %s: %s", path, res.CombineOutput())
   498  	}
   499  
   500  	res = s.ExecCilium(fmt.Sprintf("policy import %s", path))
   501  	if res.WasSuccessful() == false {
   502  		s.logger.WithFields(logrus.Fields{
   503  			logfields.Path: path,
   504  		}).Errorf("could not import policy: %s", res.CombineOutput())
   505  		return -1, fmt.Errorf("could not import policy %s", path)
   506  	}
   507  	body := func() bool {
   508  		currentRev, _ := s.PolicyGetRevision()
   509  		if currentRev > revision {
   510  			res := s.PolicyWait(currentRev)
   511  			if !res.WasSuccessful() {
   512  				log.Errorf("policy wait failed: %s", res.CombineOutput())
   513  			}
   514  			return res.WasSuccessful()
   515  		}
   516  		s.logger.WithFields(logrus.Fields{
   517  			logfields.PolicyRevision:    currentRev,
   518  			"policyRevisionAfterImport": revision,
   519  		}).Infof("policy revisions are the same")
   520  		return false
   521  	}
   522  	err = WithTimeout(body, "could not import policy", &TimeoutConfig{Timeout: timeout})
   523  	if err != nil {
   524  		return -1, err
   525  	}
   526  	revision, err = s.PolicyGetRevision()
   527  	s.logger.WithFields(logrus.Fields{
   528  		logfields.Path:           path,
   529  		logfields.PolicyRevision: revision,
   530  	}).Infof("policy import finished and revision increased")
   531  	return revision, err
   532  }
   533  
   534  // PolicyImport imports a new policy into Cilium.
   535  func (s *SSHMeta) PolicyImport(path string) error {
   536  	res := s.ExecCilium(fmt.Sprintf("policy import %s", path))
   537  	if !res.WasSuccessful() {
   538  		s.logger.Errorf("could not import policy: %s", res.CombineOutput())
   539  		return fmt.Errorf("could not import policy %s", path)
   540  	}
   541  	return nil
   542  }
   543  
   544  // PolicyRenderAndImport receives an string with a policy, renders it in the
   545  // test root directory and imports the policy to cilium. It returns the new
   546  // policy id.  Returns an error if the file cannot be created or if the policy
   547  // cannot be imported
   548  func (s *SSHMeta) PolicyRenderAndImport(policy string) (int, error) {
   549  	filename := fmt.Sprintf("policy_%s.json", MakeUID())
   550  	s.logger.Debugf("PolicyRenderAndImport: render policy to '%s'", filename)
   551  	err := RenderTemplateToFile(filename, policy, os.ModePerm)
   552  	if err != nil {
   553  		s.logger.Errorf("PolicyRenderAndImport: cannot create policy file on '%s'", filename)
   554  		return 0, fmt.Errorf("cannot render the policy:  %s", err)
   555  	}
   556  	path := GetFilePath(filename)
   557  	s.logger.Debugf("PolicyRenderAndImport: import policy from '%s'", path)
   558  	defer os.Remove(filename)
   559  	return s.PolicyImportAndWait(path, HelperTimeout)
   560  }
   561  
   562  // PolicyWait executes `cilium policy wait`, which waits until all endpoints are
   563  // updated to the given policy revision.
   564  func (s *SSHMeta) PolicyWait(revisionNum int) *CmdRes {
   565  	return s.ExecCilium(fmt.Sprintf("policy wait %d", revisionNum))
   566  }
   567  
   568  // ReportFailed gathers relevant Cilium runtime data and logs for debugging
   569  // purposes.
   570  func (s *SSHMeta) ReportFailed(commands ...string) {
   571  	if config.CiliumTestConfig.SkipLogGathering {
   572  		ginkgoext.GinkgoPrint("Skipped gathering logs (-cilium.skipLogs=true)\n")
   573  		return
   574  	}
   575  
   576  	// Log the following line to both the log file, and to console to delineate
   577  	// when log gathering begins.
   578  	res := s.ExecCilium("endpoint list") // save the output in the logs
   579  	ginkgoext.GinkgoPrint(res.GetDebugMessage())
   580  
   581  	for _, cmd := range commands {
   582  		res = s.ExecWithSudo(fmt.Sprintf("%s", cmd), ExecOptions{SkipLog: true})
   583  		ginkgoext.GinkgoPrint(res.GetDebugMessage())
   584  	}
   585  
   586  	s.DumpCiliumCommandOutput()
   587  	s.GatherLogs()
   588  	s.GatherDockerLogs()
   589  }
   590  
   591  // ValidateEndpointsAreCorrect is a function that validates that all Docker
   592  // container that are in the given docker network are correct as cilium
   593  // endpoints.
   594  func (s *SSHMeta) ValidateEndpointsAreCorrect(dockerNetwork string) error {
   595  	endpointsFilter := `{range[*]}{.status.external-identifiers.container-id}{"="}{.id}{"\n"}{end}`
   596  	jqFilter := `.[].Containers|keys |.[]`
   597  
   598  	res := s.Exec(fmt.Sprintf("docker network inspect %s | jq -r '%s'", dockerNetwork, jqFilter))
   599  	if !res.WasSuccessful() {
   600  		return errors.New("Cannot get Docker containers in the given network")
   601  	}
   602  
   603  	epRes := s.ExecCilium(fmt.Sprintf("endpoint list -o jsonpath='%s'", endpointsFilter))
   604  	if !epRes.WasSuccessful() {
   605  		return errors.New("Cannot get cilium endpoint list")
   606  	}
   607  
   608  	endpoints := epRes.KVOutput()
   609  	for _, containerID := range res.ByLines() {
   610  		_, exists := endpoints[containerID]
   611  		if !exists {
   612  
   613  			return fmt.Errorf("ContainerID %s is not present in the endpoint list", containerID)
   614  		}
   615  	}
   616  	return nil
   617  }
   618  
   619  // ValidateNoErrorsInLogs checks in cilium logs since the given duration (By
   620  // default `CurrentGinkgoTestDescription().Duration`) do not contain `panic`,
   621  // `deadlocks` or `segmentation faults` messages . In case of any of these
   622  // messages, it'll mark the test as failed.
   623  func (s *SSHMeta) ValidateNoErrorsInLogs(duration time.Duration) {
   624  	logsCmd := fmt.Sprintf(`sudo journalctl -au %s --since '%v seconds ago'`,
   625  		DaemonName, duration.Seconds())
   626  	logs := s.Exec(logsCmd, ExecOptions{SkipLog: true}).Output().String()
   627  
   628  	defer func() {
   629  		// Keep the cilium logs for the given test in a separate file.
   630  		testPath, err := CreateReportDirectory()
   631  		if err != nil {
   632  			s.logger.WithError(err).Error("Cannot create report directory")
   633  			return
   634  		}
   635  		err = ioutil.WriteFile(
   636  			fmt.Sprintf("%s/%s", testPath, CiliumTestLog),
   637  			[]byte(logs), LogPerm)
   638  
   639  		if err != nil {
   640  			s.logger.WithError(err).Errorf("Cannot create %s", CiliumTestLog)
   641  		}
   642  	}()
   643  
   644  	failIfContainsBadLogMsg(logs)
   645  
   646  	fmt.Fprintf(CheckLogs, logutils.LogErrorsSummary(logs))
   647  }
   648  
   649  // PprofReport runs pprof each 5 minutes and saves the data into the test
   650  // folder saved with pprof suffix.
   651  func (s *SSHMeta) PprofReport() {
   652  	PProfCadence := 5 * time.Minute
   653  	ticker := time.NewTicker(PProfCadence)
   654  	log := s.logger.WithField("subsys", "pprofReport")
   655  
   656  	for {
   657  		select {
   658  		case <-ticker.C:
   659  
   660  			testPath, err := CreateReportDirectory()
   661  			if err != nil {
   662  				log.WithError(err).Errorf("cannot create test result path '%s'", testPath)
   663  				return
   664  			}
   665  			d := time.Now().Add(50 * time.Second)
   666  			ctx, cancel := context.WithDeadline(context.Background(), d)
   667  
   668  			res := s.ExecInBackground(ctx, `sudo gops pprof-cpu $(pgrep cilium-agent)`)
   669  
   670  			err = res.WaitUntilMatch("Profiling dump saved to")
   671  			if err != nil {
   672  				log.WithError(err).Error("Cannot get pprof report")
   673  			}
   674  
   675  			files := s.Exec("ls -1 /tmp/")
   676  			for _, file := range files.ByLines() {
   677  				if !strings.Contains(file, "profile") {
   678  					continue
   679  				}
   680  
   681  				dest := filepath.Join(
   682  					BasePath, testPath,
   683  					fmt.Sprintf("%s.pprof", file))
   684  				_ = s.ExecWithSudo(fmt.Sprintf("mv /tmp/%s %s", file, dest))
   685  			}
   686  			cancel()
   687  		}
   688  	}
   689  }
   690  
   691  // DumpCiliumCommandOutput runs a variety of Cilium CLI commands and dumps their
   692  // output to files. These files are gathered as part of each Jenkins job for
   693  // postmortem debugging of build failures.
   694  func (s *SSHMeta) DumpCiliumCommandOutput() {
   695  
   696  	testPath, err := CreateReportDirectory()
   697  	if err != nil {
   698  		s.logger.WithError(err).Errorf(
   699  			"cannot create test results path '%s'", testPath)
   700  		return
   701  	}
   702  	reportMap(testPath, ciliumCLICommands, s)
   703  
   704  	// No need to create file for bugtool because it creates an archive of files
   705  	// for us.
   706  	res := s.ExecWithSudo(
   707  		fmt.Sprintf("%s -t %s", CiliumBugtool, filepath.Join(BasePath, testPath)),
   708  		ExecOptions{SkipLog: true})
   709  	if !res.WasSuccessful() {
   710  		s.logger.Errorf("Error running bugtool: %s", res.CombineOutput())
   711  	}
   712  
   713  }
   714  
   715  // GatherLogs dumps Cilium, Cilium Docker, key-value store logs, and gops output
   716  // to the directory testResultsPath
   717  func (s *SSHMeta) GatherLogs() {
   718  	ciliumLogCommands := map[string]string{
   719  		fmt.Sprintf("sudo journalctl -au %s --no-pager", DaemonName):             "cilium.log",
   720  		fmt.Sprintf("sudo journalctl -au %s --no-pager", CiliumDockerDaemonName): "cilium-docker.log",
   721  		"sudo docker logs cilium-consul":                                         "consul.log",
   722  	}
   723  
   724  	testPath, err := CreateReportDirectory()
   725  	if err != nil {
   726  		s.logger.WithError(err).Errorf(
   727  			"cannot create test results path '%s'", testPath)
   728  		return
   729  	}
   730  	reportMap(testPath, ciliumLogCommands, s)
   731  
   732  	ciliumStateCommands := []string{
   733  		fmt.Sprintf("sudo rsync -rv --exclude=*.sock %s %s", RunDir, filepath.Join(BasePath, testPath, "lib")),
   734  		fmt.Sprintf("sudo rsync -rv --exclude=*.sock %s %s", LibDir, filepath.Join(BasePath, testPath, "run")),
   735  		fmt.Sprintf("sudo mv /tmp/core* %s", filepath.Join(BasePath, testPath)),
   736  	}
   737  
   738  	for _, cmd := range ciliumStateCommands {
   739  		res := s.Exec(cmd, ExecOptions{SkipLog: true})
   740  		if !res.WasSuccessful() {
   741  			s.logger.Errorf("cannot gather files for cmd '%s': %s", cmd, res.CombineOutput())
   742  		}
   743  	}
   744  }
   745  
   746  // ServiceAdd creates a new Cilium service with the provided ID, frontend,
   747  // backends. Returns the result of creating said service.
   748  func (s *SSHMeta) ServiceAdd(id int, frontend string, backends []string) *CmdRes {
   749  	cmd := fmt.Sprintf(
   750  		"service update --frontend '%s' --backends '%s' --id '%d' --rev",
   751  		frontend, strings.Join(backends, ","), id)
   752  	return s.ExecCilium(cmd)
   753  }
   754  
   755  // ServiceIsSynced checks that the Cilium service with the specified id has its
   756  // metadata match that of the load balancer BPF maps
   757  func (s *SSHMeta) ServiceIsSynced(id int) (bool, error) {
   758  	var svc *models.Service
   759  	svcRes := s.ServiceGet(id)
   760  	if !svcRes.WasSuccessful() {
   761  		return false, fmt.Errorf("cannot get service id %d: %s", id, svcRes.CombineOutput())
   762  	}
   763  	err := svcRes.Unmarshal(&svc)
   764  	if err != nil {
   765  		return false, err
   766  	}
   767  
   768  	bpfLB, err := s.BpfLBList(false)
   769  	if err != nil {
   770  		return false, err
   771  	}
   772  
   773  	frontendAddr := net.JoinHostPort(
   774  		svc.Status.Realized.FrontendAddress.IP,
   775  		fmt.Sprintf("%d", svc.Status.Realized.FrontendAddress.Port))
   776  	lb, ok := bpfLB[frontendAddr]
   777  	if ok == false {
   778  		return false, fmt.Errorf(
   779  			"frontend address from the service %d does not have it's corresponding frontend address(%s) on bpf maps",
   780  			id, frontendAddr)
   781  	}
   782  
   783  	for _, backendAddr := range svc.Status.Realized.BackendAddresses {
   784  		result := false
   785  		backendSVC := net.JoinHostPort(
   786  			*backendAddr.IP,
   787  			fmt.Sprintf("%d", backendAddr.Port))
   788  		target := fmt.Sprintf("%s (%d)", backendSVC, id)
   789  
   790  		for _, addr := range lb {
   791  			if addr == target {
   792  				result = true
   793  			}
   794  		}
   795  		if result == false {
   796  			return false, fmt.Errorf(
   797  				"backend address %s does not exists on BPF load balancer metadata id=%d", target, id)
   798  		}
   799  	}
   800  	return true, nil
   801  }
   802  
   803  // ServiceList returns the output of  `cilium service list`
   804  func (s *SSHMeta) ServiceList() *CmdRes {
   805  	return s.ExecCilium("service list -o json")
   806  }
   807  
   808  // ServiceGet is a wrapper around `cilium service get <id>`. It returns the
   809  // result of retrieving said service.
   810  func (s *SSHMeta) ServiceGet(id int) *CmdRes {
   811  	return s.ExecCilium(fmt.Sprintf("service get '%d' -o json", id))
   812  }
   813  
   814  // ServiceGetFrontendAddress returns a string with the frontend address and
   815  // port. It returns an error if the ID cannot be retrieved.
   816  func (s *SSHMeta) ServiceGetFrontendAddress(id int) (string, error) {
   817  
   818  	var svc *models.Service
   819  	res := s.ServiceGet(id)
   820  	if !res.WasSuccessful() {
   821  		return "", fmt.Errorf("Cannot get service id %d: %s", id, res.CombineOutput())
   822  	}
   823  
   824  	err := res.Unmarshal(&svc)
   825  	if err != nil {
   826  		return "", err
   827  	}
   828  
   829  	frontendAddress := net.JoinHostPort(
   830  		svc.Status.Realized.FrontendAddress.IP,
   831  		fmt.Sprintf("%d", svc.Status.Realized.FrontendAddress.Port))
   832  	return frontendAddress, nil
   833  }
   834  
   835  // ServiceGetIds returns an array with the IDs of all Cilium services. Returns
   836  // an error if the IDs cannot be retrieved
   837  func (s *SSHMeta) ServiceGetIds() ([]string, error) {
   838  	filter := `{range [*]}{@.status.realized.id}{"\n"}{end}`
   839  	res, err := s.ServiceList().Filter(filter)
   840  	if err != nil {
   841  		return nil, err
   842  	}
   843  	// trim the trailing \n
   844  	trimmed := strings.Trim(res.String(), "\n")
   845  	return strings.Split(trimmed, "\n"), nil
   846  }
   847  
   848  // ServiceDel is a wrapper around `cilium service delete <id>`. It returns the
   849  // result of deleting said service.
   850  func (s *SSHMeta) ServiceDel(id int) *CmdRes {
   851  	return s.ExecCilium(fmt.Sprintf("service delete '%d'", id))
   852  }
   853  
   854  // ServiceDelAll is a wrapper around `cilium service delete --all`. It returns the
   855  // result of the command.
   856  func (s *SSHMeta) ServiceDelAll() *CmdRes {
   857  	return s.ExecCilium("service delete --all")
   858  }
   859  
   860  // SetUpCilium sets up Cilium as a systemd service with a hardcoded set of options. It
   861  // returns an error if any of the operations needed to start Cilium fails.
   862  func (s *SSHMeta) SetUpCilium() error {
   863  	return s.SetUpCiliumWithOptions("--tofqdns-enable-poller=true")
   864  }
   865  
   866  // SetUpCiliumWithOptions sets up Cilium as a systemd service with a given set of options. It
   867  // returns an error if any of the operations needed to start Cilium fail.
   868  func (s *SSHMeta) SetUpCiliumWithOptions(ciliumOpts string) error {
   869  	ciliumOpts += " --exclude-local-address=" + DockerBridgeIP + "/32"
   870  	ciliumOpts += " --exclude-local-address=" + FakeIPv4WorldAddress + "/32"
   871  	ciliumOpts += " --exclude-local-address=" + FakeIPv6WorldAddress + "/128"
   872  
   873  	systemdTemplate := `
   874  PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin:/sbin:/bin
   875  CILIUM_OPTS=--kvstore consul --kvstore-opt consul.address=127.0.0.1:8500 --debug --pprof=true --log-system-load %s
   876  INITSYSTEM=SYSTEMD`
   877  
   878  	err := RenderTemplateToFile("cilium", fmt.Sprintf(systemdTemplate, ciliumOpts), os.ModePerm)
   879  	if err != nil {
   880  		return err
   881  	}
   882  	defer os.Remove("cilium")
   883  
   884  	res := s.Exec("sudo cp /vagrant/cilium /etc/sysconfig/cilium")
   885  	if !res.WasSuccessful() {
   886  		return fmt.Errorf("%s", res.CombineOutput())
   887  	}
   888  	res = s.Exec("sudo systemctl restart cilium")
   889  	if !res.WasSuccessful() {
   890  		return fmt.Errorf("%s", res.CombineOutput())
   891  	}
   892  	return nil
   893  }
   894  
   895  func (s *SSHMeta) SetUpCiliumWithSockops() error {
   896  	return s.SetUpCiliumWithOptions("--sockops-enable --tofqdns-enable-poller=true")
   897  }
   898  
   899  // SetUpCiliumInIpvlanMode starts cilium-agent in the ipvlan mode
   900  func (s *SSHMeta) SetUpCiliumInIpvlanMode(ipvlanMasterDevice string) error {
   901  	return s.SetUpCiliumWithOptions("--tunnel=disabled --datapath-mode=ipvlan --ipvlan-master-device=" + ipvlanMasterDevice)
   902  }
   903  
   904  // WaitUntilReady waits until the output of `cilium status` returns with code
   905  // zero. Returns an error if the output of `cilium status` returns a nonzero
   906  // return code after the specified timeout duration has elapsed.
   907  func (s *SSHMeta) WaitUntilReady(timeout time.Duration) error {
   908  
   909  	body := func() bool {
   910  		res := s.ExecCilium("status")
   911  		s.logger.Infof("Cilium status is %t", res.WasSuccessful())
   912  		return res.WasSuccessful()
   913  	}
   914  	err := WithTimeout(body, "Cilium is not ready", &TimeoutConfig{Timeout: timeout})
   915  	return err
   916  }
   917  
   918  // RestartCilium reloads cilium on this host, then waits for it to become
   919  // ready again.
   920  func (s *SSHMeta) RestartCilium() error {
   921  	ginkgoext.By("Restarting Cilium")
   922  
   923  	res := s.ExecWithSudo("systemctl restart cilium")
   924  	if !res.WasSuccessful() {
   925  		return fmt.Errorf("%s", res.CombineOutput())
   926  	}
   927  	if err := s.WaitUntilReady(CiliumStartTimeout); err != nil {
   928  		return err
   929  	}
   930  	if !s.WaitEndpointsReady() {
   931  		return fmt.Errorf("Endpoints are not ready after timeout")
   932  	}
   933  	return nil
   934  }
   935  
   936  // AddIPToLoopbackDevice adds the specified IP (assumed to be in form <ip>/<mask>)
   937  // to the loopback device on s.
   938  func (s *SSHMeta) AddIPToLoopbackDevice(ip string) *CmdRes {
   939  	return s.ExecWithSudo(fmt.Sprintf("ip addr add dev lo %s", ip))
   940  }
   941  
   942  // RemoveIPFromLoopbackDevice removes the specified IP (assumed to be in form <ip>/<mask>)
   943  // from the loopback device on s.
   944  func (s *SSHMeta) RemoveIPFromLoopbackDevice(ip string) *CmdRes {
   945  	return s.ExecWithSudo(fmt.Sprintf("ip addr del dev lo %s", ip))
   946  }
   947  
   948  // FlushGlobalConntrackTable flushes the global connection tracking table.
   949  func (s *SSHMeta) FlushGlobalConntrackTable() *CmdRes {
   950  	return s.ExecCilium("bpf ct flush global")
   951  }