github.com/elfadel/cilium@v1.6.12/pkg/proxy/kafka_test.go (about)

     1  // Copyright 2017 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  // +build !privileged_tests
    16  
    17  package proxy
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"net"
    23  	"strconv"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/cilium/cilium/common/addressing"
    28  	"github.com/cilium/cilium/pkg/completion"
    29  	"github.com/cilium/cilium/pkg/datapath"
    30  	"github.com/cilium/cilium/pkg/endpoint"
    31  	"github.com/cilium/cilium/pkg/endpoint/regeneration"
    32  	"github.com/cilium/cilium/pkg/endpointmanager"
    33  	"github.com/cilium/cilium/pkg/identity"
    34  	"github.com/cilium/cilium/pkg/identity/cache"
    35  	"github.com/cilium/cilium/pkg/lock"
    36  	"github.com/cilium/cilium/pkg/logging/logfields"
    37  	monitorAPI "github.com/cilium/cilium/pkg/monitor/api"
    38  	"github.com/cilium/cilium/pkg/policy"
    39  	"github.com/cilium/cilium/pkg/policy/api"
    40  	"github.com/cilium/cilium/pkg/proxy/logger"
    41  	"github.com/cilium/cilium/pkg/revert"
    42  
    43  	"github.com/optiopay/kafka"
    44  	"github.com/optiopay/kafka/proto"
    45  	"github.com/sirupsen/logrus"
    46  
    47  	. "gopkg.in/check.v1"
    48  )
    49  
    50  // Hook up gocheck into the "go test" runner.
    51  func Test(t *testing.T) {
    52  	TestingT(t)
    53  }
    54  
    55  type proxyTestSuite struct {
    56  	repo *policy.Repository
    57  }
    58  
    59  var _ = Suite(&proxyTestSuite{})
    60  
    61  func (s *proxyTestSuite) SetUpSuite(c *C) {
    62  	s.repo = policy.NewPolicyRepository()
    63  }
    64  
    65  func (s *proxyTestSuite) GetPolicyRepository() *policy.Repository {
    66  	return s.repo
    67  }
    68  
    69  func (s *proxyTestSuite) UpdateProxyRedirect(e regeneration.EndpointUpdater, l4 *policy.L4Filter, wg *completion.WaitGroup) (uint16, error, revert.FinalizeFunc, revert.RevertFunc) {
    70  	return 0, nil, nil, nil
    71  }
    72  
    73  func (s *proxyTestSuite) RemoveProxyRedirect(e regeneration.EndpointInfoSource, id string, wg *completion.WaitGroup) (error, revert.FinalizeFunc, revert.RevertFunc) {
    74  	return nil, nil, nil
    75  }
    76  
    77  func (s *proxyTestSuite) UpdateNetworkPolicy(e regeneration.EndpointUpdater, policy *policy.L4Policy,
    78  	proxyWaitGroup *completion.WaitGroup) (error, revert.RevertFunc) {
    79  	return nil, nil
    80  }
    81  
    82  func (s *proxyTestSuite) RemoveNetworkPolicy(e regeneration.EndpointInfoSource) {}
    83  
    84  func (s *proxyTestSuite) QueueEndpointBuild(ctx context.Context, epID uint64) (func(), error) {
    85  	return nil, nil
    86  }
    87  
    88  func (s *proxyTestSuite) RemoveFromEndpointQueue(epID uint64) {}
    89  
    90  func (s *proxyTestSuite) GetCompilationLock() *lock.RWMutex {
    91  	return nil
    92  }
    93  
    94  func (s *proxyTestSuite) SendNotification(typ monitorAPI.AgentNotification, text string) error {
    95  	return nil
    96  }
    97  
    98  func (s *proxyTestSuite) Datapath() datapath.Datapath {
    99  	return nil
   100  }
   101  
   102  func (s *proxyTestSuite) GetNodeSuffix() string {
   103  	return ""
   104  }
   105  
   106  func (s *proxyTestSuite) UpdateIdentities(added, deleted cache.IdentityCache) {}
   107  
   108  type DummySelectorCacheUser struct{}
   109  
   110  func (d *DummySelectorCacheUser) IdentitySelectionUpdated(selector policy.CachedSelector, selections, added, deleted []identity.NumericIdentity) {
   111  }
   112  
   113  var (
   114  	localEndpointMock logger.EndpointUpdater = &proxyUpdaterMock{
   115  		id:       1000,
   116  		ipv4:     "10.0.0.1",
   117  		ipv6:     "f00d::1",
   118  		labels:   []string{"id.foo", "id.bar"},
   119  		identity: identity.NumericIdentity(256),
   120  	}
   121  
   122  	dummySelectorCacheUser = &DummySelectorCacheUser{}
   123  	testSelectorCache      = policy.NewSelectorCache(cache.IdentityCache{})
   124  
   125  	wildcardCachedSelector, _ = testSelectorCache.AddIdentitySelector(dummySelectorCacheUser, api.WildcardEndpointSelector)
   126  )
   127  
   128  // newTestBrokerConf returns BrokerConf with default configuration adjusted for
   129  // tests
   130  func newTestBrokerConf(clientID string) kafka.BrokerConf {
   131  	conf := kafka.NewBrokerConf(clientID)
   132  	conf.DialTimeout = 400 * time.Millisecond
   133  	conf.LeaderRetryLimit = 10
   134  	conf.LeaderRetryWait = 2 * time.Millisecond
   135  	return conf
   136  }
   137  
   138  type loggerMap struct{}
   139  
   140  func fields(args ...interface{}) logrus.Fields {
   141  	fields := logrus.Fields{}
   142  	for i := 0; i+1 < len(args); i += 2 {
   143  		fields[args[i].(string)] = args[i+1]
   144  	}
   145  	return fields
   146  }
   147  
   148  func (loggerMap) Debug(msg string, args ...interface{}) { log.WithFields(fields(args...)).Debug(msg) }
   149  func (loggerMap) Info(msg string, args ...interface{})  { log.WithFields(fields(args...)).Info(msg) }
   150  func (loggerMap) Warn(msg string, args ...interface{})  { log.WithFields(fields(args...)).Warn(msg) }
   151  func (loggerMap) Error(msg string, args ...interface{}) { log.WithFields(fields(args...)).Error(msg) }
   152  
   153  var (
   154  	proxyAddress = "127.0.0.1"
   155  )
   156  
   157  type metadataTester struct {
   158  	host               string
   159  	port               uint16
   160  	topics             map[string]bool
   161  	allowCreate        bool
   162  	numGeneralFetches  int
   163  	numSpecificFetches int
   164  }
   165  
   166  func newMetadataHandler(srv *Server, allowCreate bool, proxyPort uint16) *metadataTester {
   167  	tester := &metadataTester{
   168  		host:        proxyAddress,
   169  		port:        proxyPort,
   170  		allowCreate: allowCreate,
   171  		topics:      make(map[string]bool),
   172  	}
   173  	tester.topics["allowedTopic"] = true
   174  	tester.topics["disallowedTopic"] = true
   175  	return tester
   176  }
   177  
   178  func (m *metadataTester) NumGeneralFetches() int {
   179  	return m.numGeneralFetches
   180  }
   181  
   182  func (m *metadataTester) NumSpecificFetches() int {
   183  	return m.numSpecificFetches
   184  }
   185  
   186  func (m *metadataTester) Handler() RequestHandler {
   187  	return func(request Serializable) Serializable {
   188  		req := request.(*proto.MetadataReq)
   189  
   190  		if len(req.Topics) == 0 {
   191  			m.numGeneralFetches++
   192  		} else {
   193  			m.numSpecificFetches++
   194  		}
   195  
   196  		resp := &proto.MetadataResp{
   197  			CorrelationID: req.CorrelationID,
   198  			Brokers: []proto.MetadataRespBroker{
   199  				{NodeID: 1, Host: m.host, Port: int32(m.port)},
   200  			},
   201  			Topics: []proto.MetadataRespTopic{},
   202  		}
   203  
   204  		wantsTopic := make(map[string]bool)
   205  		for _, topic := range req.Topics {
   206  			if m.allowCreate {
   207  				m.topics[topic] = true
   208  			}
   209  			wantsTopic[topic] = true
   210  		}
   211  
   212  		for topic := range m.topics {
   213  			// Return either all topics or only topics that they explicitly requested
   214  			_, explicitTopic := wantsTopic[topic]
   215  			if len(req.Topics) > 0 && !explicitTopic {
   216  				continue
   217  			}
   218  
   219  			resp.Topics = append(resp.Topics, proto.MetadataRespTopic{
   220  				Name: topic,
   221  				Partitions: []proto.MetadataRespPartition{
   222  					{
   223  						ID:       0,
   224  						Leader:   1,
   225  						Replicas: []int32{1},
   226  						Isrs:     []int32{1},
   227  					},
   228  					{
   229  						ID:       1,
   230  						Leader:   1,
   231  						Replicas: []int32{1},
   232  						Isrs:     []int32{1},
   233  					},
   234  				},
   235  			})
   236  		}
   237  		return resp
   238  	}
   239  }
   240  
   241  func (s *proxyTestSuite) TestKafkaRedirect(c *C) {
   242  	server := NewServer()
   243  	server.Start()
   244  	defer server.Close()
   245  
   246  	log.WithFields(logrus.Fields{
   247  		"address": server.Address(),
   248  	}).Debug("Started kafka server")
   249  
   250  	pp := getProxyPort(policy.ParserTypeKafka, true)
   251  	c.Assert(pp.configured, Equals, false)
   252  	var err error
   253  	pp.proxyPort, err = allocatePort(pp.proxyPort, 10000, 20000)
   254  	c.Assert(err, IsNil)
   255  	c.Assert(pp.proxyPort, Not(Equals), 0)
   256  	pp.reservePort()
   257  	c.Assert(pp.configured, Equals, true)
   258  
   259  	proxyAddress := fmt.Sprintf("%s:%d", proxyAddress, uint16(pp.proxyPort))
   260  
   261  	kafkaRule1 := api.PortRuleKafka{APIKey: "metadata", APIVersion: "0"}
   262  	c.Assert(kafkaRule1.Sanitize(), IsNil)
   263  
   264  	kafkaRule2 := api.PortRuleKafka{APIKey: "produce", APIVersion: "0", Topic: "allowedTopic"}
   265  	c.Assert(kafkaRule2.Sanitize(), IsNil)
   266  
   267  	// Insert a mock EP to the endpointmanager so that DefaultEndpointInfoRegistry may find
   268  	// the EP ID by the IP.
   269  	ep := endpoint.NewEndpointWithState(s, uint16(localEndpointMock.GetID()), endpoint.StateReady)
   270  	ipv4, err := addressing.NewCiliumIPv4("127.0.0.1")
   271  	c.Assert(err, IsNil)
   272  	ep.IPv4 = ipv4
   273  	ep.UpdateLogger(nil)
   274  	endpointmanager.Insert(ep)
   275  	defer endpointmanager.Remove(ep)
   276  
   277  	_, dstPortStr, err := net.SplitHostPort(server.Address())
   278  	c.Assert(err, IsNil)
   279  	portInt, err := strconv.Atoi(dstPortStr)
   280  	c.Assert(err, IsNil)
   281  	r := newRedirect(localEndpointMock, pp, uint16(portInt))
   282  
   283  	r.rules = policy.L7DataMap{
   284  		wildcardCachedSelector: api.L7Rules{
   285  			Kafka: []api.PortRuleKafka{kafkaRule1, kafkaRule2},
   286  		},
   287  	}
   288  
   289  	redir, err := createKafkaRedirect(r, kafkaConfiguration{
   290  		lookupSrcID: func(mapname, remoteAddr, localAddr string, ingress bool) (uint32, error) {
   291  			return uint32(1000), nil
   292  		},
   293  		// Disable use of SO_MARK, IP_TRANSPARENT for tests
   294  		testMode: true,
   295  	}, DefaultEndpointInfoRegistry)
   296  	c.Assert(err, IsNil)
   297  	defer redir.Close(nil)
   298  
   299  	log.WithFields(logrus.Fields{
   300  		"address": proxyAddress,
   301  	}).Debug("Started kafka proxy")
   302  
   303  	server.Handle(MetadataRequest, newMetadataHandler(server, false, r.listener.proxyPort).Handler())
   304  
   305  	broker, err := kafka.Dial([]string{proxyAddress}, newTestBrokerConf("tester"))
   306  	if err != nil {
   307  		c.Fatalf("cannot create broker: %s", err)
   308  	}
   309  
   310  	// setup producer
   311  	prodConf := kafka.NewProducerConf()
   312  	prodConf.RetryWait = time.Millisecond
   313  	prodConf.Logger = loggerMap{}
   314  	producer := broker.Producer(prodConf)
   315  	messages := []*proto.Message{
   316  		{Value: []byte("first")},
   317  		{Value: []byte("second")},
   318  	}
   319  
   320  	// Start handling allowedTopic produce requests
   321  	server.Handle(ProduceRequest, func(request Serializable) Serializable {
   322  		req := request.(*proto.ProduceReq)
   323  		log.WithField(logfields.Request, logfields.Repr(req)).Debug("Handling req")
   324  		return &proto.ProduceResp{
   325  			CorrelationID: req.CorrelationID,
   326  			Topics: []proto.ProduceRespTopic{
   327  				{
   328  					Name: req.Topics[0].Name,
   329  					Partitions: []proto.ProduceRespPartition{
   330  						{
   331  							ID:     0,
   332  							Offset: 5,
   333  						},
   334  					},
   335  				},
   336  			},
   337  		}
   338  	})
   339  
   340  	// send a Produce request for an allowed topic
   341  	offset, err := producer.Produce("allowedTopic", 0, messages...)
   342  	c.Assert(err, IsNil)
   343  	c.Assert(offset, Equals, int64(5))
   344  
   345  	// send a Produce request for disallowed topic
   346  	_, err = producer.Produce("disallowedTopic", 0, messages...)
   347  	c.Assert(err, Equals, proto.ErrTopicAuthorizationFailed)
   348  
   349  	log.Debug("Testing done, closing listen socket")
   350  	finalize, _ := redir.Close(nil)
   351  	finalize()
   352  
   353  	// In order to see in the logs that the connections get closed after the
   354  	// 1-minute timeout, uncomment this line:
   355  	// time.Sleep(2 * time.Minute)
   356  }