blob: 013ff8b7a1d07422920e681bf0c73bbaa6624d9b [file] [log] [blame]
// Copyright Istio Authors
//
// 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 model
import (
"fmt"
)
import (
"google.golang.org/protobuf/proto"
networking "istio.io/api/networking/v1alpha3"
"k8s.io/apimachinery/pkg/types"
)
import (
"github.com/apache/dubbo-go-pixiu/pkg/config"
"github.com/apache/dubbo-go-pixiu/pkg/config/labels"
"github.com/apache/dubbo-go-pixiu/pkg/config/visibility"
)
// This function merges one or more destination rules for a given host string
// into a single destination rule. Note that it does not perform inheritance style merging.
// IOW, given three dest rules (*.foo.com, *.foo.com, *.com), calling this function for
// each config will result in a final dest rule set (*.foo.com, and *.com).
//
// The following is the merge logic:
// 1. Unique subsets (based on subset name) are concatenated to the original rule's list of subsets
// 2. If the original rule did not have any top level traffic policy, traffic policies from the new rule will be
// used.
// 3. If the original rule did not have any exportTo, exportTo settings from the new rule will be used.
func (ps *PushContext) mergeDestinationRule(p *consolidatedDestRules, destRuleConfig config.Config, exportToMap map[visibility.Instance]bool) {
rule := destRuleConfig.Spec.(*networking.DestinationRule)
resolvedHost := ResolveShortnameToFQDN(rule.Host, destRuleConfig.Meta)
if mdrList, exists := p.destRules[resolvedHost]; exists {
// `addRuleToProcessedDestRules` determines if the incoming destination rule would become a new unique entry in the processedDestRules list.
addRuleToProcessedDestRules := true
for _, mdr := range mdrList {
existingRule := mdr.rule.Spec.(*networking.DestinationRule)
bothWithoutSelector := rule.GetWorkloadSelector() == nil && existingRule.GetWorkloadSelector() == nil
bothWithSelector := existingRule.GetWorkloadSelector() != nil && rule.GetWorkloadSelector() != nil
selectorsMatch := labels.Instance(existingRule.GetWorkloadSelector().GetMatchLabels()).Equals(rule.GetWorkloadSelector().GetMatchLabels())
if bothWithSelector && !selectorsMatch {
// If the new destination rule and the existing one has workload selectors associated with them, skip merging
// if the selectors do not match
continue
}
// If both the destination rules are without a workload selector or with matching workload selectors, simply merge them.
// If the incoming rule has a workload selector, it has to be merged with the existing rules with workload selector, and
// at the same time added as a unique entry in the processedDestRules.
if bothWithoutSelector || (rule.GetWorkloadSelector() != nil && selectorsMatch) {
addRuleToProcessedDestRules = false
}
// Deep copy destination rule, to prevent mutate it later when merge with a new one.
// This can happen when there are more than one destination rule of same host in one namespace.
copied := mdr.rule.DeepCopy()
mdr.rule = &copied
mdr.from = append(mdr.from, types.NamespacedName{Namespace: destRuleConfig.Namespace, Name: destRuleConfig.Name})
mergedRule := copied.Spec.(*networking.DestinationRule)
existingSubset := map[string]struct{}{}
for _, subset := range mergedRule.Subsets {
existingSubset[subset.Name] = struct{}{}
}
// we have an another destination rule for same host.
// concatenate both of them -- essentially add subsets from one to other.
// Note: we only add the subsets and do not overwrite anything else like exportTo or top level
// traffic policies if they already exist
for _, subset := range rule.Subsets {
if _, ok := existingSubset[subset.Name]; !ok {
// if not duplicated, append
mergedRule.Subsets = append(mergedRule.Subsets, subset)
} else {
// duplicate subset
ps.AddMetric(DuplicatedSubsets, string(resolvedHost), "",
fmt.Sprintf("Duplicate subset %s found while merging destination rules for %s",
subset.Name, string(resolvedHost)))
}
}
// If there is no top level policy and the incoming rule has top level
// traffic policy, use the one from the incoming rule.
if mergedRule.TrafficPolicy == nil && rule.TrafficPolicy != nil {
mergedRule.TrafficPolicy = rule.TrafficPolicy
}
// If there is no exportTo in the existing rule and
// the incoming rule has an explicit exportTo, use the
// one from the incoming rule.
if len(p.exportTo[resolvedHost]) == 0 && len(exportToMap) > 0 {
p.exportTo[resolvedHost] = exportToMap
}
}
if addRuleToProcessedDestRules {
p.destRules[resolvedHost] = append(p.destRules[resolvedHost], convertConsolidatedDestRule(&destRuleConfig))
}
return
}
// DestinationRule does not exist for the resolved host so add it
p.destRules[resolvedHost] = append(p.destRules[resolvedHost], convertConsolidatedDestRule(&destRuleConfig))
p.exportTo[resolvedHost] = exportToMap
}
// inheritDestinationRule child config inherits settings from parent mesh/namespace
func (ps *PushContext) inheritDestinationRule(parent, child *consolidatedDestRule) *consolidatedDestRule {
if parent == nil {
return child
}
if child == nil {
return parent
}
if parent.Equals(child) {
return parent
}
parentDR := parent.rule.Spec.(*networking.DestinationRule)
if parentDR.TrafficPolicy == nil {
return child
}
merged := parent.rule.DeepCopy()
// merge child into parent, child fields will overwrite parent's
proto.Merge(merged.Spec.(proto.Message), child.rule.Spec.(proto.Message))
merged.Meta = child.rule.Meta
merged.Status = child.rule.Status
childDR := child.rule.Spec.(*networking.DestinationRule)
// if parent has MUTUAL+certs/secret specified and child specifies SIMPLE, could break caCertificates
// if both parent and child specify TLS context, child's will be used only
if parentDR.TrafficPolicy.Tls != nil && (childDR.TrafficPolicy != nil && childDR.TrafficPolicy.Tls != nil) {
mergedDR := merged.Spec.(*networking.DestinationRule)
mergedDR.TrafficPolicy.Tls = childDR.TrafficPolicy.Tls.DeepCopy()
}
out := &consolidatedDestRule{}
out.rule = &merged
out.from = append(out.from, parent.from...)
out.from = append(out.from, child.from...)
return out
}
func convertConsolidatedDestRule(cfg *config.Config) *consolidatedDestRule {
return &consolidatedDestRule{
rule: cfg,
from: []types.NamespacedName{
{
Namespace: cfg.Namespace,
Name: cfg.Name,
},
},
}
}
// Equals compare l equals r consolidatedDestRule or not.
func (l *consolidatedDestRule) Equals(r *consolidatedDestRule) bool {
if l == r {
return true
}
if l == nil || r == nil {
return false
}
// compare from
if len(l.from) != len(r.from) {
return false
}
for i, v := range l.from {
if v != r.from[i] {
return false
}
}
return true
}