Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions pilot/pkg/model/push_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ type PushContext struct {
// ServiceAccounts map[string][]string
// Temp: the code in alpha3 should use VirtualService directly
VirtualServiceConfigs []Config `json:"-,omitempty"`

destinationRuleHosts []Hostname
destinationRuleByHosts map[Hostname]*combinedDestinationRule

Expand Down Expand Up @@ -290,6 +290,10 @@ func (ps *PushContext) VirtualServices(gateways map[string]bool) []Config {
return out
}

func (ps *PushContext) VirtualService(gateways map[string]bool, hostname Hostname) (Config, bool) {
return Config{}, false
}

// InitContext will initialize the data structures used for code generation.
// This should be called before starting the push, from the thread creating
// the push context.
Expand Down Expand Up @@ -332,19 +336,19 @@ func (ps *PushContext) initServiceRegistry(env *Environment) error {
return err
}
// Sort the services in order of creation.
ps.Services = sortServicesByCreationTime(services)
sortServicesByCreationTime(services)
ps.Services = services
for _, s := range services {
ps.ServiceByHostname[s.Hostname] = s
}
return nil
}

// sortServicesByCreationTime sorts the list of services in ascending order by their creation time (if available).
func sortServicesByCreationTime(services []*Service) []*Service {
func sortServicesByCreationTime(services []*Service) {
sort.SliceStable(services, func(i, j int) bool {
return services[i].CreationTime.Before(services[j].CreationTime)
})
return services
}

// Caches list of virtual services
Expand All @@ -353,7 +357,6 @@ func (ps *PushContext) initVirtualServices(env *Environment) error {
if err != nil {
return err
}

sortConfigByCreationTime(vservices)
ps.VirtualServiceConfigs = vservices
// convert all shortnames in virtual services into FQDNs
Expand Down
112 changes: 112 additions & 0 deletions pilot/pkg/model/radix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright 2018 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 (
"github.com/hashicorp/go-immutable-radix"
"strings"
)

// TODO: if there are conflicts, pick the oldest config

type hostLookup interface {
Lookup(hostname Hostname) map[Hostname]Config
}

type radix struct {
radix *iradix.Tree
}

func newRadix() *radix {
return &radix{
radix: iradix.New(),
}
}

// This function returns the set of the most specific matching configs
// for a hostname. It supports wildcards in both the query hostname as well
// as the config hostnames.
//
// To retrieve the most specific matches, we need to define what we consider more or less specific.
// We define the specificity of a match as the amount of the query host that is matched with the
// config host. A match where more of the query host is matched is considered more specific.
//
// Consider the query hostname "abc.def" and config hostnames "abc.def" and "*.def":
// - The query hostname "abc.def" matches both "abc.def" and "*.def"
// - The query hostname "abc.def" has an exact match of all 7 characters of "abc.def", but only 4
// characters of "*.def". Therefore the match of "abc.def" is considered more specific than
// the match of "*.def".
//
// This definition of specificity becomes important when wildcards are present in both the query
// host and the config host. When the query host contains a wildcard, there can be multiple
// equally specific matches. This is illustrated below with an example:
//
// The query host "*.def" matches "abc.def", "*.def", and "*"
// - the match with "abc.def" and "*.def" have equal specificity: both have an exact match of
// 4 characters with the query host.
// - the match of "*" is less specific than the other two matches, since the exact match is 0 characters.
// thus, the most specific matches are "abc.def" and "*.def"
//
// This function uses a radix to implement the behavior described above.
func (r *radix) Lookup(hostname Hostname) map[Hostname]Config {
configs := make(map[Hostname]Config)
wildcard := strings.Contains(string(hostname), "*")

// If a wildcard is present in the query hostname there may be multiple equally specific matches,
// so we attempt to walk every config hostname under this prefix.
if wildcard {
r.radix.Root().WalkPrefix(r.toKey(hostname), func(k []byte, v interface{}) bool {
config, _ := v.(Config)
configs[r.fromKey(k)] = config
return false
})
}

// If the query hostname has no wildcard, or there were no configs under the prefix, we get the
// longest matching prefix for this query hostname.
if !wildcard || len(configs) == 0 {
k, v, _ := r.radix.Root().LongestPrefix(r.toKey(hostname))
config, _ := v.(Config)
configs[r.fromKey(k)] = config
}

return configs
}

func (r *radix) Insert(hostname Hostname, config Config) {
r.radix, _, _ = r.radix.Insert(r.toKey(hostname), config)
}

// Strips the wildcard character '*' and stores the hostname in the radix in reversed character order.
func (r *radix) toKey(hostname Hostname) []byte {
s := strings.Replace(string(hostname), "*", "", -1)
data := []byte(s)
reverse(data)
return data
}

// Unreverses the hostname.
func (r *radix) fromKey(key []byte) Hostname {
data := make([]byte, len(key))
copy(data, key)
reverse(data)
return Hostname(data)
}

func reverse(data []byte) {
for i := 0; i < len(data)/2; i++ {
data[i], data[len(data)-i-1] = data[len(data)-i-1], data[i]
}
}
72 changes: 72 additions & 0 deletions pilot/pkg/model/radix_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright 2018 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 (
"testing"
)

func TestRadix(t *testing.T) {
r := newRadix()

contents := []struct {
config Config
hostnames Hostnames
}{
{Config{ConfigMeta: ConfigMeta{Name: "cnn"}}, Hostnames{"www.cnn.com", "*.cnn.com", "*.com"}},
{Config{ConfigMeta: ConfigMeta{Name: "edition_cnn"}}, Hostnames{"edition.cnn.com"}},
{Config{ConfigMeta: ConfigMeta{Name: "*.co.uk"}}, Hostnames{"*.co.uk"}},
{Config{ConfigMeta: ConfigMeta{Name: "*"}}, Hostnames{"*"}},
{Config{ConfigMeta: ConfigMeta{Name: "io"}}, Hostnames{"*.io"}},
{Config{ConfigMeta: ConfigMeta{Name: "*.preliminary.io"}}, Hostnames{"*.preliminary.io"}},
}

for _, content := range contents {
for _, hostname := range content.hostnames {
r.Insert(hostname, content.config)
}
}

testCases := []struct {
in Hostname
out Hostnames
}{
{"www.cnn.com", Hostnames{"www.cnn.com"}},
{"money.cnn.com", Hostnames{".cnn.com"}},
{"edition.cnn.com", Hostnames{"edition.cnn.com"}},
{"bbc.co.uk", Hostnames{".co.uk"}},
{"www.wikipedia.org", Hostnames{""}},
{"*.cnn.com", Hostnames{"www.cnn.com", ".cnn.com", "edition.cnn.com"}},
{"*.com", Hostnames{".com", ".cnn.com", "www.cnn.com", "edition.cnn.com"}},
{"*.uk", Hostnames{".co.uk"}},
{"*.istio.io", Hostnames{".io"}},
{"*.preliminary.io", Hostnames{".preliminary.io"}},
{"*.io", Hostnames{".io", ".preliminary.io"}},
{"nothing.nowhere.net", Hostnames{""}},
// {"*", Hostnames{"www.cnn.com", ".cnn.com", ".com", "edition.cnn.com", "", ".co.uk"}}, // maintenance burden
}

for _, tt := range testCases {
configs := r.Lookup(tt.in)
if len(tt.out) != len(configs) {
t.Errorf("f(%v) -> wanted len()=%v, got len()=%v", tt.in, len(tt.out), len(configs))
t.Errorf("%#v", configs)
}
for _, h := range tt.out {
if _, ok := configs[h]; !ok {
t.Errorf("f(%v) -> missing %v", tt.in, h)
}
}
}
}
1 change: 1 addition & 0 deletions pilot/pkg/networking/core/v1alpha3/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ func (c outboundListenerConflict) addMetric(push *model.PushContext) {
// buildSidecarOutboundListeners generates http and tcp listeners for outbound connections from the service instance
// TODO(github.com/istio/pilot/issues/237)
//
// FIXME: the doc below is out of date...
// Sharing tcp_proxy and http_connection_manager filters on the same port for
// different destination services doesn't work with Envoy (yet). When the
// tcp_proxy filter's route matching fails for the http service the connection
Expand Down