blob: 873069944cd5df2d39e7bf01540089bc4f82e91c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 v1alpha1
import (
"encoding"
"fmt"
"net"
"reflect"
"sort"
"strconv"
"strings"
)
import (
"github.com/pkg/errors"
)
const (
KubeNamespaceTag = "k8s.dubbo.io/namespace"
KubeServiceTag = "k8s.dubbo.io/service-name"
KubePortTag = "k8s.dubbo.io/service-port"
)
const (
// Mandatory tag that has a reserved meaning in Dubbo.
ServiceTag = "dubbo.io/service"
ServiceUnknown = "unknown"
// Locality related tags
ZoneTag = "dubbo.io/zone"
MeshTag = "dubbo.io/mesh"
// Optional tag that has a reserved meaning in Dubbo.
// If absent, Dubbo will treat application's protocol as opaque TCP.
ProtocolTag = "dubbo.io/protocol"
// InstanceTag is set only for Dataplanes that implements headless services
InstanceTag = "dubbo.io/instance"
// External service tag
ExternalServiceTag = "dubbo.io/external-service-name"
// Listener tag is used to select Gateway listeners
ListenerTag = "gateways.dubbo.io/listener-name"
// Used for Service-less dataplanes
TCPPortReserved = 49151 // IANA Reserved
// DisplayName is a standard label that can be used to easier recognize policy name.
// On Kubernetes, Dubbo resource name contains namespace. Display name is original name without namespace.
// The name contains hash when the resource is synced from global to zone. In this case, display name is original name from originated CP.
DisplayName = "dubbo.io/display-name"
// ResourceOriginLabel is a standard label that has information about the origin of the resource.
// It can be either "global" or "zone".
ResourceOriginLabel = "dubbo.io/origin"
)
// extensions
const (
ApplicationName = "applicationName"
)
type ResourceOrigin string
const (
GlobalResourceOrigin ResourceOrigin = "global"
ZoneResourceOrigin ResourceOrigin = "zone"
)
func (o ResourceOrigin) IsValid() error {
switch o {
case GlobalResourceOrigin, ZoneResourceOrigin:
return nil
default:
return errors.Errorf("unknown resource origin %q", o)
}
}
type ProxyType string
const (
DataplaneProxyType ProxyType = "dataplane"
IngressProxyType ProxyType = "ingress"
EgressProxyType ProxyType = "egress"
)
func (t ProxyType) IsValid() error {
switch t {
case DataplaneProxyType, IngressProxyType, EgressProxyType:
return nil
}
return errors.Errorf("%s is not a valid proxy type", t)
}
type InboundInterface struct {
DataplaneAdvertisedIP string
DataplaneIP string
DataplanePort uint32
WorkloadIP string
WorkloadPort uint32
}
// We need to implement TextMarshaler because InboundInterface is used
// as a key for maps that are JSON encoded for logging.
var _ encoding.TextMarshaler = InboundInterface{}
func (i InboundInterface) MarshalText() ([]byte, error) {
return []byte(i.String()), nil
}
func (i InboundInterface) String() string {
return fmt.Sprintf("%s:%d:%d", i.DataplaneIP, i.DataplanePort, i.WorkloadPort)
}
func (i *InboundInterface) IsServiceLess() bool {
return i.DataplanePort == TCPPortReserved
}
type OutboundInterface struct {
DataplaneIP string
DataplanePort uint32
}
// We need to implement TextMarshaler because OutboundInterface is used
// as a key for maps that are JSON encoded for logging.
var _ encoding.TextMarshaler = OutboundInterface{}
func (i OutboundInterface) MarshalText() ([]byte, error) {
return []byte(i.String()), nil
}
func (i OutboundInterface) String() string {
return net.JoinHostPort(i.DataplaneIP,
strconv.FormatUint(uint64(i.DataplanePort), 10))
}
func (n *Dataplane_Networking) GetOutboundInterfaces() []OutboundInterface {
if n == nil {
return nil
}
ofaces := make([]OutboundInterface, len(n.Outbound))
for i, outbound := range n.Outbound {
ofaces[i] = n.ToOutboundInterface(outbound)
}
return ofaces
}
func (n *Dataplane_Networking) ToOutboundInterface(outbound *Dataplane_Networking_Outbound) OutboundInterface {
oface := OutboundInterface{
DataplanePort: outbound.Port,
}
if outbound.Address != "" {
oface.DataplaneIP = outbound.Address
} else {
oface.DataplaneIP = "127.0.0.1"
}
return oface
}
func (n *Dataplane_Networking) GetInboundInterface(service string) (*InboundInterface, error) {
for _, inbound := range n.Inbound {
if inbound.Tags[ServiceTag] != service {
continue
}
iface := n.ToInboundInterface(inbound)
return &iface, nil
}
return nil, errors.Errorf("Dataplane has no Inbound Interface for service %q", service)
}
func (n *Dataplane_Networking) GetInboundInterfaces() []InboundInterface {
if n == nil {
return nil
}
ifaces := make([]InboundInterface, len(n.Inbound))
for i, inbound := range n.Inbound {
ifaces[i] = n.ToInboundInterface(inbound)
}
return ifaces
}
func (n *Dataplane_Networking) GetInboundForPort(port uint32) *Dataplane_Networking_Inbound {
for _, inbound := range n.Inbound {
if port == inbound.Port {
return inbound
}
}
return nil
}
func (n *Dataplane_Networking) ToInboundInterface(inbound *Dataplane_Networking_Inbound) InboundInterface {
iface := InboundInterface{
DataplanePort: inbound.Port,
}
if inbound.Address != "" {
iface.DataplaneIP = inbound.Address
} else {
iface.DataplaneIP = n.Address
}
if n.AdvertisedAddress != "" {
iface.DataplaneAdvertisedIP = n.AdvertisedAddress
} else {
iface.DataplaneAdvertisedIP = iface.DataplaneIP
}
if inbound.ServiceAddress != "" {
iface.WorkloadIP = inbound.ServiceAddress
} else {
iface.WorkloadIP = iface.DataplaneIP
}
if inbound.ServicePort != 0 {
iface.WorkloadPort = inbound.ServicePort
} else {
iface.WorkloadPort = inbound.Port
}
return iface
}
func (n *Dataplane_Networking) GetHealthyInbounds() []*Dataplane_Networking_Inbound {
var inbounds []*Dataplane_Networking_Inbound
for _, inbound := range n.GetInbound() {
if inbound.GetState() != Dataplane_Networking_Inbound_Ready {
continue
}
if inbound.Health != nil && !inbound.Health.Ready {
continue
}
inbounds = append(inbounds, inbound)
}
return inbounds
}
// GetService returns a service represented by this inbound interface.
//
// The purpose of this method is to encapsulate implementation detail
// that service is modeled as a tag rather than a separate field.
func (d *Dataplane_Networking_Inbound) GetService() string {
if d == nil {
return ""
}
return d.Tags[ServiceTag]
}
// GetProtocol returns a protocol supported by this inbound interface.
//
// The purpose of this method is to encapsulate implementation detail
// that protocol is modeled as a tag rather than a separate field.
func (d *Dataplane_Networking_Inbound) GetProtocol() string {
if d == nil {
return ""
}
return d.Tags[ProtocolTag]
}
// GetService returns a service name represented by this outbound interface.
//
// The purpose of this method is to encapsulate implementation detail
// that service is modeled as a tag rather than a separate field.
func (d *Dataplane_Networking_Outbound) GetService() string {
if d == nil || d.GetTags() == nil {
return ""
}
return d.GetTags()[ServiceTag]
}
const MatchAllTag = "*"
type TagSelector map[string]string
func (s TagSelector) Matches(tags map[string]string) bool {
if len(s) == 0 {
return true
}
for tag, value := range s {
inboundVal, exist := tags[tag]
if !exist {
return false
}
if value != inboundVal && value != MatchAllTag {
return false
}
}
return true
}
func (s TagSelector) MatchesFuzzy(tags map[string]string) bool {
if len(s) == 0 {
return true
}
for tag, value := range s {
inboundVal, exist := tags[tag]
if !exist {
return false
}
if !strings.Contains(inboundVal, value) && value != MatchAllTag {
return false
}
}
return true
}
func (s TagSelector) Rank() TagSelectorRank {
var r TagSelectorRank
for _, value := range s {
if value == MatchAllTag {
r.WildcardMatches++
} else {
r.ExactMatches++
}
}
return r
}
func (s TagSelector) Equal(other TagSelector) bool {
return len(s) == 0 && len(other) == 0 || len(s) == len(other) && reflect.DeepEqual(s, other)
}
func MatchAnyService() TagSelector {
return MatchService(MatchAllTag)
}
func MatchService(service string) TagSelector {
return TagSelector{ServiceTag: service}
}
func MatchTags(tags map[string]string) TagSelector {
return TagSelector(tags)
}
// Set of tags that only allows a single value per key.
type SingleValueTagSet map[string]string
func (t SingleValueTagSet) Keys() []string {
keys := make([]string, 0, len(t))
for key := range t {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}
func Merge[TagSet ~map[string]string](other ...TagSet) TagSet {
// Small optimization, to not iterate over the whole map if only one
// argument is provided
if len(other) == 1 {
return other[0]
}
merged := TagSet{}
for _, t := range other {
for k, v := range t {
merged[k] = v
}
}
return merged
}
// MergeAs is just syntactic sugar which converts merged result to assumed type
func MergeAs[R ~map[string]string, T ~map[string]string](other ...T) R {
return R(Merge(other...))
}
func (t SingleValueTagSet) Exclude(key string) SingleValueTagSet {
rv := SingleValueTagSet{}
for k, v := range t {
if k == key {
continue
}
rv[k] = v
}
return rv
}
func (t SingleValueTagSet) String() string {
var tags []string
for tag, value := range t {
tags = append(tags, fmt.Sprintf("%s=%s", tag, value))
}
sort.Strings(tags)
return strings.Join(tags, " ")
}
// Set of tags that allows multiple values per key.
type MultiValueTagSet map[string]map[string]bool
func (t MultiValueTagSet) Keys() []string {
keys := make([]string, 0, len(t))
for key := range t {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}
func (t MultiValueTagSet) Values(key string) []string {
if t == nil {
return nil
}
var result []string
for value := range t[key] {
result = append(result, value)
}
sort.Strings(result)
return result
}
func (t MultiValueTagSet) UniqueValues(key string) []string {
if t == nil {
return nil
}
alreadyFound := map[string]bool{}
var result []string
for value := range t[key] {
if !alreadyFound[value] {
result = append(result, value)
alreadyFound[value] = true
}
}
sort.Strings(result)
return result
}
func MultiValueTagSetFrom(data map[string][]string) MultiValueTagSet {
set := MultiValueTagSet{}
for tagName, values := range data {
for _, value := range values {
m, ok := set[tagName]
if !ok {
m = map[string]bool{}
}
m[value] = true
set[tagName] = m
}
}
return set
}
func (d *Dataplane) TagSet() MultiValueTagSet {
tags := MultiValueTagSet{}
for _, inbound := range d.GetNetworking().GetInbound() {
for tag, value := range inbound.Tags {
_, exists := tags[tag]
if !exists {
tags[tag] = map[string]bool{}
}
tags[tag][value] = true
}
}
return tags
}
func (d *Dataplane) SingleValueTagSets() []SingleValueTagSet {
var sets []SingleValueTagSet
for _, inbound := range d.GetNetworking().GetInbound() {
sets = append(sets, SingleValueTagSet(inbound.Tags))
}
return sets
}
func (d *Dataplane) GetIdentifyingService() string {
services := d.TagSet().Values(ServiceTag)
if len(services) > 0 {
return services[0]
}
return ServiceUnknown
}
func (t MultiValueTagSet) String() string {
var tags []string
for tag := range t {
tags = append(tags, fmt.Sprintf("%s=%s", tag, strings.Join(t.Values(tag), ",")))
}
sort.Strings(tags)
return strings.Join(tags, " ")
}
// TagSelectorRank helps to decide which of 2 selectors is more specific.
type TagSelectorRank struct {
// Number of tags that match by the exact value.
ExactMatches int
// Number of tags that match by a wildcard ('*').
WildcardMatches int
}
func (r TagSelectorRank) CombinedWith(other TagSelectorRank) TagSelectorRank {
return TagSelectorRank{
ExactMatches: r.ExactMatches + other.ExactMatches,
WildcardMatches: r.WildcardMatches + other.WildcardMatches,
}
}
func (r TagSelectorRank) CompareTo(other TagSelectorRank) int {
thisTotal := r.ExactMatches + r.WildcardMatches
otherTotal := other.ExactMatches + other.WildcardMatches
if thisTotal == otherTotal {
return r.ExactMatches - other.ExactMatches
}
return thisTotal - otherTotal
}