<commit_before>// Copyright 2018-2019 Authors of Cilium
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package k8s

import (
	"fmt"
	"net"
	"strings"

	"github.com/cilium/cilium/pkg/annotation"
	"github.com/cilium/cilium/pkg/comparator"
	"github.com/cilium/cilium/pkg/k8s/types"
	"github.com/cilium/cilium/pkg/loadbalancer"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/node"
	"github.com/cilium/cilium/pkg/option"
	"github.com/cilium/cilium/pkg/service"

	"github.com/sirupsen/logrus"
	"k8s.io/api/core/v1"
)

func getAnnotationIncludeExternal(svc *types.Service) bool {
	if value, ok := svc.ObjectMeta.Annotations[annotation.GlobalService]; ok {
		return strings.ToLower(value) == "true"
	}

	return false
}

func getAnnotationShared(svc *types.Service) bool {
	if value, ok := svc.ObjectMeta.Annotations[annotation.SharedService]; ok {
		return strings.ToLower(value) == "true"
	}

	return getAnnotationIncludeExternal(svc)
}

// ParseServiceID parses a Kubernetes service and returns the ServiceID
func ParseServiceID(svc *types.Service) ServiceID {
	return ServiceID{
		Name:        svc.ObjectMeta.Name,
		Namespace:   svc.ObjectMeta.Namespace,
		k8sExternal: len(svc.Spec.ExternalIPs) != 0,
	}
}

// ParseService parses a Kubernetes service and returns a Service
func ParseService(svc *types.Service) (ServiceID, *Service) {
	scopedLog := log.WithFields(logrus.Fields{
		logfields.K8sSvcName:    svc.ObjectMeta.Name,
		logfields.K8sNamespace:  svc.ObjectMeta.Namespace,
		logfields.K8sAPIVersion: svc.TypeMeta.APIVersion,
		logfields.K8sSvcType:    svc.Spec.Type,
	})

	svcID := ParseServiceID(svc)

	switch svc.Spec.Type {
	case v1.ServiceTypeClusterIP, v1.ServiceTypeNodePort, v1.ServiceTypeLoadBalancer:
		break

	case v1.ServiceTypeExternalName:
		// External-name services must be ignored
		return svcID, nil

	default:
		scopedLog.Warn("Ignoring k8s service: unsupported type")
		return svcID, nil
	}

	if svc.Spec.ClusterIP == "" {
		return svcID, nil
	}

	clusterIP := net.ParseIP(svc.Spec.ClusterIP)
	headless := false
	if strings.ToLower(svc.Spec.ClusterIP) == "none" {
		headless = true
	}
	svcInfo := NewService(clusterIP, headless, svc.Labels, svc.Spec.Selector)
	svcInfo.IncludeExternal = getAnnotationIncludeExternal(svc)
	svcInfo.Shared = getAnnotationShared(svc)

	if len(svc.Spec.ExternalIPs) != 0 {
		// Accordingly with k8s docs: Traffic that ingresses into the cluster
		// with the external IP (as destination IP), on the service port, will
		// be routed to one of the service endpoints.
		// For Cilium this means the backends are the cartesian product of
		// service ports x external IPs + real k8s endpoints and the service IP
		// will continue to be the service IP.
		eps := newEndpoints()
		for _, ipStr := range svc.Spec.ExternalIPs {
			portCfg := service.PortConfiguration{}
			for _, port := range svc.Spec.Ports {
				portCfg[port.Name] = loadbalancer.NewL4Addr(loadbalancer.L4Type(port.Protocol), uint16(port.Port))
			}
			eps.Backends[ipStr] = portCfg
		}
		svcInfo.K8sExternalIPs = eps
	}

	for _, port := range svc.Spec.Ports {
		p := loadbalancer.NewFEPort(loadbalancer.L4Type(port.Protocol), uint16(port.Port))
		portName := loadbalancer.FEPortName(port.Name)
		if _, ok := svcInfo.Ports[portName]; !ok {
			svcInfo.Ports[portName] = p
		}
		// This is a hack;-( In the case of NodePort service, we need to create
		// two surrogate frontends per IP protocol - one with a zero IP addr used
		// by the host-lb, another with a public iface IP addr. For each frontend
		// we will need to store a service ID used for a reverse NAT translation
		// and for deleting a service.
		// Unfortunately, doing this in daemon/{loadbalancer,k8s_watcher}.go
		// would introduce more complexity in already too complex LB codebase,
		// so for now (until we have refactored the LB code) keep NodePort
		// frontends in Service.NodePorts.
		if svc.Spec.Type == v1.ServiceTypeNodePort {
			if option.Config.EnableNodePort {
				if _, ok := svcInfo.NodePorts[portName]; !ok {
					svcInfo.NodePorts[portName] =
						make(map[string]*loadbalancer.L3n4AddrID)
				}
				proto := loadbalancer.L4Type(port.Protocol)
				port := uint16(port.NodePort)
				id := loadbalancer.ID(0) // will be allocated by k8s_watcher

				// TODO(brb) switch to if-clause when dual stack is supported
				switch {
				case option.Config.EnableIPv4 &&
					clusterIP != nil && !strings.Contains(svc.Spec.ClusterIP, ":"):

					nodePortFE := loadbalancer.NewL3n4AddrID(proto, net.IPv4(0, 0, 0, 0), port, id)
					svcInfo.NodePorts[portName][nodePortFE.String()] = nodePortFE
					nodePortFE = loadbalancer.NewL3n4AddrID(proto, node.GetExternalIPv4(), port, id)
					svcInfo.NodePorts[portName][nodePortFE.String()] = nodePortFE
				case option.Config.EnableIPv6 &&
					clusterIP != nil && strings.Contains(svc.Spec.ClusterIP, ":"):

					nodePortFE := loadbalancer.NewL3n4AddrID(proto, net.IPv6zero, port, id)
					svcInfo.NodePorts[portName][nodePortFE.String()] = nodePortFE
					nodePortFE = loadbalancer.NewL3n4AddrID(proto, node.GetIPv6(), port, id)
					svcInfo.NodePorts[portName][nodePortFE.String()] = nodePortFE
				}
			}
		}
	}

	return svcID, svcInfo
}

// ServiceID identities the Kubernetes service
type ServiceID struct {
	Name      string `json:"serviceName,omitempty"`
	Namespace string `json:"namespace,omitempty"`
	// k8sExternal accounts if the service contains external K8s IPs or not.
	k8sExternal bool
}

// String returns the string representation of a service ID
func (s ServiceID) String() string {
	return fmt.Sprintf("%s/%s", s.Namespace, s.Name)
}

// ParseServiceIDFrom returns a ServiceID derived from the given kubernetes
// service FQDN.
func ParseServiceIDFrom(dn string) *ServiceID {
	// typical service name "cilium-etcd-client.kube-system.svc"
	idx1 := strings.IndexByte(dn, '.')
	if idx1 >= 0 {
		svc := ServiceID{<commit_msg>k8s: Add surrogate NodePort frontend with cilium_host IP addr<commit_after>// Copyright 2018-2019 Authors of Cilium
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package k8s

import (
	"fmt"
	"net"
	"strings"

	"github.com/cilium/cilium/pkg/annotation"
	"github.com/cilium/cilium/pkg/comparator"
	"github.com/cilium/cilium/pkg/k8s/types"
	"github.com/cilium/cilium/pkg/loadbalancer"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/node"
	"github.com/cilium/cilium/pkg/option"
	"github.com/cilium/cilium/pkg/service"

	"github.com/sirupsen/logrus"
	"k8s.io/api/core/v1"
)

func getAnnotationIncludeExternal(svc *types.Service) bool {
	if value, ok := svc.ObjectMeta.Annotations[annotation.GlobalService]; ok {
		return strings.ToLower(value) == "true"
	}

	return false
}

func getAnnotationShared(svc *types.Service) bool {
	if value, ok := svc.ObjectMeta.Annotations[annotation.SharedService]; ok {
		return strings.ToLower(value) == "true"
	}

	return getAnnotationIncludeExternal(svc)
}

// ParseServiceID parses a Kubernetes service and returns the ServiceID
func ParseServiceID(svc *types.Service) ServiceID {
	return ServiceID{
		Name:        svc.ObjectMeta.Name,
		Namespace:   svc.ObjectMeta.Namespace,
		k8sExternal: len(svc.Spec.ExternalIPs) != 0,
	}
}

// ParseService parses a Kubernetes service and returns a Service
func ParseService(svc *types.Service) (ServiceID, *Service) {
	scopedLog := log.WithFields(logrus.Fields{
		logfields.K8sSvcName:    svc.ObjectMeta.Name,
		logfields.K8sNamespace:  svc.ObjectMeta.Namespace,
		logfields.K8sAPIVersion: svc.TypeMeta.APIVersion,
		logfields.K8sSvcType:    svc.Spec.Type,
	})

	svcID := ParseServiceID(svc)

	switch svc.Spec.Type {
	case v1.ServiceTypeClusterIP, v1.ServiceTypeNodePort, v1.ServiceTypeLoadBalancer:
		break

	case v1.ServiceTypeExternalName:
		// External-name services must be ignored
		return svcID, nil

	default:
		scopedLog.Warn("Ignoring k8s service: unsupported type")
		return svcID, nil
	}

	if svc.Spec.ClusterIP == "" {
		return svcID, nil
	}

	clusterIP := net.ParseIP(svc.Spec.ClusterIP)
	headless := false
	if strings.ToLower(svc.Spec.ClusterIP) == "none" {
		headless = true
	}
	svcInfo := NewService(clusterIP, headless, svc.Labels, svc.Spec.Selector)
	svcInfo.IncludeExternal = getAnnotationIncludeExternal(svc)
	svcInfo.Shared = getAnnotationShared(svc)

	if len(svc.Spec.ExternalIPs) != 0 {
		// Accordingly with k8s docs: Traffic that ingresses into the cluster
		// with the external IP (as destination IP), on the service port, will
		// be routed to one of the service endpoints.
		// For Cilium this means the backends are the cartesian product of
		// service ports x external IPs + real k8s endpoints and the service IP
		// will continue to be the service IP.
		eps := newEndpoints()
		for _, ipStr := range svc.Spec.ExternalIPs {
			portCfg := service.PortConfiguration{}
			for _, port := range svc.Spec.Ports {
				portCfg[port.Name] = loadbalancer.NewL4Addr(loadbalancer.L4Type(port.Protocol), uint16(port.Port))
			}
			eps.Backends[ipStr] = portCfg
		}
		svcInfo.K8sExternalIPs = eps
	}

	for _, port := range svc.Spec.Ports {
		p := loadbalancer.NewFEPort(loadbalancer.L4Type(port.Protocol), uint16(port.Port))
		portName := loadbalancer.FEPortName(port.Name)
		if _, ok := svcInfo.Ports[portName]; !ok {
			svcInfo.Ports[portName] = p
		}
		// This is a hack;-( In the case of NodePort service, we need to create
		// three surrogate frontends per IP protocol - one with a zero IP addr used
		// by the host-lb, one with a public iface IP addr and one with cilium_host
		// IP addr.
		// For each frontend we will need to store a service ID used for a reverse
		// NAT translation and for deleting a service.
		// Unfortunately, doing this in daemon/{loadbalancer,k8s_watcher}.go
		// would introduce more complexity in already too complex LB codebase,
		// so for now (until we have refactored the LB code) keep NodePort
		// frontends in Service.NodePorts.
		if svc.Spec.Type == v1.ServiceTypeNodePort {
			if option.Config.EnableNodePort {
				if _, ok := svcInfo.NodePorts[portName]; !ok {
					svcInfo.NodePorts[portName] =
						make(map[string]*loadbalancer.L3n4AddrID)
				}
				proto := loadbalancer.L4Type(port.Protocol)
				port := uint16(port.NodePort)
				id := loadbalancer.ID(0) // will be allocated by k8s_watcher

				// TODO(brb) switch to if-clause when dual stack is supported
				switch {
				case option.Config.EnableIPv4 &&
					clusterIP != nil && !strings.Contains(svc.Spec.ClusterIP, ":"):

					for _, ip := range []net.IP{net.IPv4(0, 0, 0, 0), node.GetNodePortIPv4(), node.GetInternalIPv4()} {
						nodePortFE := loadbalancer.NewL3n4AddrID(proto, ip, port, id)
						svcInfo.NodePorts[portName][nodePortFE.String()] = nodePortFE

					}
				case option.Config.EnableIPv6 &&
					clusterIP != nil && strings.Contains(svc.Spec.ClusterIP, ":"):

					for _, ip := range []net.IP{net.IPv6zero, node.GetNodePortIPv6(), node.GetIPv6()} {
						nodePortFE := loadbalancer.NewL3n4AddrID(proto, ip, port, id)
						svcInfo.NodePorts[portName][nodePortFE.String()] = nodePortFE
					}
				}
			}
		}
	}

	return svcID, svcInfo
}

// ServiceID identities the Kubernetes service
type ServiceID struct {
	Name      string `json:"serviceName,omitempty"`
	Namespace string `json:"namespace,omitempty"`
	// k8sExternal accounts if the service contains external K8s IPs or not.
	k8sExternal bool
}

// String returns the string representation of a service ID
func (s ServiceID) String() string {
	return fmt.Sprintf("%s/%s", s.Namespace, s.Name)
}

// ParseServiceIDFrom returns a ServiceID derived from the given kubernetes
// service FQDN.
func ParseServiceIDFrom(dn string) *ServiceID {
	// typical service name "cilium-etcd-client.kube-system.svc"
	idx1 := strings.IndexByte(dn, '.')
	if idx1 >= 0 {
		svc := ServiceID{
{'fraction_non_alphanumeric': 0.1121692295683397, 'fraction_numerical': 0.011006166575599094, 'mean_word_length': 3.903176425564485, 'pattern_counts': {'":': 4, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5959713', 'n_tokens_mistral': 4641, 'n_tokens_neox': 4276, 'n_words': 1127}